Part of meta-issue #1788918: [GTD] [META] Prepare for Twig main engine core inclusion until December 1
Problem/Motivation
In Drupal 7 we were using PHP print statements in field.tpl.php to add our RDFa attributes around each field formatter. We typically had these attribute variables: $attributes
, $title_attributes
, $content_attributes
and $item_attributes
in node.tpl.php, field.tpl.php, etc. The latter $item_attributes
was used to wrap each field item with the appropriate attributes in field.tpl.php. Here is the example of a text field output:
<div class="field field-name-body field-type-text-with-summary field-label-hidden">
<div class="field-items">
<div class="field-item even" property="schema:text">
<!-- field formatter output is inserted here, without any way to add RDFa attributes. -->
</div>
</div>
</div>
Drupal 7's limitations
Placing the attributes on the item wrapper element worked for some core fields in D7, those which were outputting simple one value items (text or number). We started to feel the limit of this approach for fields like file or image where we were forced to duplicate the URL value on the item wrapper because it was not possible to inject the RDFa markup inside the field formatter in the same element as the file link. See for example this image field output (note how the file URL is repeated):
<?php
<div class="field-item even" property="og:image" resource="http://openspring.net/sites/openspring.net/files/styles/medium/public/8553992227_c90f23718a.jpg">
<a href="/week-ecuador-viva-drupal-latino"><img alt="" src="http://openspring.net/sites/openspring.net/files/styles/medium/public/8553992227_c90f23718a.jpg" /></a>
</div>
?>
In an ideal situation, the RDFa attribute 'property' would be placed on the img element where the image URL is, but because D7 didn't have any way to achieve that, we had to resort to duplicating the image URL in the wrapping element. This markup is suboptimal. The ideal markup for D8 is to simply add one RDFa attribute (property="..") to the img element inside the field formatter:
<?php
<div class="field-item even">
<a href="/week-ecuador-viva-drupal-latino"><img alt="" property="og:image" src="http://openspring.net/sites/openspring.net/files/styles/medium/public/8553992227_c90f23718a.jpg" /></a>
</div>
?>
Besides requiring duplicate values in the wrapping element, D7's suboptimal method of placing RDFa markup in the wrapping element failed when it came the time to RDFa-ize compound fields from contrib which have multiple elements inside each item, for example addressfield (street, locality, country, zip code) or fivestar (rating, number of votes). There is only so much structured data you can cram into a single HTML element.
Lesson learned: do not solely rely on in the item wrapper to place RDFa attributes, it's not a generic, scalable solution.
As more field types like date and link are moved to core, it becomes crucial to find a solution to this problem, and be able to inject attributes in the HTML elements closer to the actual values inside the field formatters. For date.module, we want to be able to take advantage of the HTML5 time element and inject the RDFa property attribute in the time element, and for link, we want to be able to annotate both the URL and the link text. We can't achieve this without being able to inject RDFa attributes inside the field formatters.
Proposed resolution
Pass the RDFa attributes to the field formatter and give it a chance to place the attributes at the right place in HTML markup it generates. If the field formatter does not use these attributes and leave them set in the field item, the field template will inject them in the usual location (like it currently does in Drupal 7).
This issue focuses on the fields which were part of Drupal 7 and are currently tested in the RDF module: text, taxonomy_term, file, image. Other fields are covered in #2034951: [META] Support and test all field formatters RDFa output. Compound fields are covered in #1778194: RDF module can't handle compound fields. Note that this issue does not cover data like post date, etc., as those items are not provided by fields.
User interface changes
none
API changes (most of them are API additions)
- The logic for loading the mappings and generating the attributes in hook implementations rdf_preprocess_field() and rdf_field_attach_view_alter() is removed and replaced by a single rdf_entity_prepare_view() which generates the RDFa attributes for each field items about to be displayed.
- Field items have a new property 'html_data_attributes' ($item->html_data_attributes
) which contains the attributes to be placed in the HTML output by the field formatters or the field template.
- Field formatters have the ability to inject RDFa attributes inside the HTML markup they generate. The RDFa attributes are generated by the RDF module in rdf_entity_prepare_view(), and placed in the new field item property html_data_attributes
, which the field formatters can then inject at the right place in their HTML output. If a field formatter leaves html_data_attributes
intact, these attributes will be inserted in the field template as it is currently the case in Drupal 7. If a field formatters makes use of the HTML data attributes, it should unset them so that they are not added in the field template.
- theme_file_link() accepts a new parameter 'attributes', which contains attributes to be placed in the a element (necessary for optimal RDFa markup for file field link.)
- The mapping type mapping_type: 'rel'
no longer needs to be specified in the RDF mapping definitions. These were necessary in Drupal 7 in order to generate a different set of attributes for the field wrapper depending on the type of object contained in the field wrapper. mapping_type: 'rel'
was used when the object in the field output was another resource (with a URI) such as a file, as opposed to a string/literal (e.g. text field). This is no longer necessary because we can now inject the attributes directly in the field formatter.
Performance
ab showed some small performance gain in some situations, see details in #52. Additionally, this patch also reduces the markup size in the case of file and image fields, since the URL does not need to be processed on the backend and repeated in the final HTML output.
Steps for reviewing the intended results of this patch
Here is a quick outline for patch reviewers. Viewing the intended effects is relatively simple, despite the far-reaching changes to code.
- On a fresh installation of Drupal 8, create an entity with contents in one or all of the following fields: text, taxonomy term, file, image.
- View the entity and save the source for these fields for later.
- Apply the most recent patch
- Reload the entity and compare the source to that from before the patch
Comment | File | Size | Author |
---|---|---|---|
#120 | 1778122-120.patch | 814 bytes | Anonymous (not verified) |
#117 | module_enable-1778122-117.patch | 861 bytes | tim.plunkett |
#113 | 1778122-113-formatter-attributes.patch | 28.87 KB | scor |
#113 | interdiff.txt | 1.44 KB | scor |
#109 | 1778122-109-formatter-attributes.patch | 28.7 KB | scor |
Comments
Comment #1
Anonymous (not verified) CreditAttribution: Anonymous commentedI was in the process of posting #1778194: RDF module can't handle compound fields while you were posting this. In that issue, I explain the additional problem that the RDF mapping can't nest field property mappings inside their fields.
I do agree that using Twig for this would be the best route, I talked to yched, effulgensia, and others about this concept at DrupalCon and AFAIK there weren't any concerns raised.
Comment #1.0
scor CreditAttribution: scor commentedtypo
Comment #2
Anonymous (not verified) CreditAttribution: Anonymous commentedThe important part of this isn't the Twig template as much as the fact that the attributes are being injected in the field formatter. Changing title to reflect this.
Comment #3
Anonymous (not verified) CreditAttribution: Anonymous commentedI spoke with swentel and alexpott about this today. They both agreed that we should make sure that all of core's field formatters use theme functions (text doesn't at the moment) and then add the attributes in the twig templates.
Comment #4
scor CreditAttribution: scor commentedNote that as per a conversation with effulgentsia a few days ago, July 1st is an API freeze and not a code freeze, so that means that API implementations can still happen after July 1st, but APIs themselves need to be committed before July 1st. Let's try to identify the bits that require API addition/change, and prioritize from there.
Single value fields with @property in field item
My initial thought was follow Lin's recommendation and to push all the attribute placeholders that we have in field.html.twig down to the field formatters. However after trying to implement this I found that this would make the markup of some of the basic value fields more complicated and would introduce additional HTML elements. In other words, the field item twig file provides (in some cases) the most appropriate place to add attributes. Consider for example the telephone field. I created a field of type telephone to see the markup Drupal generates at the moment. This is what I added to my rdf.mapping.node.article.yml (using a made-up dc:telephone property):
It generated this markup:
Plain text formatter:
Telephone link formatter:
Both snippets generate the expected output, which extracts the telephone value (not the @href tel: value). Note that schema.org expects a "Text" type here, which is what we generate.
If we were to push the property attribute inside the field formatter, we would need to add a wrapping span HTML element around the number in the plain text formatter, or a span element inside the a element for the link formatter. By injecting the property attribute at the field level, we don't need to add additional HTML elements.
The email field is similar. Email Plain text formatter:
Email field formatter:
(same conclusion as telephone above, schema.org expects the text value, not a mailto: value, so we are good).
Other fields which are working with field item:
- longtext, though longtext could be considered hybrid-compound because it has multiple values (default and trimmed) but only one is displayed at a given time. Offering the ability to define different mappings is dealt with in a separate issue.
- text
Other fields with attributes in their field formatter
On the other hand, some fields require to have the attributes added inside the field formatter:
- Date: the default field formatter output a time element where we want to add the property attribute.
- File: this is a single value field (the URI of the file) and we want to add @property in the a element of theme_file_link. Note however that file theme functions haven't been converted to Twig yet (#1898070: file.module - Convert theme_ functions to Twig). In D7 we added @property in the field item tpl with @resource file URI.
- Image: same as file. Image hasn't been converted to Twig yet: #1939068: Convert theme_image() to Twig.
- Link: similar to file. We want to add @property in the a element to capture @href. When "URL only" and "Show URL as plain text" as checked, @property should be added to the field item because the field formatter outputs a bare URI value. Link hasn't been converted to Twig yet: #1898426: link.module - Convert theme_ functions to Twig.
- Compound fields in general (addressfield, voting API).
Conclusion
Given the analysis above, I would suggest to have a dual approach where field formatters can decide whether the RDFa attributes should be pushed up to the field item template, or injected in the field formatter output. By default, unless a field formatter specifies one way or the other, no attribute would be added anywhere, this would prevent malfunctioning RDFa markup to be generated like in D7 where @property can get added to the field item when there is no valid value in the field formatter. This would solve #1778034: Field formatter developers can easily pollute a site's RDFa output.
This is the API we should hash out here, and implementations can be done post July 1st. I'm going to leave it at that for this comment as it's getting pretty late here. More tomorrow. In the meantime maybe others can say whether they agree and start tossing ideas on how to do this.
Comment #5
Anonymous (not verified) CreditAttribution: Anonymous commentedI think that we want to consider placing the attributes in the templates for all field properties. For example, the 'value' property for text fields would be placed in the field formatter. This makes the API consistent.
Comment #6
scor CreditAttribution: scor commentedok, so I think I found a good compromise. The hybrid approach I was talking about in #4 is a nice to have, but not mission critical. On the other hand we need to inject markup in field formatters, so let's focus on that in this issue and add a wrapping element in the field formatter when needed. Deal?
Could we make the additional wrapper optional (aka not hard-coded / forced) in the field formatter so that it is not added when there is no mapping, and so it can be disabled if a contrib wanted to implement the hybrid approach above?
I would suggest to leave the implementations of this API in separate issues, so that we can move quicker on this issue which is the one bringing API changes in core.
Comment #7
Anonymous (not verified) CreditAttribution: Anonymous commentedYes, I was thinking that the placement of attributes would be conditional. We wouldn't want additional markup to be added if RDF was not enabled... and, while I hadn't thought of it, your suggestion not to add additional markup if there is no mapping makes sense.
Comment #8
Anonymous (not verified) CreditAttribution: Anonymous commentedSo this is just a quick POC of a possible solution. It introduces a
hook_formatter_prepare_view
, which gives RDF module the chance to add the HTML data attributes it needs to.The only formatters that have been altered to demonstrate the pipeline are TextPlainFormatter and MailToFormatter. You can see the passing EmailFieldRdfaTest, which tests these two formatters. The email formatter doesn't actually need this more fine-grained attribute placement, it's already exposing data correctly without any hackery, but it was the easiest to test and demonstrate with.
The patch also removes
rdf_preprocess_field
, which might come back later once we have an API for handling compound fields, andrdf_field_attach_view_alter
, which engaged in some hackery and should be rendered unnecessary by this work. Many tests will fail because these were removed.I expect that the patch will negatively impact performance. I'm open to suggestions for improving the performance. However, I don't think that we can accept the status quo as an option. We need to ensure that the data that we expose is more reliable than it is in D7.
This patch depends on #2034975: Test RDFa output in email formatter and #2031797: Provide a RDF wrapper twig template for renderable variables + attributes.
Comment #10
Anonymous (not verified) CreditAttribution: Anonymous commentedThis patch demonstrates how the solution would work for taxonomy links. Because we insert the attributes into the
<a>
tag itself, we no longer need to use the 'rel' property, which we are trying to get away from as explained in #1780090: Decide: Use RDFa Lite's property attribute, or give users choice.The interdiff only includes the changes relevant to changing how the attributes are placed, it does not include the merge I did to bring the taxonomy test into this patch.
Comment #12
scor CreditAttribution: scor commentedwe might want to follow recommendations from #2036765: Drupal\rdf\Tests\StandardProfileTest needs clean up regarding the test method name.
s/Get/Gets
s/Attach/Attaches
s/Add/Adds
let's use the 'resource' attribute here which is part of RDFa Lite.
As far as I can tell, the MailTo and TextPlain formatters add the wrapper no matter whether there are RDF mappings, right, or did I miss something? Is there a way to only add the wrapper if there are going to be attributes in it?
Comment #13
Anonymous (not verified) CreditAttribution: Anonymous commentedThis is a Proof of Concept, so I was hoping for some architectural feedback. For example, it adds a new hook as I mention in #8. I'm not sure whether we want to do this, or whether there are better alternatives.
Comment #14
scor CreditAttribution: scor commentedI'm curious, don't we have any use case of this in core yet? I guess not since they would break without this code. What happens right now if a module passes an array in #title? Surely RDF is not an isolated use case for having markup in a link title.
Comment #15
Anonymous (not verified) CreditAttribution: Anonymous commentedThe content model of the
<a>
element changed in HTML5. It didn't allow nested block elements before.EDIT: clarified that it didn't allow nested block elements before
Comment #16
yched CreditAttribution: yched commentedDo we want to let each formatter actually control (or forget) whether it needs to call the FormatterBase::prepareView() ?
If not, since this is not really formatter-related, I'd be inclined to move the hook invocation inside field_attach_prepare_view() ?
More generally, adding those extra properties directly in the $items held by the entity is a bit hackish. I do get that $items it's the only thing formatters receive currently, so is the only thing fitted to pass additional data.
#2021817: Make widgets / formatters work on EntityNG Field value objects. is about making formatters (prepareView() / view() / viewElement() methods) receive $items as EntityNG Field objects instead of the old D7 array format (that is currently generated from the native Field object before calling the formatter, and transleted back). Wondering what the perf impact would then be of adding ad-hoc properties in those NG FieldItems. Could use @fago's opinion on this one...
Strictly speaking, those elements are not part of the entity itself, they are derived data computed at display time.
For those kind of things, I've been considering whether it would make more sense to let the "prepareView" phase build an $extra array, ditched after rendering, rather than cram random additional properties in the $items themselves (which makes the Entities stateful, since viewing them alters their content currently)
Comment #17
Anonymous (not verified) CreditAttribution: Anonymous commentedThanks for taking a look, yched.
This makes sense to me.
I agreee. Either of your suggestions sound good to me, though I would lean towards the second. I'll try it in the next patch.
Comment #18
Anonymous (not verified) CreditAttribution: Anonymous commentedI was looking at how to move the logic into field_attach_prepare_view, but it looks like field_attach_prepare_view is deprecated for D8. yched and I discussed this at NYC Camp and agreed that we should get @fago or @berdir to look at this, as yched explains in #16.
Comment #19
yched CreditAttribution: yched commentedSummary of the discussion we had with @linclark
Myself from #16
Basically this means introducing a $view_state similar to the $form_state we have on the FAPI side.
While it makes sense IMO, I'm not sure of the extend of the API impact at this time in the cycle...
So I guess this boils down to :
@fago, @Berdir, do you guys think the approach in #10 - let 3rd party code add random properties (not listed in getPropertiesDefinition()) in FieldItem objects during the render callstack - is OK performance-wise ?
Comment #20
effulgentsia CreditAttribution: effulgentsia commentedFrom #4:
That makes this either a task or a bug report, but not a feature request. We have rdf.module in core, and as of D8, we also have date and link field types in core, and we need to make sure those all work together correctly. I'm categorizing this as a task for now, because I don't quite understand what the specific bug is in terms of what happens when the RDFa is on the less optimal item wrapper element, as it is currently in HEAD. If someone can explain it as a bug, then please recategorize this accordingly.
Also, raising to major, because entities, fields, and semantic correctness of their output is so foundational to Drupal.
Comment #20.0
effulgentsia CreditAttribution: effulgentsia commentedUpdated issue summary.
Comment #21
scor CreditAttribution: scor commented@effulgentsia: I've updated the issue summary (see limitations section), let me know if it's more clear. Bottom line is we should move away from using the wrapper to place our RDFa attributes.
Comment #21.0
scor CreditAttribution: scor commentedexplains more about D7's limitations
Comment #22
fagoThat all looks and sounds reasonable, but wouldn't the span stay even if the attributes are empty, i.e. RDF module is disabled? I guess front-end people wouldn't be happy about that, so could we make it in a way it only creates the wrapper-spans when necessary?
I think it's ok, but I don't think it's an optimal solution as putting stuff in $item just for copying it further later on is a bit weird. Couldn't that be an alter of the render array instead? What we are doing here is not really prepare-viewing, i.e. pre-load stuff or so - is it? It's RDF taking part at the "view" process itself, not?
Comment #23
scor CreditAttribution: scor commentedI agree with you. That would depend on individual field formatters, some of them already output a wrapper no matter what. Address field for example already wraps each individual values in a span, for example:
we would take advantage of that and add our RDFa attribute in that same existing span. Some fields such as the text field don't have a wrapper inside the field formatter, so in this case it would be optionally added when RDF is enabled. The patch #10 already has this concept of html_data_wrapper.
Comment #24
yched CreditAttribution: yched commentedAs I wrote in #16, I totally agree that adding those in each $item in prepare_view is a bit off.
Hence my proposal of adding them to a $view_state array, similar to $form_state, that would be passed along the view callstack.
That seems like a reasonable flow: collect additional info from 3rd party at prepare_view time, then let the formatter deal (or not) with that info - since, if I get things right, we need to move to a model where formatters (or at least some of them) need to be aware of RDF matters (or at least be aware that some modules might want to add some granular HTML attributes).
But as @fago suggests, if this can be handled by having rdf_entity_view_alter() post-adding the needed attributes to the render arrays built by the formatters, it might be easier at this point in the cycle (less API change impact). It then becomes the responsibility of the theme function used by the formatter to use make use (or not) of those elements from the render array. The formatter class itself stays completely ignorant of RDF / 3rd party additions.
I guess that in the first approach, the formatter will probably just stick the attributes in the render array and let the theme function handle them as well - which would in the end still make outputting rdf be the responsibility of the theme function ?
I have a bit of a hard time deciding which approach is cleaner :-) Thoughts ?
Comment #25
scor CreditAttribution: scor commentedyou're spot on, Yves. I like this approach. rdf_entity_view_alter() could populate the appropriate attributes for each field formatter value (the column values, not sure what they are called now in D8), and the field formatter would be responsible for adding these attributes at the right place in the HTML output. The field formatter could check whether there are attributes or not for a given value, and may decide to not wrap the value if there is no attribute to add. I'm not sure whether it would be up to the RDF / 3rd party module to navigate the render array to find all the column values, or alternatively, there could be a dedicated "value_attributes" array/registry where 3rd party module could add attributes for each rendered value, and the field formatter would pick the attributes from this array/registry when rendering. Here is a quick and dirty untested POC of the second idea for an address field (combining column name + CSS classes from the D7 addressfield and schema.org properties):
I'm using a generic concept of 'value attributes' here since modules might want to add CSS classes or other attributes beyond semantic RDFa or HTML data.
Comment #26
Anonymous (not verified) CreditAttribution: Anonymous commentedSo one problem is that many formatters do not use theme functions. For example, theme_link isn't actually used for most core formatters that output links, but instead
'#type' => 'link'
, which results in a call tol()
if I'm not mistaken.If we feel OK about converting ALL formatters to use formatter-specific templates/theme functions, then this would work. However, we would need to verify that that change is acceptable.
Comment #27
scor CreditAttribution: scor commentedso, I misread yched's comment. My assumption was that rdf_entity_view_alter() would run before field formatters run their viewElements(), which is not the case. Is there any other hook that RDF/3rd party modules could implement that would run before viewElements(), so that the attributes are available during viewElements? Then viewElements() could use these attributes as needed (see my example in #25).
Comment #28
yched CreditAttribution: yched commentedThere's hook_entity_prepare_view(), which would be a better fit than the hook_formatter_prepare_view() introduced in #10 (hook_entity_prepare_view() costs one single hook invoke for an entity_view() or entity_view_multiple(), while the hook introduced in #10 would be one hook invoke per field).
So, am I right in stating that we have an agreement on the following ?
1) RDFa (well, "extra attributes") need to be collected before formatters kick in and produce the final render array, so that formatters (or the theme functions they end up using, if any) can use them (or not) in whatever way they see fit.
2) For each field, those "extra attributes" and their granularity depend only on the field definition (field type...) and on the field values for a given entity. They are formatter-agnostic, and do *not* depend on the specific formatter that will output the values (well, except - no need to compute attributes for fields that are not displayed, right ?)
I'd especially like confirmation of 2), since I have a hard time convincing myself that the attributes are completely independent of the final shape of the HTML.
Comment #29
yched CreditAttribution: yched commentedTo give concrete examples:
- for a date field, do the exact same attributes make sense whether the date is displayed as a human readable, ISO, timestamp... ?
- for an image, do the exact same attributes make sense whether the image is displayed as a "thumbnail", "large", original image, raw file name, link to the file... ?
Because of course, if the actual set of attributes has to depends on the formatter, then the problem becomes much more complex...
Comment #30
scor CreditAttribution: scor commentedThese are excellent questions, yched.
yes, I think this is essential to solving this issue, and it allows the field formatter to be smart about deciding to add a wrapper if there are attributes. This is also required because as pointed out in #26, some field formatter don't use Twig templates, so there might not be a way to add these attributes at a later stage in the rendering process.
I can't think of a field type where the attributes would differ based on the formatter. So yes, I think it's safe to assume that the attributes for all values can be prepared before we hit the formatter, and then the formatter can place these where it makes sense. So the placement of the attributes might differ from one formatter to the other, but the attributes themselves would remain the same for all formatters.
I just checked and all the date field formatters use the HTML5 time element with the datetime attribute already prefilled with the right machine readable value, so we would just need add our 'property' attribute to that element (no matter which formatter is selected).
We would markup the URL of the image, whichever size it rendered. The attributes would not include the URL of the image, only the mapping (e.g. schema:image), so in this case also, the attributes are the same and formatter agnostic. The contract given with these value attributes is this: here is the attributes that should be placed on the HTML element that contains the image URL. The formatter is in charge of placing these attributes (more likely one 'property' attribute in this particular case) on the right HTML element. If it's a large image with no link, place the RDFa attribute in the img element. If it's the original image with a link, place the RDFa attributes in the img element (ignore the a element). If it's a link to the image file, place the attributes in the a element.
In a nutshell, I think all your assumptions are sound, yched.
Now, to the hook used to populate these attributes, I guess one possibility is hook_entity_prepare_view() like you said in #28. The RDF module would go through each field and populate such attributes. Not sure yet where these attributes would be stored so they are preserved until we get to the formatter viewElements() code.
Another alternative would be to invoke a new hook at the beginning of viewElements(), maybe called hook_formatter_value_attributes() - better naming welcome. We would need the field data and the entity passed in the hook, from there RDF could do its work. This hook would be invoked for each field that is being rendered.
Comment #31
yched CreditAttribution: yched commentedExactly, this is the issue that was raised earlier about sticking extra data right into the field value items. We still need to solve that, but at least now that we've established the code flow that we need, we know that this is what we need to figure out.
Right, and that's a performance no-go IMO. Better to leave hook_entity_prepare_view($entities) implementations the burden of iterating on the entities and the fields inside those entities. entity_view() / entity_view_multiple() are very much on a critical path.
Comment #32
Anonymous (not verified) CreditAttribution: Anonymous commentedI agree with yched.
Comment #33
Fabianx CreditAttribution: Fabianx commentedJust a heads up, we are trying to solve the issue of #type doing just rendering via l(), etc. in #2052253: [META] Add #render property to drupal_render() and convert #type "#pre_render -> #markup" calls to use it and related to that #2052271: Introduce theme_info() call, deprecate theme() and have theme_info() just return render array information (like element_info()) (even if theme_info() will probably not happen).
So if anyone could again very clearly state the need / problem that exists here (with an example), it would help us to take care of that in the theme system overhaul.
Comment #34
scor CreditAttribution: scor commentedThis is a straight reroll of #10.
Comment #35
scor CreditAttribution: scor commentedPer #20, moving this to bug report because (1) file and image fields can't support RDFa Lite markup without this and (2) date field is currently hard-coding its RDFa attributes in its formatter.
I removed hook_formatter_prepare_view() which was introduced earlier in this issue, and use the existing hook_entity_prepare_view() instead which is only called once per entity. I added support for TextDefaultFormatter and updated the other formatters to use the new attributes which are now added by rdf_entity_prepare_view() to each property of the entity. The RDF module adds its attributes to each
$property->html_data_attributes
. I'd appreciate some feedback on whether there is a better place to store them during the rendering.I need to update the tests next.
Comment #37
scor CreditAttribution: scor commentedoops, forgot to remove FieldRdfaTestBase.php from the patch. Here is a patch that applies.
Comment #39
jibranTwig template should be used here instead of theme function and we can move logic in preprocess.
Comment #40
Fabianx CreditAttribution: Fabianx commentedEven #type => html_tag might be enough.
Comment #41
scor CreditAttribution: scor commentedThe entity_test entity type does not have a render controller defined, so I switched to a node based test class to be able to test the formatters. email formatter tests are passing locally.
Comment #42
scor CreditAttribution: scor commentedaddress comments #39 and #40 by using '#type' => 'html_tag' in theme_html_data_wrapper().
Comment #44
Fabianx CreditAttribution: Fabianx commentedNope, this is already happening in l().
The html to TRUE should be set explicitly by the calling code.
Uhm, I think this should merge, rather than overwrite.
Also needs tests for this case.
Remove the theme function, use #type => html_tag in the render array directly.
Use #type => html_tag here. and in the other case as well.
Do most of these field formatters really have no wrapper that the attributes could be merged to?
Comment #45
scor CreditAttribution: scor commented@Fabianx: thanks for the review :) Addressed all your comments, see also below.
For this particular case of MailToFormatter,
$elements[$delta]
is initialized just in the code above as an array, so there is no chance for$elements[$delta]['#attributes']
to already be set.It depends if we can use an element that the formatter already generates. In the case of a taxonomy term link or email link, we can directly add our RDFa attributes to the a element. It's also the case for the telephone field for example, which will be dealt with in #2034951: [META] Support and test all field formatters RDFa output. But in the case of text, there is typically no wrapper so that's when we need to add our own.
In this patch I'm also adding TaxonomyTermReferenceRdfaTest.php from #10 in order to keep tests in sync with the formatters which are being updated in this patch.
Comment #47
scor CreditAttribution: scor commented- updated rdf_entity_prepare_view() to support multiple field items
- added support for taxonomy term reference as plain text
- disabled the tests for the type and title of referenced taxonomy terms. It's something that Drupal 7 outputs, I've created this follow up issue: #2072791: Output RDFa markup for type + title in taxonomy term reference and entity reference formatters. Let's keep the current issue on basic field formatters.
- added support for image and file field formatters. The latter required to add a new key to theme_file_link() in order to allow the formatter to pass some attributes to be added to the a element.
- removed mapping type rel from standard profile since we no longer rely on it in the formatters.
Comment #49
scor CreditAttribution: scor commented- disabled the tests for the type and title of referenced taxonomy terms in the standard profile test (to be handled in #2072791: Output RDFa markup for type + title in taxonomy term reference and entity reference formatters)
- added support for TextTrimmedFormatter formatter (teaser)
Comment #49.0
scor CreditAttribution: scor commentedadd anchor
Comment #49.1
scor CreditAttribution: scor commentedreduce the summary to focus on what's relevant for this isssue, and switch focus from Twig templates to field formatters (since most field formatters are in fact not Twig templates).
Comment #50
scor CreditAttribution: scor commentedLet's handle the new fields in #2034951: [META] Support and test all field formatters RDFa output. I've removed the email field hunks from this patch, in order to focus on the older fields that were present in Drupal 7 and are already tested in rdf.module.
Comment #50.0
scor CreditAttribution: scor commentedissue coverage
Comment #50.1
kay_v CreditAttribution: kay_v commentedadd reference to compound field issue
Comment #50.2
kay_v CreditAttribution: kay_v commentedUpdated issue summary.
Comment #51
scor CreditAttribution: scor commentedThere were some leftover calls to parent::prepareView() which I removed in this patch. They were first introduced with the hook_formatter_prepare_view() which is no longer used.
Comment #52
scor CreditAttribution: scor commentedI ran some performance benchmark locally measuring page load times with ab and devel_generate, and notice a tiny performance improvement on frontpage with many nodes, and no impact on pages with lots of comments.
50 nodes on the frontpage, each with an image, a body and a text field with 10 items (average times on 3 runs of ab -n 100):
8.x = 803.5ms
w/ patch = 791.5ms
impact: 1.5% faster
node with 50 comments (average times on 3 runs of ab -n 100):
8.x = 1256ms
w/ patch = 1258ms
impact: 0%
I suspect the performance gain is due to removing the calls to hook_preprocess_HOOK() for field.tpl.php, since we now only call hook_entity_prepare_view() once for each entity.
Comment #53
yched CreditAttribution: yched commentedwhy not entity_view() ?
it's a little sad to switch tests from entity_test to node - can't we just add EntityRenderController as a render controller in EntityTest annotations ?
Could be just $this->entity->{$this->fieldName}->target_id = $this->term->id();
Possibly even, using more implicit magic : $this->entity = $this->drupalCreateNode(array('type' => 'test_node_type', $this->fieldName => $this->term->id())); (I'm less sure about that)
Perf: rdf_get_mapping() doesn't seem to be statically cached, so this will load a config entity over and over again for all entities of the same bundle ?
(although I guess HEAD as the same issue ?)
This is where I'm lost. This code adds one html_data_attributes for all deltas, rather than one per item ?
Also, the actual field values are not passed to rdf_rdfa_attributes() (they were, in previous rdf_preprocess_field()). So those can't be the final data_attributes (per item, built on item values), right ?
+ This is where https://drupal.org/node/2072791 would kick in ? Do we have a notion what it would look like ?
+ Minor:
Space after foreach
Current code uses $field or $items rather than $property
Doesn't seem needed ?
Could be shortened to
$elements[$delta]['#options'] += array('attributes' => array());
Also, a similar construct would streamline the hunk in ImageFormatter.
Wondering how much of this could / should be moved to a helper method in FormatterBase ? Probably no big deal, though.
Comment #54
scor CreditAttribution: scor commentedThanks yched for this review. Fixed all your remarks except the ones discussed below.
I was able to use entity_test_render instead of node, thanks to larowlan for pointing this out.
Yes, this is not specific to this patch. I've created #2076769: Optimize rdf_get_mapping() performance to track this (see my remarks in the issue summary).
So far only text fields have required this wrapper, so I'm not sure if it will be that common across all formatters. Also, depending on the formatter, you might want to use a different tag (div or span), so the method would have to have this as paramter. We had a theme function earlier, but I'm not sure this abstraction level brings us much benefits (#39). Let's revisit this once we're covered all formatters, I've added your suggestion as a comment in the relevant META issue.
Are you suggesting to attach the mappings to the field and dispatch them to each item in the formatter? I can roll a new patch with that approach.
Comment #55
yched CreditAttribution: yched commentedOh, ok, sorry, I misread the code snippet, it does assign html_data_attributes to each item separately. I guess it's because var names are misleading. I'd suggest:
Then what I don't get is that html_data_attributes doesn't depend on the actual field values ?
rdf_rdfa_attributes() only gets passed $field_mapping here, not the actual field values.
In current HEAD, rdf_preprocess_field() does
$variables['item_attributes'][$delta] = rdf_rdfa_attributes($field_mapping, **$item**);
Comment #56
scor CreditAttribution: scor commentedThanks again, @yched. Fixed both issues in attached patch, and added a test for the value passed to rdf_rdfa_attributes().
Comment #57
yched CreditAttribution: yched commentedgetString() seems weird, $item->getValue() would be more like what you need - i.e "the item as a ($property_name => $property_value) array", which is what rdf_preprocess_field() was passing rdf_rdfa_attributes() so far.
(sorry, I could have mentioned that method explicitly in my earlier post)
AAMOF, *if* rdf_rdfa_attributes() works exclusively on field items (but I'm not sure that's the case), it would be worth refactoring it in a followup to accept FieldItem objects directly.
The last thing that bugs me is that all those $item->html_data_attributes properties stay in the $entity for the rest of the request, and can get stale if field values are changed, or if the same entity is displayed again in a different view mode later in the request.
Can't easily check right now, but I'm not sure there's a place / hook where those could be cleaned up / removed once formatters have done their job.
We should at least make sure that rdf_entity_prepare_view() resets html_data_attributes to at least an empty value on all fields in the entity ?
Comment #58
scor CreditAttribution: scor commentedGood point, I've fixed that in the attached patch. Note that rdf_rdfa_attributes() is not only used for fields, we use it for example to generate the number of comments link which is not a field.
I'm not sure this is necessary because the html_data_attributes are unlikely to change in most cases since the mappings remain the same (we don't support changing these on the fly during the same request). The only case I can think of is if we use datatype_callback which adds the value variant into a @content attribute. But I would think that if entity values are to be changed during a request, shouldn't they be changed prior to viewing/rendering the entity? (in which case the attribute would inherit the right up to date values).
could you explain how that would work? if you have already rendered an entity and you change its values and render it again (in same or a different view mode), rdf_entity_prepare_view() will be run again, and the html_data_attributes will be re-applied appropriately.
Comment #59
yched CreditAttribution: yched commentedHmm, is it not in D8 ? It's not a "configurable field" of course, but D8's entity API unifies how "base fields" (former "properties") & "configrable fields" (former "Field API fields") are handled and manipulated within an entity.
Anyway, that's food for thought for later at best, but it might make sense that in the end "fields" (bese, configurable or computed) are the things that can have rdfa attributes.
That's the typical case, of course, and in practice it's probably going to work in 99% code flows, so no biggie. But that's still less than ideal, this is exactly why we try to avoid entities to have a "state" - so that you don't hit weird edge cases (for example when writing tests).
However, *if* formatters are the only things that are ever going to use this html_data_attributes property, then it's not really a problem in practice. As you point, rdf_entity_prepare_view() will always re-compute html_data_attributes before formatters run, so it's not a big deal if field values changed meanwhile.
I'd still find it cleaner if we had a way to remove html_data_attributes after the rendering has happened, so that we don't clutter the entity with possibly stale values, but that's not a blocker.
Comment #60
yched CreditAttribution: yched commentedCode-wise I'm fine with the patch. I don't really follow the test changes that were made (notably in the last patch ?) - are they all strictly related to the code changes made here ?
Code style / nitpicks review:
"An associative array...", to be consistent with the line above ?
Nitpick. Pphdoc for functions/methods should be 3rd person, but inline code comments are usually imperative.
-> e.g. "Pass the HTML data attributes"
(same in ImageFormatter)
One empty line too many
Missing an empty line after the last method in the class
Same as above - s/Sets/Set
Empty line after last method
two empty lines
Missing empty line after the class statement, and after the last method
s/Iterates/Iterate
(well, same in the rest of the patch ;-))
Still don't get this - not that I would expect to ;-), but maybe add a word in the issue summary about how this is related / why this is needed ?
More generally, it would help the patch to be committed if the issue summary was updated with:
- an overview of the code changes in the patch (new code flow, hooks involved, stuff added/removed)
- a section about the impact in terms of "API changes" (like "formatters are now in charge of outputting RDFa data, not RDFa gets printed if they fail to do so")
Comment #60.0
yched CreditAttribution: yched commentedUpdated issue summary.
Comment #61
yched CreditAttribution: yched commented@scor: I see you updated the OP, thanks!
Does it mean the patch should be changing / simplifying things in theme_field() / field.html.twig ?
This is about theme_file_link(), not field_theme(), right ? :-)
+ adding a couple tags, not sure whether there are tags that make sense on the RDF side.
We'll need to get a core committer's approval for the API changes
Comment #61.0
yched CreditAttribution: yched commentedAPI changes
Comment #62
scor CreditAttribution: scor commentedthanks again @yched for the thorough reviews! I've fixed all your code styles remarks from #60 (plus some minor docs improvements). See below for my answers to your other questions from the last 3 comments.
It's still unclear how to best achieve that, so I've created a followup issue: #2079745: Clean up html_data_attributes after entity rendering has happened.
That test change is because I reused an existing datatype callback for the initial patch, but it didn't work so well for handling field items like you had suggested to pass to the callback, instead of a string like I originally used in #56.
As you noted, I've updated the issue summary, see the Proposed resolution and API changes sections.
No, at least not without further consideration, which should take place in a follow up issue. theme_field() / field.html.twig only include placeholders in case modules (like rdf in D7) want to inject attributes in specific location in the field wrapper output. While it's true that rdf.module will no longer need to do that for fields with this patch, other module might. All templates (block, node, comment, field) were normalized in D7 to have the $attributes, $title_attributes and $content_attributes there tpl for consistency, so if we were to remove them from theme_field(), we should also remove them from the other tpls. However other modules are actually using these for other purposes, for example title_attributes is used by overlay and search (D8). I'm sure D7 contrib has found use for these too, so this would be another API change. There is no logic in theme_field() regarding these placeholders, it's pretty dumb. I'm open to be proven wrong here, but we should probably discuss this in another issue if someone thinks it's worth removing any or all of these generic template attribute placeholders.
correct, it's theme_file_link(), I've updated the summary ;)
Comment #64
yched CreditAttribution: yched commentedThanks for the clarifications.
I've been wondering one thing: if the field template can still print attributes as in D7, would it be possible to still output the attributes in there if the formatter hasn't taken care of it ?
Then what the patch does is add the *possibility* (not the obligation) for formatters to output the attributes if they feel they'll do it better than the default output, but there is still a default output if the formatter doesn't want to bother. This way we don't put an additional burden on all formatters (which, even if in theory other modules might add attributes, looks like "all formatters need to account for the optional rdf.module"), and the patch becomes a strict API addition, not an API change.
Could work like:
- the formatter should unset $item->html_data_attributes if it has taken care of displaying them
- field rendering displays the html_data_attributes it finds in the $items, in the D7 way
What do you think ?
Comment #65
scor CreditAttribution: scor commented(re-adding tags that the bot removed)
re #64: I'm not convinced we should do this. For one it would complicate the API, and provide two ways for adding RDFa, which means developers might be potentially confused on which one to choose. Secondly, we decided to move away from using the field template because it was not convenient for most fields (see summary and #4). If the formatter hasn't output any RDFa markup, then it's best to not let the field wrapper output some "default" RDFa, because chances are it's going to be wrong. There is no silver bullet as we've learnt in D7, and it's best not to add any markup at all instead of doing guess work and asserting wrong statements in RDFa in the field template. The only field which would make sense to have its RDFa attributes in the field template is the text field. IMO the only case where RDFa attributes would make sense to be added to the field template is if the formatter asked to do so, see my hybrid approach in the conclusion of #4. The advantage of keeping everything in the field formatter is that we don't need to rely on the field template (which could be altered / overridden by developers). Either way, do you agree this should be discussed and possibly developed in a follow up issue?
Could that workflow even be possible? can the field template execute logic after the field formatter code has been executed?
Comment #66
scor CreditAttribution: scor commentedpatch #62 failed to due to #1497374: Switch from Field-based storage to Entity-based storage. This new patch makes the necessary adjustments to the field entity creation code in the tests.
Comment #67
yched CreditAttribution: yched commentedre #65: I'm not sure we understood each other :-)
I'm not talking about adding another way to add another set of rdf info. There would still be one single set of data, $item->html_data_attributes, added in rdf_entity_prepare_view(). What I'm proposing is only about how this set of data gets into the HTML.
- current patch does: the formatter needs to catch it - if it doesn't the rdf data is lost even though it's supposed to be relevant since rdf.module placed stuff in there and expects it to be displayed. This adds burden on every single implementation of a formatter (contrib, or custom one-offs in a custom module for a specific site) and requires them to bother with stuff (rdf) they didn't need to know about so far.
- proposal is: formatter receives $item->html_data_attributes as in the current patch, and can do custom output if it needs - if so, it unsets $item->html_data_attributes before returning. If it doesn't, it means it has no special formatting needs for the rdf data, and is ok with the "default output" (the one we have in D7 at the field.tpl level)
rdf_preprocess_field() then catches the $item->html_data_attributes that are left (it does not re-compute a new set of attributes), and formats them as in current D7.
--> Image / File can still have their specific output, the other 95% formatters don't have to bother about html_data_attributes, the contract of "being a formatter" stays simple.
Comment #68
Anonymous (not verified) CreditAttribution: Anonymous commentedThis sounds reasonable to me. Even though it depends on some implicit interactions between the two parts of the system, I think it is better than other options.
So +1 to #67.
Comment #68.0
Anonymous (not verified) CreditAttribution: Anonymous commentedcorrect name of them function: theme_file_link()
Comment #69
scor CreditAttribution: scor commentedThanks for clarifying yched, I now understand your idea better and I'm starting to like it! As a bonus, it allows to implement the hybrid approach I explained in the conclusion of #4, so that's great. I've implemented it in the attached patch. Implementation is simpler than I thought, let me know if that's what you had in mind.
Comment #70
yched CreditAttribution: yched commentedAwesome ! And yay on reverting the changes in text formatters :-)
Nitpick:
It's a known bug in HEAD, that has its own issue already, and this patch doesn't really change things (doesn't introduce it or make it worse), so we can ditch the @todo IMO. One of the patches will need to be rerolled when the other gets in, but we usually don't put @todos for that.
Other than that, RTBC :-)
Comment #71
yched CreditAttribution: yched commentedThe OP will need to be updated, there are no API changes per se now :-)
(well, not sure if the mapping_type: 'rel' thing constitutes an API break or if its something that is just not needed anymore)
Comment #72
Wim LeersI've been following this issue since the beginning because it so strongly relates to in-place editing. I manually tested this patch to ensure it did not break in-place editing (because Drupal does not yet have JS test coverage), precisely because it touches the same areas. Everything continues to work just fine.
The updated code looks a lot cleaner, very nice!
I'm slightly worried that without the follow-up #2072791: Output RDFa markup for type + title in taxonomy term reference and entity reference formatters being addressed (which is generally no longer accepted in this stage), this issue leaves core in a regressed state. But I trust scor enough to follow through on that, so I think it's a low risk.
So: yay!
But: I've got a bunch of stupid nitpicks — sorry :( If all of it was just typos I could fix, I'd have rerolled the patch, but I don't feel comfortable writing test class docs and there is one question that I can't answer.
Test class docs missing.
Double spaces before the double arrows.
And that dash should probably be an em-dash :)
Missing newline.
Should be
array
as per the code below, notmixed
.If in general it could be
mixed
, then why isn't there an interface for it so that you can just inherit the docs here?80 col w wrapping is slightly off.
"Merge … with", not "Merge … in"?
Comment #72.0
Wim Leersexplain hybrid approach
Comment #73
scor CreditAttribution: scor commentedThanks for all your help & guidance on this issue yched, I think that with this hybrid approach, we got the best of both approaches!! (field tpl and field formatters)
Thanks Wim for checking that this patch doesn't break in-place editing ;) I don't think anyone had checked that.
This patch fixes the code style issues reported by Wim and yched.
Comment #74
yched CreditAttribution: yched commentedYay. Back to RTBC then.
Comment #75
Wim LeersRTBC+1
The patch in #73 did remove
// @todo https://drupal.org/node/2034003
though — is that intentional?Comment #76
scor CreditAttribution: scor commentedyes, see #70.
Comment #77
Wim LeersAlright :) Definitely RTBC then.
Comment #77.0
Wim LeersAPI changes (most of them are API additions)
Comment #77.1
scor CreditAttribution: scor commentedrefining
Comment #78
effulgentsia CreditAttribution: effulgentsia commentedThis looks great to me as well. Another RTBC+1. I was about to point out that the issue summary says:
And it doesn't look like this patch deals with that at all, but then I see that #72 links to the issue for addressing that.
Minor question: is
$item->html_data_attributes
the best name for this injected property? RDFa attributes don't generally start withdata-
: for example, none of the ones in the issue summary do.Presumably, the reason we're calling it html_data_attributes, rather than rdf_attributes, is that we want other modules (e.g., edit.module) to be able to add attributes here as well. In which case, this should be a
+=
(or some other merge operation) rather than a=
.Both discussing an improved name and the merging can be follow ups though, so leaving at RTBC.
Comment #79
scor CreditAttribution: scor commentedThanks for chiming in, Alex! Regarding the naming: we're using the concept of "HTML data" as a broad concept, which not only includes the custom application-specific data-* attributes (used by edit), but also all the syntaxes that allow to embed structured data in HTML for third party consumption such as RDFa, microdata, microformats and microformats-2, as discussed in the HTML data guide W3C Note.
html_data_attributes
can be used for all HTML Data attributes, not only for the HTML data attributes. Like you said, if someone comes up with a better name, we can probably handle that in a follow up.Comment #80
catchThis looks good overall. I had a couple of minor nitpicks. Also while the benchmarks are encouraging, I'd be more comfortable if it was profiling. On the other hand the shift from hook_preprocess() to hook_entity_prepare_view() does look like it should improve things. And agreed the code looks a lot nicer now.
Have they actually been rendered to a string already when we get here? Looks like they're prepared for rendering in this same hunk, so being removed to avoid duplication?
Why does this have to do extra set up compared to the GenericFileFormatter - could do with a comment. The formatter below also does the extra setup, so is GenericFileFormatter missing something?
When does this code run? It says it only runs when the formatter didn't inject HTML data attributes, is the comment reversed?
Comment #81
yched CreditAttribution: yched commentedre @catch #80.3:
The patch implements the following sequence:
1) rdf_entity_prepare_view() computes rdf attributes for each entity / field / delta, and puts them in a place where the formatters can access them : $entity->$field_name[$delta]->html_data_attributes
2) formatters then run, and *can* make use of html_data_attributes to place the attributes in their HTML in a custom way if they need to - they then need to unset $item->html_data_attributes to prevent setp 3) from happening.
3) rdf_preprocess_field() grabs the html_data_attributes that are still present and makes sure they end up in the HTML in the "default / fits most cases" output (i.e. the D7 output, wrapping the HTML generated by the formatter)
This is what this comment in rdf_preprocess_field() is saying - "inject" as in "took care of placing the content of $item->html_data_attributes into the HTML".
Comment #82
scor CreditAttribution: scor commentedYou're right, the comment was misleading. I've updated the comments of the 3 formatters which inject the HTML data attributes to the following:
good catch, fixed GenericFileFormatter to also initialize the array.
What yched said. I've expanded on the comment above, let me know it's more clear.
Comment #83
yched CreditAttribution: yched commentedThen the second line should do =+ instead of =, or the 1st line is useless ?
Minor, didn't catch it before, but let's fix it since it seems this will need another roll: core code comments try to avoid elisions (don't -> do not, they've -> they have...)
Also, I'd argue that "added to the formatter" is a bit sloppy :-). Maybe "included in the formatter output" ?
Also, I'm actually not sure html_data_attributes / "HTML data attributes" is the right name for the property.
First, as you pointed above, it's open to mis-interpetation: it means "html-data attributes", not "html data-attributes" - i.e it's not HTML (markup).
Then, those are rdfa attributes, nothing else ? We can't really pretend there can be other stuff in there, since:
- it's rdf_entity_prepare_view() that sets the property (overwriting anything another module might have placed)
- it's rdf_preprocess_field() that takes care of transferring it into the markup
So it does look like it's really the realm of rdf.module ?
Comment #84
effulgentsia CreditAttribution: effulgentsia commentedNo. We should fix those issues you point out instead:
- Move the initializing of $item->html_data_attributes into EntityRenderController::buildContent() prior to invoking hook_entity_prepare_view().
- Move the transferring to the markup from rdf_preprocess_field() to template_preprocess_field().
- Make rdf_entity_prepare_view() add to $item->html_data_attributes, not overwrite it.
- Add a test that verifies that a test module can also add to $item->html_data_attributes within hook_entity_prepare_view(), and that it makes its way into the markup.
Any thoughts on a better name than "html_data_attributes"? Some ideas:
- "attributes": However, this overlaps with some field types (e.g., Link) that define this as a (editable) property of the field. But, maybe that's ok, since we can just merge the two anyway? But, maybe the Link field formatter unsetting an intrinsic property of its field would be bad?
- "item_attributes"
- "injected_attributes"
- any others?
Comment #85
yched CreditAttribution: yched commented#84 sounds like a good plan.
Yes, I think it would :-)
No real insight on better names on my side, I could go with item_attributes I guess.
@scor, what do you think ?
Comment #86
scor CreditAttribution: scor commentedI would be fine with item_attributes, it removes the ambiguity around HTML data attribute and is a more generic name. Note that item_attributes is already a variable in the field template. I guess it is fine to have both, and they should not collide since the new item_attributes (ex html_data_attributes) would be in the field item, which would be merged into the item_attributes in template_preprocess_field().
If people are fine with item_attributes, I'll roll a patch with @yched and @effulgentsia suggestions.
Comment #87
effulgentsia CreditAttribution: effulgentsia commentedOne other suggestion (I don't know if I like it more or less than "item_attributes", so just putting it out there to see what others think), is "_attributes". This would follow the same convention that Symfony uses for route parameters: parameters that are route/controller specific don't lead with "_", those that have special meaning to the routing system and non-route-specific subscribers lead with "_". See #2043781: Drupal::request()->attributes->get('account') may conflict with an account object loaded from the path for an example issue where this was discussed with respect to _account and #2052389: All elements added to the Request attributes should have a _ prepended unless they come from the path where it was extended to all non-route-specific attributes. So if we want to adopt this pattern, field item properties that are defined by the field type should not lead with "_", but those that are added by the field management system and its non-field-type-specific hooks should lead with "_".
My hesitance with this, however, is that I still have old habits of thinking that a leading "_" means "private". But we have OOP now, where "private" is a language feature, not an "_" convention any more, so perhaps it's appropriate to redefine the meaning of "_" in this way, as Symfony has already done.
Comment #88
yched CreditAttribution: yched commentedWIdgets already place a '_weight' property for reorderable multiple fields (although that one is just in submitted form values and doesn't actually reach the $item objects)
So why not '_attributes', yes. I'll defer to @scor on that one.
(@effulgentsia: only tangentially related, but while you're here :-p - you might be interested in #2083835: theme('field') displays no value on empty fields even if the formatter returned an output)
Comment #89
yched CreditAttribution: yched commentedAw. Now that I mention it...
#2083835: theme('field') displays no value on empty fields even if the formatter returned an output is indeed not strictly related, but it makes me realize that the approach taken here means that image field's "default image" will not get RDF attributes, since it's added by the image formatter and is not present in the $entity yet when rdf_entity_prepare_view() runs.
Not sure if that's a big issue - because other than that, populating rdf data in rdf_entity_prepare_view() makes a damn lot of sense... :-(
Comment #90
scor CreditAttribution: scor commentedI like _attributes. In this patch:
- rename html_data_attributes to _attributes
- initialize _attributes in EntityRenderController
- rdf_preprocess_field() has been removed and its code has been placed in template_preprocess_field()
I left a todo in template_preprocess_field() for us to resolve here: should we use $variables['items'] instead of $element['#items']? I'd be curious to know the difference between two.
I still have to write a test for hook_entity_prepare_view() and _attributes, but wanted to post an update to show you how the new code flow looks. I think it's getting better and better. thanks for your continued reviews!
Comment #91
yched CreditAttribution: yched commented$variables['items'] is the thing fed to the template, it contains the render arrays returned by the formatters.
So yes, $element['#items'] is the one you want to use to grab $item->_attributes properties.
A bit confusing indeed, but this is probably not the right issue to change variable names in templates :-).
Problem in latest patch is that those added lines in template_preprocess_field() have the effect of overwriting the $variables['item_attributes'][$delta] that are set in the lines just above. Not sure if those are obsolete now and can be removed, but right now it looks at least weird ?
Comment #93
effulgentsia CreditAttribution: effulgentsia commentedNot sure when they were ever not obsolete, but yeah, they can be replaced as so. template_preprocess_field() is always the first theme('field')-specific preprocess function to run, so $variables['item_attributes'] can't already be set to anything prior to it running.
Comment #95
scor CreditAttribution: scor commentedThe NodeTypeInitialLanguageTest fails because of this error:
which comes when $name is equal to 'language' in:
because language is set to be displayed in the node output by testLanguageFieldVisibility() in NodeTypeInitialLanguageTest.php.
The FieldUpgradePathTest fails because of this error:
This happens when we do this in EntityRenderController::buildContent():
$name ends up being a deleted field at some point in FieldUpgradePathTest and $entity->get($name) fails.
It seems that both fails above could be solved if we could somehow ensure that we only initialize these attributes on actual fields. Is there a way to run some kind of $entity->getFields()? There is getProperties() but that returns everything.
The reason why these fails didn't show up before is because we were not initializing _attributes, and only setting it if an RDF mapping was defined for the field.
Comment #96
effulgentsia CreditAttribution: effulgentsia commentedgetProperties() would be correct: it shouldn't return anything that would trigger a "field is unknown" exception. But for computational efficiency, here's the way I recommend.
Comment #98
effulgentsia CreditAttribution: effulgentsia commented#96: 1778122-96-formatter-attributes.patch queued for re-testing.
Comment #99
yched CreditAttribution: yched commentedOuch, nice catch !
I opened #2089273: upgrade path puts D7 deleted fields in EntityDisplay objects for FieldUpgradePathTest / test_deleted_field
Fix looks correct. #1875974: Abstract 'component type' specific code out of EntityDisplay might get us to a point where we can ask a $display object "give me all the displayed component that are 'fields'", but for now this looks like the best we can do.
Comment #100
yched CreditAttribution: yched commentedActually, why would the following not work ?
Comment #101
effulgentsia CreditAttribution: effulgentsia commentedRe #100, it would, but it would result in getProperties() being called (for the iterator), which means a get() on every field name, instantiating a bunch of objects that might not be needed if they're not displayed. That's what I tried to explain in the code comment, but maybe not well enough?
Comment #102
yched CreditAttribution: yched commentedAh, you're right, not all Field objects have been created at this time.
Maybe "Avoid needlessly instantiating Field objects by only iterating on those that are actually displayed" ? Then the comment about "not all displayed components are actual entity fields" could go above the if (getPropertyDefinition()) line ?
Comment #103
yched CreditAttribution: yched commentedAlso, I was wrong in #89, the "default image" will still receive rdf attributes, for the same reason it does now: the default image gets added as a regular FieldItem object by the formatter in prepareView() / field_attach_prepare_view(), and this runs before _attributes are initialized and populated.
It sucks that image formatters modify the entity by adding a field value - if the entity was saved after being displayed (which usually doesn't happen of course, and node preview only displays a clone of the entity being edited), the "default image" would get saved as a regular value. "Do not display then save" is sad, but nothing new here.
Patch is ready IMO, aside from the minor/optional comment nit in #102. RTBC :-)
Comment #104
yched CreditAttribution: yched commentedDoing :
might save a couple cycles ?
Comment #105
scor CreditAttribution: scor commentedI'm posting a patch with yched's approach. I compared the time it takes to process 100 fields for both approaches, and I found a tiny improvement with the new approach:
patch #96: 0.830703306198 ms/operation on 100 fields
this patch: 0.698279190063 ms/operation on 100 fields
Might not be measurable on entities with a small amount of fields, but it might be worth it anyways?
Comment #107
effulgentsia CreditAttribution: effulgentsia commented#105: 1778122-105-formatter-attributes.patch queued for re-testing.
Comment #108
effulgentsia CreditAttribution: effulgentsia commentedYep, unrelated to this issue, but #2078155: Access protected field items being removed has just fixed one occurrence of that, and #2089123: Centralize access check for formatters is open to see if we can do it better. Now that $items is an object, I'm concerned this problem might be quite a bit more widespread than it was in D7. Not sure if we want to introduce cloning for the entire view pipeline, or make a blanket rule that formatters and other view hooks should never modify.
Comment #109
scor CreditAttribution: scor commentedI added a test for the _attributes element in hook_entity_prepare_view() as requested by @effulgentsia in #84. EntityFieldTest looked like a good home for this test, but it sets up the entity_test entity type which does not have a render controller. So I used EntityViewControllerTest instead.
Comment #110
yched CreditAttribution: yched commentedThanks for the benchmarks in #105, @scor.
Not sure I get this - does it mean the difference is
0.830703306198-0.698279190063 ms ?
or (0.830703306198-0.698279190063) *100 ms ?
If the former, then the gain seems really marginal, and @effulgentsia's original code is slightly more understandable, so I'd vote for code clarity.
If the latter, then the CPU gain might be worth keeping.
Comment #111
scor CreditAttribution: scor commentedThe patch #105 is 0.13ms faster on a bulk of 100 fields. So that is 0.0013 ms faster per field on average, which is negligible for bundles with a low amount of fields. I agree #96 makes the logic more explicit and is more readable.
Comment #112
yched CreditAttribution: yched commentedRight, let's switch back to #86 then. Sorry for the noise :-)
Comment #113
scor CreditAttribution: scor commentedreverted back to #96 while keeping the new test from #109.
Comment #114
yched CreditAttribution: yched commentedThanks ! Let's do this.
Comment #115
catchThe $item->_attributes stuff is weird and disconcerting, but it's been discussed plenty on this issue and I don't have a better idea at the moment. Since it's an existing problem, this doesn't make it much worse and there's definitely a need for this.
Committed/pushed to 8.x, thanks!
Needs a change notice.
Comment #116
tim.plunkettThis added a module_enable() call in core/modules/system/lib/Drupal/system/Tests/Entity/EntityViewControllerTest.php
Comment #117
tim.plunkettComment #118
BerdirManually confirmed the HEAD fail and that the patch fixes it.
Comment #119
catchSorry. Committed/pushed to 8.x.
Comment #120
Anonymous (not verified) CreditAttribution: Anonymous commenteddurp durp durpal:
pretty sure that check on entity_type is missing a '='? i got here because #1605290: Enable entity render caching with cache tag support starts failing on on this code after this patch.
attached patch adds the '='.
Comment #121
effulgentsia CreditAttribution: effulgentsia commentedComment #122
webchickCommitted and pushed that to 8.x too. :) Thanks!
Comment #123
Fabianx CreditAttribution: Fabianx commented.
Comment #124
jesse.d CreditAttribution: jesse.d commentedDescription
Drupal 8: Field formatter passing item attributes to the theme function
Impacts:
* Module Developers
Comment #124.0
jesse.d CreditAttribution: jesse.d commentedadd note on performance
Comment #125
xjmComment #126
scor CreditAttribution: scor commented#124 looks good to me. Jesse, could you post that as a change notice please? https://drupal.org/list-changes
Comment #127
star-szrhttps://drupal.org/node/2155247 was posted in December, thanks @jesse.d!