Problem/Motivation
editablefields_formatter renders a full entity edit widget (Drupal\Core\Field\WidgetBase::form()) inline on a display/view mode, to allow inline editing without leaving the page. In EditableFieldsFieldFormatter::viewElements() the returned build comes from EditableFieldsHelper::getForm(), a plain Drupal form array with no cacheable metadata attached for the entity being edited.
Field widgets never declare cache tags for their entity. That responsibility lives on FormatterBase/FieldItemListInterface::view() for read-only display, not on WidgetBase::form(), because widgets were only ever expected to run inside an entity's own edit form, a route that isn't normally page-cached.
When the formatter is used on a display/view mode that is reachable through a page-cacheable route (the node's default view mode at /node/{node}, for example) and Dynamic Page Cache is enabled (the default), the value shown in the widget gets baked into that page's HTML at generation time. Because the widget's render array carries no #cache[tags] dependency on the entity, the resulting Dynamic Page Cache entry is never tagged with the entity's own cache tag (for example node:123).
Saving the entity through any other path (its own edit form in another tab, Drush, another user, a different module) correctly invalidates that cache tag through the normal entity API, but this specific Dynamic Page Cache entry never depended on it, so it is not evicted. The stale value keeps being served until the entry is evicted for an unrelated reason: its own max-age, a different cache tag it happens to carry, or an explicit cache clear.
Drupal core avoids exactly this class of problem for the CSRF token and the form action URL in FormBuilder::prepareForm(), by rendering them through #lazy_builder placeholders instead of inline values, specifically so that embedding a form in an otherwise cacheable page does not force the whole page to max-age: 0 or bake in stale values. The field widget itself gets none of that treatment here.
Steps to reproduce
- Enable Dynamic Page Cache (on by default in a standard install).
- Set a field's formatter to
editablefields_formatter, behaviour "Inline form", on a view mode reachable at a normal page-cacheable route (for example the entity's default view mode). - Load the entity's page as an authenticated user, so the response is stored in Dynamic Page Cache.
- Change the field's value on the same entity through a different path (the entity's own edit form, Drush, another session).
- Reload the first page. The editable field widget still shows the old value, because the cached page was never tagged with the entity's cache tag.
Proposed resolution
Apply the entity's cacheable metadata to the render array returned by the formatter, for example in EditableFieldsFieldFormatter::viewElements() or EditableFieldsHelper::getForm():
\Drupal\Core\Cache\CacheableMetadata::createFromObject($entity)->applyTo($build);so the containing page inherits the entity's cache tags (and any cache contexts the entity declares), and gets correctly invalidated on save, the same way a normal read-only field formatter already does.
Remaining tasks
Write a patch, add a test that renders the formatter's build array and asserts the entity's cache tags are present, review.
Issue fork editablefields-3623560
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #3
introfini commentedPatch posted as a merge request against 1.0.x: !36.