Support for Drupal 7 is ending on 5 January 2025—it’s time to migrate to Drupal 10! Learn about the many benefits of Drupal 10 and find migration tools in our resource center.
Problem/Motivation
Fields cannot be rendered on their own in a block without the intervention of a contributed module (like ctools).
Proposed resolution
Include a block in core which can render entity fields when an entity is passed by context.
User interface changes
No change to existing interfaces, just the addition of new blocks.
API changes
None
Data model changes
None, just the addition of a new block.
Comment | File | Size | Author |
---|---|---|---|
#69 | 2918500-field_block-69.patch | 34.57 KB | tim.plunkett |
#69 | 2918500-field_block-69-interdiff.txt | 5.74 KB | tim.plunkett |
#62 | 2918500-field_block-62.patch | 34.99 KB | tim.plunkett |
Comments
Comment #2
EclipseGc CreditAttribution: EclipseGc at Acquia commentedComment #3
EclipseGc CreditAttribution: EclipseGc at Acquia commentedLet's get an initial patch for this.
Applying #2671964: ContextHandler cannot validate constraints will fix this.
Eclipse
Comment #4
EclipseGc CreditAttribution: EclipseGc at Acquia commentedbah patch...
Comment #6
johnwebdev CreditAttribution: johnwebdev commentedIt would be a nice addition to be able to render a fixed set of items from the field, for instance if we have a multiple value field. Is this something that we can consider implementing as well?
Comment #7
amateescu CreditAttribution: amateescu for Pfizer, Inc. commented@johndevman, we already have an issue for that specific request here #1234624: Limit the number of fields to display.
Comment #8
EclipseGc CreditAttribution: EclipseGc at Acquia commented@johndevman
I intended to ultimately add that degree of nuance to this patch. Looks like I should be discussing it with others before I do. :-D
Eclipse
Comment #9
phenaproximaNit: s/id/ID
Same.
Nit: If you move the parent::__construct() call to the top of the method, you can explode $this->getDerivativeId(). Might be a little clearer. Also, can self::DERIVATIVE_SEPARATOR be used as the string on which to split?
Nit: No need for the type hint if you use $entity->get($this->fieldName).
Why does $build['field'] need to be a thing? Can't we simply have $build = $field->view($display_settings)?
I could be wrong, but isn't there a static method on \Drupal\Core\Cache that does this?
Is there no way to have the context system take care of this? Seems like, if we limit the available contexts to bundles which carry the field, this check is not necessary.
I don't understand the benefit conferred by this potentially expensive operation. Why render it out to determine access?
Isn't there a constant we can use here?
Supernit: It's a Form API callback, not Render API.
Also technically a Form API callback.
Missing @return documentation.
What if the first available bundle doesn't match the context entity's bundle? This seems like it could have weird, unintended consequences.
Missing @return documentation.
O_O
Comment #10
johnwebdev CreditAttribution: johnwebdev commentedShouldn't this part do that limitation #7 @phenaproxima?
Comment #11
phenaproxima@johndevman: I was wondering if we could implement a context constraint (or reuse one, if one already exists) like "entity has field" or something, rather than do that $entity->hasField() call. The code you pointed out will ensure that the entity has the correct bundle, but the thing I pointed out in #9.7 is checking for the presence of the field.
Comment #12
EclipseGc CreditAttribution: EclipseGc at Acquia commentedSo, I'll respond to 9 tomorrow, but since there's discussion about 9.7, I'll address that now.
The bundle constraint is nice to have and does most of what we need. Doing a hasField() check should give us the ability to gracefully fail when things like fields are pulled off a particular bundle. I'm not keen on the idea of inventing a new constraint for this purpose, certainly not in this issue. If we want to do it in a follow up, I'd be super open to seeing what direction that might take, but we've successfully used this approach for quite some time and I'd like to stick with it for the duration of this issue. We can change it in follow ups.
Eclipse
Comment #13
larowlanshould we use the Plugin::DERIVATIVE_SEPARATOR constant here. Also, should we use the third argument to explode to ensure we get only 3?
$this->applyTo($build)
? or(new CacheableMetadata())->addCacheableDependecy($this)->applyTo($build)
?Can't we set the bundle in the definition in the deriver, then use that here? The issue is that the same field can have different labels depending on the bundle so it will appear with the wrong label for the user? Yes there will be more definitions, but the context should still filter out the surplus ones.
These need a comment
Check for a __sleep method on FieldStorageDefinitionBase perhaps
same here re constant
Comment #14
larowlanI think views supports that so perhaps we can borrow from there
Comment #15
tim.plunkett#13
1) Yes!
2)
CacheableMetadata::createFromObject($this)->applyTo($build);
3) Did not address yet
4) Fixed
5) Did not address yet
6) Fixed
#14
Did not address yet
Also made some improvements to the access checking, and added the necessary changes for config schema
Working on this more, but I think it will be blocked by #2671964: ContextHandler cannot validate constraints
Also bumping to major since it hard blocks the Layout Builder work
Comment #16
tim.plunkettThis should be
block.settings.field_block:*:*:
because of the extra use of : to split the entity type ID and field nameComment #17
tim.plunkett#13.3
I've set the bundles in the definition, but left the reset() approach for now.
Opened #2931585: Consider providing a human-readable label for field storage definitions to discuss the label issue
#13.5
I can't reproduce the need for this, removing for now.
#14
Opened #2931586: Add support for specifying deltas or ranges of field values when adding a FieldBlock to discuss further.
This patch also cleans up the form portion of the code.
Comment #18
tim.plunkettComment #19
larowlanI think we need test coverage for these forms, they're not just simple FAPI forms, there are some complex ajax behaviours, and (see below) I think there may be subtle bugs
Correct me if I'm wrong, but isn't it possible that these might refer to a previous formatter? i.e. configuration is set, but then user changes the formatter, at which point the old configuration is reused.
Comment #20
larowlanFor #19
Comment #22
tim.plunkettI've rewritten ::getFormatter() to not rely on PHP's bad merging via
+=
, which is how the previous code protected against #19.2This should be more clear that it uses EITHER the input OR the defaults.
Additionally, had to add a workaround for #2671964: ContextHandler cannot validate constraints.
Fixed the schema error I introduced.
Is the previous kernel test still needed? Not sure if it makes sense to have both.
Comment #23
EclipseGc CreditAttribution: EclipseGc at Acquia commentedSo when I built the code to do this back in D7, I actually used a nonsense view_mode string like "ctools_block" or something like that since I didn't want to inadvertently trigger 3rd party code that actually cared about legitimate view mode ids. I think doing something like that here could be beneficial for the same reason, plus it gives a custom view mode for people to key off of if they want to affect what we're doing.
the previous statement might apply here too. Just calling it out.
This patch looks REALLY great. There's a ton of good stuff in here and best practices you don't find a lot of time just due to the complexity inherent in what we're doing so big ++'s all around.
Eclipse
Comment #24
tim.plunkettGood idea, we use a similar trick in Layout Builder for custom regions.
Removing the other test, discussed it further and it is redundant.
Comment #26
EclipseGc CreditAttribution: EclipseGc at Acquia commentedI'm super happy with this.
Eclipse
Comment #27
andypostSpecifically for custom render field view can accept array of settings instead of view mode, see https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Entity%21...
Internals of entity view allows view mode as string and as array, so not sure this kind of default is good enough
IMO better to have fallback to default formatter & settings
Comment #28
tim.plunkett@andypost This hook is invoked in one other place only, and is always a string.
We do delegate to the default formatter and settings. This is for third party settings only.
Comment #30
tedbowI like the idea of having blocks for fields but I wonder from UX perspective the change to the "Add Block" form will be very jarring.
without the patch:
Without the patch:
The problems I see are:
The Password field is also exposed here but if you try to place it the form doesn't submit.
I see this error in the logs:
Settings to "Needs work" for the password problem.
Comment #31
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedRe #23 / #24, is there any reason not to use
\Drupal\Core\Entity\EntityDisplayBase::CUSTOM_MODE
? It's pretty much what you're looking for, a view mode ID that should never collide with one that exists in configuration.Comment #32
tim.plunkettPassword and UUID were a problem, and were caught by the test coverage.
They have no formatters!
This removes those from ever being available
I went one step further and set up a
no_ui
flag similar to \Drupal\Core\Field\Annotation\FieldType::$no_uiThis is used by those fields that can be rendered, but should not be exposed in the Block UI.
Comment #33
EclipseGc CreditAttribution: EclipseGc at Acquia commentedWorks for me.
Eclipse
Comment #35
larowlanI think we should also skip base fields that are
> hidden by default (in their view display definition) *and*
> not view configurable
Good idea
#31 needs to be done still
I could be missing these (only reviewed interdifffs), but is the
no_ui
flag missing tests?Comment #36
Wim LeersThis is basically https://www.drupal.org/project/fieldblock in core AFAICT? If so, we should ask @marcvangend to review.
Comment #37
Wim LeersCursory review.
Nit: mismatch.
I applaud you thinking about cacheability, but the entity access check's access result should already have taken care of this.
👌
Nit: Let's be consistent with leading slashes or not.
Woah! Might be the most complex XPath expression in core :)
Comment #38
tim.plunkett#31, perfect!
#35
1) I disagree with the "hidden by default" part. If it's view configurable, it should be available.
2) This now has test coverage in FieldBlockTest.
#36, not really. That has a very different flow of exposing the blocks. This is a port of the CTools EntityField block
#37
1)
...which is why this mismatch happened :)
2)
So it does
4)
+1
5)
It may be, but it is a copy/paste from BlockUiTest so it will be the second instance :)
Due to the change hiding non-configurable fields, had to switch from `created` to a true field.
Comment #39
EclipseGc CreditAttribution: EclipseGc at Acquia commentedI don't like this. It's sort of a misnomer because we're only hiding it from the normal core block ui, not all block uis. Can we name it something a bit more nuanced? $exclude_block_ui or something? I don't know. Hard problems and all that.
At this point, that's really my only issue with this patch. I really like where this has landed.
Eclipse
Comment #40
tim.plunkettI agree. And putting it on the Annotation is a little too much of an API.
Went with 'block_ui_hidden'.
Comment #41
EclipseGc CreditAttribution: EclipseGc at Acquia commentedAwesome, totally ++. Can we get an RTBC here?
Eclipse
Comment #42
jibranPatch looks good to me I have a couple of questions:
Shouldn't we at least clear the cache in update hook for this change?
\Drupal\ctools_block\Plugin\Block\EntityField
in ctools?Comment #43
EclipseGc CreditAttribution: EclipseGc at Acquia commentedI'll let Tim cover 1, but the answer to 2 has traditionally been that the contrib module that provided the functionality would be responsible for the upgrade path (i.e. cck, etc). As a CTools maintainer, I'd expect the same to hold true here.
Eclipse
Comment #44
Wim LeersIf it's an
@internal
thing, then let's prefix with_
to discourage anybody from relying on it.Comment #45
borisson_Render API/AJAX callback, let's use the same terminology in both places.
Comment #46
EclipseGc CreditAttribution: EclipseGc at Acquia commentedAddressed 44 & 45
Eclipse
Comment #47
EclipseGc CreditAttribution: EclipseGc at Acquia commentedTalked with Tim about 42.1 and introduced an empty update hook to update this schema change.
Eclipse
Comment #48
jibranAll the feedback has been addressed so setting it to RTBC as @EclipseGc suggested in #41.
This can be a post-update hook as well.
Comment #49
jibranJust for the record upgrade path doesn't make sense because we are using update hook just to clear the cache.
Comment #50
jibranDo we not need http://cgit.drupalcode.org/ctools/commit/?h=8.x-3.x&id=73a75efd630cef31f... anymore? I think we should credit all the contributors of
\Drupal\ctools_block\Plugin\Block\EntityField
here. Pasting the names here:Sorry, found some more nits.
We should statically cache the
$build
IMO because building it twice doesn't make sense. We should also add cache metadata toblockAccess
$build
array.Can we use
isVisibleElement
here instead of children?Comment #51
phenaproximaI <3 this patch. I found a few nitpicks, but nothing serious. Although my review was relatively shallow :)
Nit: 'id' should be 'ID'.
Nit: You could simply return
$access->andIf(AccessResult::forbidden())
, then remove the else block and "flatten" the code a bit, for readability.This should probably use $this->getConfiguration() to ensure that defaults are merged in and whatnot.
\Drupal\field_ui\Form\EntityViewDisplayEditForm has a protected getFieldLabelOptions() method, which this is directly replicating. Any chance we can move that protected method into a public static method somewhere else in core and re-use it, rather than copy and paste? If not here, maybe in a follow-up?
Comment #52
Wim LeersThese changes seem out of scope? Why can't or shouldn't this live in a different issue/patch?
I think unit tests to assert correct behavior for both common cases and edge cases would be prudent.
If this patches goes in as-is, there's a significant risk for security regressions in edge cases IMHO.
Comment #53
tim.plunkett#48
Addressed
#50
1)
Statically caching this makes me nervous. I'm not sure it's necessary?
2) getVisibleChildren, but yes
#51
1) Fixed
2) Many returns vs one is a stylistic choice, but I think in this case you are right about the readability.
3) Switched to using ::build() directly
4) Opened #2933924: Move \Drupal\field_ui\Form\EntityViewDisplayEditForm::getFieldLabelOptions() to a trait
#52
1) The first 2 hunks could be a hard blocker for this issue, but the final hunk is specifically for this addition and requires the other part. I'm not sure it would be better to have this split?
2) Okay, working on that now.
Comment #54
jibranThanks, for addressing the feedback.
The problem is build method is called twice on a single page request. I think we can avoid that by just store it statically.
What if
hook_block_build_alter
adds a visible child to the element?Comment #55
EclipseGc CreditAttribution: EclipseGc at Acquia commentedre: 50
Your point about the ctools commit is well taken. I think I was developing the block initially for working within a wizard, and it is likely that this block will need it to for situation where a tempstore might come into play.
54.2
I'd say build alters are always subject to some degree of order of operations. I'd also suggest that a different build alter would be more appropriate for such a situation.
Eclipse
Comment #56
tim.plunketthook_block_build_alter is block.module, this is all internal to the block system.
hook_block_build_alter wouldn't run when blocks are placed via another mechanism, like Panels or Layout Builder.
I fully intended to write a pure unit test, but there's way too much in TypedData to mock.
Comment #57
Wim LeersTests entity access.
FYI: these labels can contain spaces :) They're meant to be human-readable test case descriptions.
Tests unfieldable entity.
Tests fieldable entity without a particular field.
Tests field access.
Tests populated vs empty build.
BUT: this one has a name that doesn't match what it's testing.
Just that one nit at the very end, other than that, it looks like this is testing all cases. I didn't review this in detail though.
Comment #58
jibranThis assumption is not correct. Please see #2559533: Support hook_block_build_alter() and hook_block_view_alter() for panels. For layout builder in
\Drupal\layout_builder\SectionComponent::toRenderArray
we are already have'#theme' => 'block',
which is defined inblock.module
so layout_builder already dependent on block hence it should callhook_block_build_alter
while rendering the blocks.Comment #59
EclipseGc CreditAttribution: EclipseGc at Acquia commentedThis addresses 57.
Eclipse
Comment #60
samuel.mortensonWhy would the output of the build affect access? What if a field used a lazy builder, would that be "not visible" at this point?
Is there a case where users would want to use this block for a specific bundle that isn't the first bundle returned by
reset
here? Would different field definitions affect the form or build at all?Edit - This was already brought up in #9.13 and #13.3, but I'm not sure if it was addressed.
Why is using the raw user input required at this point? Some AJAX weirdness?
Why not continue at this point and not add the derivative to
$this->derivatives
, if block_ui_hidden is set?I also manually tested the patch by placing the "User picture" in a theme region and verifying that it renders correctly. Nice job!
One functionality note - I think that users may expect to be able to use this block to place any entity field in any theme region, only allowing it to be displayed if the appropriate context is available. For example, you may want to display a node's author information in the sidebar region of a theme, which is not possible with this patch as only user fields are available for placement at /admin/structure/block. Field Layout and Layout Builder address this use case, but it's important to note that without those modules the functionality is basically limited to user fields.
Comment #61
EclipseGc CreditAttribution: EclipseGc at Acquia commented60.1
So I'm pretty certain the intent here is to prevent access TRUE on empty return values. The page template layer has long standing issues with that problem, so I think this block attempted to shortcut the problem for it's own returns. There are also various other bits of weirdness around field renders that this helps us bypass (sorry to hand wave that so hard). Lazy builders build against a token of some sort, so there will still be output for them, and if the lazy builder returns a null value, then the attempt to prevent page template problems will have been in vain, but only for that small subset of situations. Also, as I understand it, it seems like a lazy builder should only be involved if the block is definitely going to render and has some cache breaking properties, like shopping cart, or time stamps and the like. Clearly I could be over-generalizing here, but I'm just conveying my understanding.
So long as my understanding is correct, then the build() call here actually CYA's pretty well and allows site builder the flexibility to not have to include some sort of evaluations of their own to prevent empty divs from being included in their markup.
60.2
So yeah, basically Drupal doesn't have a good api for dealing with labels of fields at the storage level and instead delegates it to each bundle to name their fields however they like. Views runs into this problem as well and has some pretty esoteric code to pick one label. I think we'd like to see labels introduced at the storage level for the sort of consistent field handling across bundles situations, but that seems external to this patch and a "nice to have".
60.3
I'll let Tim answer this one, but I think the answer is "yes".
60.4
Because it being hidden does not mean it's not instantiate-able. If you were to not add it to derivative, it wouldn't be a block plugin that could be instantiated, it would just be code sitting around we're not using.
As to the rest of your comment, yes I pushed back against that a bit, but as of right now, it seems like an ok caveat until such time as all the compounding issues around it are solved. I'd prefer this patch not get blocked on people discussing the best way forward for block UI so status quo for the time being seems better than "OMG WHAT DID YOU DO TO BLOCK LAYOUT" :-D
Eclipse
Comment #62
tim.plunkett#60.1
Upon further testing, that check for build is an attempt to avoid #953034: [meta] Themes improperly check renderable arrays when determining visibility, which is a longstanding bug that affects all render arrays, not just this.
I don't think that this is our responsibility. Instead, I propose that we check to see if the field has value. In fact, this even matches with the renamed test methods @Wim Leers suggested (testBlockAccessEntityAllowedFieldHasValue)
This also means we can remove the static caching of the built block, which is a huge relief.
#60.2
As mentioned in #17, I opened #2931585: Consider providing a human-readable label for field storage definitions as a means for solving this.
#60.3
This is needed because of AJAX, yes. See also
\Drupal\image\Plugin\Field\FieldWidget\ImageWidget::validateRequiredFields()
\Drupal\menu_ui\MenuForm::submitOverviewForm()
\Drupal\file\Element\ManagedFile::valueCallback()
Comment #63
jibranI think we should add a todo for #953034: [meta] Themes improperly check renderable arrays when determining visibility. Other than that it is RTBC.
Comment #64
tim.plunkettAre you proposing that we stop checking for field values once that issue is in?
I'm not sure we'd want to remove that check.
Comment #65
jibranNo, we need the empty value check. I thought we'd need an empty rendered string check but that's always checked in the template(or not) so no we don't need @todo.
Comment #66
larowlanI think we should mark this as @internal until layout builder is beta.
Or move it to layout builder module.
Putting it straight into core signals its ok to use now, but we might need more iterations.
Comment #67
jibranWe have been using it via ctools for a while now. I think this can be a public api now.
Comment #68
phenaproxima+1 for this. IMHO it should be in Layout Builder and marked internal. As long as the plugin ID remains stable, it's trivial to un-internal it and move it elsewhere into core.
Comment #69
tim.plunkettI think this is overly cautious, but it's close enough to 8.5.0 that it's probably for the best.
Comment #70
EclipseGc CreditAttribution: EclipseGc at Acquia commentedI think this move is overly cautious as well, but if it eases minds, that's fine by me. RTBCing because nothing dramatic has changed since the last rtbc happened.
Eclipse
Comment #72
larowlanComment #74
larowlanComment #76
larowlanComment #78
larowlanComment #80
larowlanComment #82
larowlanComment #84
larowlanComment #86
larowlanComment #88
larowlanComment #90
larowlanComment #92
larowlanCan we get an update to https://www.drupal.org/node/2924128?
Committed as b66af73 and pushed to 8.5.x.
Thanks
Comment #93
larowlan@tim.plunkett made the change record updates at https://www.drupal.org/node/2924128/revisions/view/10721865/10778707
Comment #94
EclipseGc CreditAttribution: EclipseGc at Acquia commentedCan I just say "WOOHOO!"
Eclipse
Comment #95
gaurav.kapoor CreditAttribution: gaurav.kapoor at OpenSense Labs commentedVery nice to see this one fixed.
Comment #96
dsnopekI second that woohoo!
I think we started on the CTools implementation during DC Barcelona - it's so cool to see this in core :-)
Comment #97
tim.plunkettThanks everyone!