Problem/Motivation

My custom enhancer plugin is not receiving anything in the $context variable. When I backtrace, the context seems to be lost in ContentEntityDenormalizer::prepareInput() line 62:
$field_value[$key] = $enhancer ? $enhancer->transform($individual_field_value) : $individual_field_value;

Custom enhancers' transform() method allows for a second parameter, $context, but evidently core enhancers' does not.

I see that there were a couple of similar issues fixed 6 years ago, but this one (at least) remains.

Proposed resolution

Check whether $enhancer->transform() allows a $context parameter, perhaps by using instanceof, and if so, pass it the entity type, bundle, and field name in the $context.

Command icon 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

benstallings created an issue. See original summary.

benstallings’s picture

Issue summary: View changes
benstallings’s picture

Issue summary: View changes

benstallings’s picture

Status: Active » Needs review

bbrala made their first commit to this issue’s fork.

bbrala’s picture

Context is great to have available there yeah, i do see a test failure though, which does seem to point to an issue?

bbrala’s picture

Had claude do a review run:

Review: Issue #3541582 / MR !70 — "pass context in ContentEntityDenormalizer"

Verdict: The feature is worth having, but MR !70 as written is incomplete, over-engineered, and untested. It should not be merged as-is.

What the change does

In ContentEntityDenormalizer::prepareInput(), when an enhancer transforms an incoming (denormalization) field value, it now builds a Shaper\Util\Context with entity_type / bundle / field_name and passes it to transform() — but only for multi-value fields, and only when the enhancer is an instance of ResourceFieldEnhancerBase.

Findings

1. 🔴 Incomplete — the single-cardinality branch is not touched (biggest problem)
The MR only edits the elseif (is_array($field_value)) branch (ContentEntityDenormalizer.php:101–110, cardinality ≠ 1). The getCardinality() === 1 branch at :93–100 still calls $enhancer->transform($field_value) with no context. Single-value fields (datetime, most scalars — i.e. the typical enhancer target) get no context at all. There's a real chance the reporter's own field is single-valued and this MR wouldn't even fix their case.

2. 🟠 The instanceof ResourceFieldEnhancerBase guard is unnecessary and slightly wrong
ConfigurableResourceType::getFieldEnhancer() returns a ResourceFieldEnhancerInterface. That interface extends Shaper's TransformationInterface, whose signature is already:
public function transform($data, ?Context $context = NULL);
So every enhancer accepts the optional context — the base trait (DataAdaptorTransformerTrait) even defaults it to new Context(). The instanceof check adds a dead "no context" branch, and it checks the concrete base class rather than the interface — an enhancer that implements ResourceFieldEnhancerInterface without extending ResourceFieldEnhancerBase would wrongly be denied context. The guard should simply be removed; context can always be passed.

3. 🟠 No test coverage
Only ContentEntityDenormalizer.php is changed — no test asserts that context reaches the enhancer's doTransform(). This is exactly what bbrala's Aug‑21 comment about a test failure was pointing at. A behavior change like this needs a regression test. (There are currently no denormalizer tests at all.)

4. 🟡 Inconsistent with the normalize/output path
On output, ResourceObjectNormalizer.php:56 and FieldItemNormalizer.php:64 already pass context — but they pass new Context($serialization_context) (the full context array). The MR's input path hand-builds a 3-key context instead. Custom enhancers now see a differently-shaped context depending on direction. Worth deciding on a consistent contract (the reporter's request is reasonable; just make both directions agree, or document the difference).

5. 🟡 Style — a one-line ternary became ~18 nested lines for both branches.

Recommended fix

Build the context once and pass it in both branches, no instanceof, no ResourceFieldEnhancerBase import — only use Shaper\Util\Context;:

$context = new Context([
'entity_type' => $entity_type_id,
'bundle' => $resource_type->getBundle(),
'field_name' => $internal_name,
]);
// cardinality 1:
$field_value = $enhancer ? $enhancer->transform($field_value, $context) : $field_value;
// multi-value:
$field_value[$key] = $enhancer ? $enhancer->transform($individual_field_value, $context) : $individual_field_value;
Plus a kernel/unit test with a stub enhancer asserting it receives the expected context keys on denormalization.

Note on the reporter's premise

Their diagnosis ("core enhancers' transform() doesn't accept $context") is slightly off — all enhancers accept it via the interface. The real gap is that the denormalizer never passed one. The requested outcome is still valid.

bbrala’s picture

Status: Needs review » Needs work
benstallings’s picture

Status: Needs work » Needs review
bbrala’s picture

Status: Needs review » Fixed

Thanks!

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.