Skip to content

Fix generated fast path bypassing global TransformerRegistry casters - #114

Merged
dereuromark merged 1 commit into
masterfrom
fix/fast-path-transformer-registry
Jul 24, 2026
Merged

Fix generated fast path bypassing global TransformerRegistry casters#114
dereuromark merged 1 commit into
masterfrom
fix/fast-path-transformer-registry

Conversation

@dereuromark

Copy link
Copy Markdown
Contributor

AbstractDto::fromArray() guarded the generated fast path with HAS_FAST_PATH only, while __construct() and _toArrayInternal() additionally checked the TransformerRegistry. Casters are consumed in the generic setFromArray() path, so a globally registered caster was silently skipped when hydrating a generated DTO through fromArray(), even though new Dto($data) applied it.

TransformerRegistry::addCaster(PlainClass::class, fn ($v) => new PlainClass('cast:' . $v));

new GeneratedDto(['plainData' => 'hello']);                 // cast:hello
(new GeneratedDto())->fromArray(['plainData' => 'hello']);  // hello   <- wrong

While fixing this, the guards are also scoped to the direction they actually apply to:

  • input (constructor, fromArray()) bypasses the fast path only when a caster is registered
  • output (toArray()) bypasses it only when a serializer is registered

Previously any registration disabled both, so registering an output-only serializer forced generic hydration - an unnecessary slowdown, and a behavior change for DTOs with lazy fields, which would then hydrate eagerly.

TransformerRegistry::hasAnyCaster() and hasAnySerializer() are added for this; hasAny() stays as is.

The regression was untestable with the existing fixtures because they are all non-fast-path, so tests/TestDto/FastPathCasterDto.php mimics generated output for a class-typed field.

`AbstractDto::fromArray()` only checked `HAS_FAST_PATH`, unlike the
constructor and `_toArrayInternal()`, so globally registered casters were
silently skipped when hydrating a generated DTO via `fromArray()` while
`new Dto($data)` honored them.

Scope the guards to the relevant direction as well: input paths bypass the
fast path only when a caster is registered, output only when a serializer
is registered. Registering a serializer no longer disables fast-path
hydration (and vice versa), which matters for DTOs with lazy fields.

Adds `TransformerRegistry::hasAnyCaster()` and `hasAnySerializer()`;
`hasAny()` is kept.
Copilot AI review requested due to automatic review settings July 24, 2026 16:42
@dereuromark dereuromark added the bug Something isn't working label Jul 24, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a correctness issue where generated DTOs using the hydration fast path (HAS_FAST_PATH) could bypass globally registered casters when calling AbstractDto::fromArray(). It also refines the fast-path guard conditions so that fast paths are bypassed only in the relevant direction: casters disable fast-path hydration, while serializers disable fast-path serialization.

Changes:

  • Add TransformerRegistry::hasAnyCaster() and TransformerRegistry::hasAnySerializer() to distinguish input vs output transformer registrations.
  • Update Dto::__construct(), AbstractDto::fromArray(), and Dto::_toArrayInternal() to use the new direction-specific guards.
  • Add a fast-path-like test DTO plus new tests to cover caster/serializer interactions with generated fast paths.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.

Show a summary per file
File Description
tests/TestDto/FastPathCasterDto.php Adds a test DTO that mimics generated fast-path behavior for a class-typed field.
tests/Dto/DtoTest.php Adds regression tests ensuring global casters/serializers interact correctly with fast-path DTO hydration/serialization.
src/Transformer/TransformerRegistry.php Adds hasAnyCaster() / hasAnySerializer() helpers while keeping hasAny() intact.
src/Dto/Dto.php Switches fast-path guards: constructor checks casters; _toArrayInternal() checks serializers.
src/Dto/AbstractDto.php Ensures fromArray() fast path is only taken when no global casters are registered.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@dereuromark
dereuromark merged commit 7fca8b1 into master Jul 24, 2026
11 checks passed
@dereuromark
dereuromark deleted the fix/fast-path-transformer-registry branch July 24, 2026 16:47
@codecov

codecov Bot commented Jul 24, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 83.27%. Comparing base (de2306c) to head (333ca69).
⚠️ Report is 1 commits behind head on master.

Additional details and impacted files
@@             Coverage Diff              @@
##             master     #114      +/-   ##
============================================
+ Coverage     83.07%   83.27%   +0.19%     
- Complexity     1555     1558       +3     
============================================
  Files            45       45              
  Lines          3835     3839       +4     
============================================
+ Hits           3186     3197      +11     
+ Misses          649      642       -7     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants