Skip to content

Complete the Data package API and fix creation, validation and output defects - #645

Merged
binaryfire merged 14 commits into
0.4from
upstream-sync-data-reconciliation
Oct 5, 2026
Merged

binaryfire merged 14 commits into
0.4from
upstream-sync-data-reconciliation

Conversation

@binaryfire

@binaryfire binaryfire commented Oct 4, 2026 •

Copy link
Copy Markdown
Member

This brings Hypervel's Data package in line with the current spatie/laravel-data API wherever it fits Hypervel, and fixes a long list of creation, validation and output defects. Spatie's test suite is now part of the Data tests, merged with the existing Hypervel coverage, and every defect it exposed is fixed with a regression test. The Data guide is rewritten around common tasks.

Two small framework changes support this. Container and routing calls now convert scalar arguments the way Laravel does, so a typed route parameter accepts '123' for int $id. The container can also resolve a class's contextual attribute values without building it, which lets the Data package convert them before construction.

Restored APIs

Several Spatie APIs that Hypervel had left out are now supported:

  • RouteParameterReference, AuthenticatedUserReference and ContainerReference for validation attributes. An unresolvable container binding throws instead of resolving to null, which could otherwise turn a database rule's constraint into whereNull.
  • #[Rule] normalization. A #[Rule] attribute's rules become typed validation attributes, so #[Rule('required|string')] replaces the inferred required and string rules instead of duplicating them, and rule inferrers can see them by type. Rule strings split on | as Laravel splits them, except a string that starts with a regex rule, which stays whole because its pattern may contain |.
  • The rule_inferrers, ignore_invalid_partials, throw_when_max_transformation_depth_reached, var_dumper_caster_mode and computed-property exception options, and make:data namespace and suffix defaults. Rule inferrers are resolved for each compilation, so a scoped inferrer follows the current coroutine.
  • The custom cast signature with the object's declared property values, prepareForPipeline(), SerializeTransformer, withOptionalValues() and withoutOptionalValues(), defaultWrap(), factory() accepting a CreationContext, and the optional FormRequestNormalizer.
  • Paginated collections and paginator properties transform to the {data, links, meta} shape in toArray(), toJson() and responses. Responses build the links and meta from the original paginator, so a cursor whose ordering field the output renames or hides no longer throws.

Creation

  • Validation now receives the complete normalized input. Undeclared fields never reached the validator before, so confirmed always failed and rules such as required_if silently passed. Validated output still excludes undeclared keys.
  • Scalars follow PHP's weak typing, including collection items. '123' becomes 123 for an int property, while 'abc' fails with a TypeError. Contextual values such as #[RouteParameter('id')] int $id are converted the same way.
  • Union properties record the type the input selects, for each collection item, and validation, casting and construction all use it. A normalized child object is no longer mistaken for an accepted array, a container type casts its declared items, and a value several container types could accept is rejected rather than guessed.
  • Explicit casts on data object and collection properties receive the input before any nested object is built.
  • Constructor parameters that are not properties receive their input by name. Protected and private promoted parameters no longer receive input, so from($request) cannot set an object's internal state.
  • Model attributes exposed through an overridden getAttribute() are read, and columns that were not selected are treated as missing.
  • Named factories must return the requested class, as in Spatie.
  • FailOnUnknownFields checks the normalized source, so a custom normalizer's envelope input is no longer rejected.
  • Lifecycle methods work on anonymous data classes, collect(null, $into) returns an empty target, and a collection class's own @extends annotation gives a property its item type.

Validation

  • Rules are assembled as in Spatie: inferred presence and type rules come first, and a declared attribute replaces the inferred rule of its type. Backed enum properties infer the Enum rule.
  • Validation and construction read the same field. Validated creation reads only the mapped input name, and attributes that reference another property, such as RequiredWith('lastName') or #[Rule('required_with:lastName')], now resolve it to that property's input name, including nested, root and collection paths. Rule strings returned from rules() are passed to the validator as written.
  • Nested input that cannot be read fails validation at its own path instead of creating an empty item or throwing a TypeError. A missing or null required data object now compiles its children's rules.
  • Rule normalization never changes what a rule means. Parameters an attribute cannot hold, such as integer:strict, keep the rule as written; AcceptedIf, DeclinedIf and ExcludeIf no longer turn '1' into 'true'; and the date attributes no longer throw a TypeError for a parseable date.
  • Finished values and collection items apply the declared and class rules that target them, with their custom messages, and wildcard attributes are named like explicit ones in error messages.

Output

  • A data collection property transforms the items of any iterable a lazy closure returns, and a property without an item class no longer throws on first use.
  • Items read from a collection by key or in a loop carry its partials without consuming them, and repeated reads no longer pile up copies.
  • An included lazy property that resolves to Optional is omitted, and nested data collections in responses keep their own wrapper.
  • Numeric partial path segments no longer throw a TypeError when nested partials are checked or merged, or when they come from a request's query string. Array indexes remain supported, and a numeric segment where a data property is expected follows the usual rules for unknown properties.
  • Inertia's scroll metadata reads a paginated data collection's paginator.

Eloquent casts

Abstract data casts store the subtype's alias when one is registered and its class name otherwise, and read either. An alias was required before, so data stored without one could not be read. The stored type must be a concrete subtype of the declared class, checked before the class is created. Collection casts accept collections and other Arrayable values, and the package's internal enums are string-backed so a creation failure's trace can be JSON-encoded.

Framework changes

  • Container construction, BoundMethod, the controller and callable dispatchers, and Controller::callAction() call application code through a small invoker that leaves out strict_types, so PHP applies its normal weak scalar conversion as it does in Laravel.
  • Container::resolveContextualParameters(), now part of the container contract, resolves a class's contextual attribute values, optionally limited to named parameters, so a package can adjust them and pass them to buildWith(). Variadic parameters are left for the build to resolve, because an override is passed as a single argument.
  • #[Give] accepts a property path, like RouteParameter and CurrentUser.

Design and performance

The package keeps its fixed creation engine rather than Spatie's configurable pipeline. Validation and construction share one prepared input and one set of type decisions, metadata is cached for the worker lifetime, and per-operation state lives in objects created for that operation, so concurrent requests in a worker never share it.

Performance was measured with the Data benchmark harness, with OPcache on and JIT off. The general creation path now reads each property once. Restoring Spatie's check for invalid nested partials costs about 0.57 µs per item for a collection with a nested only(); the check only runs where a selection continues into a property. The later creation, validation and output fixes add about 1.5–1.9 µs per object on the general creation path (about 9%), 0.8–2.1 µs per item for general-path collections, about 6 µs (14%) for contextual constructor injection, about 3% to validation, and about 0.15 µs to the direct and flat factory paths. Nested creation, eager and lazy collections, large validation, transformation, responses, Eloquent casts and relation loading stay within run-to-run variation. A review of the hot paths found no redundant work to remove. The harness's expensive scenarios now use shorter samples, so a full run takes minutes rather than a quarter of an hour, and its header reports the OPcache and JIT status that is actually running.

Intentional differences

The package README lists the remaining differences from Spatie and why. The notable ones:

  • Values a union already accepts are kept rather than converted, and model null attributes stay null.
  • Contextual attribute values always win over payload input, so client input cannot replace a server-resolved value such as the current user.
  • Responses use 200 for every method; set 201 in withResponse() when something was created.
  • Malformed partial paths given in code throw instead of being silently ignored.
  • pipeline() overrides and custom data pipes are not supported. Named factories, prepareForPipeline() and factory hooks provide customization without allowing the built-in creation phases to be reordered.

Documentation

The data objects guide is reorganized around common tasks, fills the gaps against Spatie's documentation, and covers the behavior above. The porting guide lists the behavior differences to review in ported code, and the container guide documents resolveContextualParameters() and #[Give] property paths. docs/upstream-sync/sync.yaml records the checked-through revisions for Wayfinder, the Laravel docs, Inertia and Laravel Data.

Verification

The Data suite, PHPStan and formatting pass on the final head. The Container and Inertia suites, FacadeDocblocksTest and the full parallel suite also ran during development. The documentation examples were run, and its links, anchors and code fences were checked.

Review in cubic

Note

Complete Data package creation, validation, and transformation pipeline

  • Reworks data creation: constructor-only inputs, contextual container parameters, union-type selection, deferred named factories, and cast inputs are tracked in ConstructionState and resolved through the new CreationExtensions registry in DataCreator.php
  • Rewrites validation compilation in DataValidationCompiler.php: rules are normalized via new RuleNormalizer/RuleDenormalizer, field references resolve through mapped input names, configured rule inferrers can add or remove rules, and preserved values restore from validator data
  • Fixes transformation: max-depth can stop with an empty array instead of throwing, nested paginators emit links and meta under the wrap key, and invalid nested partials raise CannotPerformPartialOnDataField
  • Adds container support: Container::resolveContextualParameters, NativeInvoker weak-typed construction, and Give attribute property-path extraction
  • Adds config options in data.php for rule inferrers, normalizers, invalid partials, depth throwing, computed input, and make:data namespace/suffix defaults
  • Risk: behavioral changes — casts now receive a property-value array instead of ConstructionState (Cast implementations must update); DataTypeFactory::buildFromString, InvalidDataDeclaration::nonPublicPromotedProperty, CannotFindDataClass::forTypeable, and several CannotCastData factories were removed; scalar coercion now follows PHP weak typing so non-numeric strings raise TypeError

Macroscope summarized 036bb01.

Complete the owner-assigned Laravel Data assessment against
spatie/laravel-data main (ce296f2), restoring the upstream APIs Hypervel
had dropped and fixing defects found along the way.

Restored upstream APIs:
- RouteParameterReference, AuthenticatedUserReference and
  ContainerReference for validation attributes. An unresolvable container
  binding now throws instead of resolving to null, which upstream lets
  turn a database constraint into whereNull.
- The computed-input ignore option, non-throwing max transformation depth
  (Eloquent casts still throw), make:data suffix and namespace config
  (--target-namespace selects one class's namespace), the VarDumper
  caster mode, and invalid nested partial checks with
  ignore_invalid_partials.
- The upstream custom cast signature, with a view of the declared
  property values; prepareForPipeline(); configurable rule_inferrers,
  resolved for each compilation so scoped inferrers follow the current
  coroutine; and later-payload precedence for multiple payloads.

Fixes:
- Validation receives the complete normalized input. Confirmed always
  failed, and rules such as required_if silently passed, because
  undeclared fields never reached the validator. Class rule and inferrer
  contexts and beforeValidation hooks now see the same input; validated
  output still excludes undeclared keys.
- Custom data collection withResponse() overrides are called.
- PropertyRules::prepend() keeps argument order (upstream Arr::prepend
  bug).
- A prepareData hook receives each selected value under the spelling
  resolution reads first, so an identity hook changes nothing.
- Inferrer-removed keyword rules never resolve their parameters, and
  surviving rules resolve once.

Performance: the general creation path reads each property once, and
nested partial checks only run where a selection continues into a
property. The owner accepted the remaining measured cost of the
restored partial check (about 0.57 us per item for a collection with a
nested only()).

The README differences are reconciled against main, with the Data docs,
porting guide, sync note and benchmark scenarios updated.

Validation: tests/Data, PHPStan, harness comparisons against the base
revision, and the SDK generator suite against this branch.
Set the queue targets as the checked-through revisions for
laravel/wayfinder main, laravel/docs 13.x, inertiajs/inertia-laravel 3.x
and spatie/laravel-data main. Data examined no pull requests, so its
last reviewed PR stays unset.
The 1,000-item collection and Eloquent scenarios measured 200 operations
per sample and validate-5000-nested 20, so single samples ran for 1.5 to
18 seconds and a full default run took about 15 minutes. They now run 20
and 2 operations per sample; the shortest sample is still about 40 ms,
long enough that scheduler noise does not dominate, and a full run takes
about 2.5 minutes. The faster scenarios keep their operation counts.

The README now asks for the owner's confirmation that the machine is idle
before running a benchmark, because background load skews the results.
Port the upstream creation coverage from spatie/laravel-data main
(ce296f2): CreationTest and its shared fixtures, merged with the existing
cast, metadata, PHPDoc, creation, validation and transformation-context
tests under upstream names. Excluded features carry REMOVED comments
(EnumerableCast, UnserializeCast, deprecated collection classes,
withoutOptionalValues() and custom pipes).

Fixes, each with a regression test:
- Explicit casts on data object and collection properties receive the
  input before any of its objects are built. Under validation, the input
  beneath the cast is prepared and validated by the ordinary Fill in the
  same construction state; named factories and object construction wait
  until the cast declines, and a matching factory is no validation
  exemption. Without validation the cast receives the raw value.
- Named object factories must return the requested object, as upstream
  requires; the Hypervel-only mode that continued from another returned
  value is removed (owner-approved).
- Unions with several container types or a data object record the type
  the raw value selects, per collection item, and Fill, hook
  reconciliation, validation and casting all read it. A normalized child
  is no longer mistaken for an accepted array, typed container arms cast
  their items, and ambiguous containers are rejected. A union value's
  inferred type rule follows the type that holds it.
- Constructor-only inputs receive their raw input by name, governed only
  by declared rules (owner-approved).
- Absent properties outside the constructor keep constructor-assigned
  values, Optional input counts as absent, and ancestor-promoted
  properties ignore input.
- Models: names from an overridden getAttribute() are read; an explicit
  null stays a supplied null (owner-approved), while strict-mode missing
  attributes and unselected columns without a getter are absent.
- collect(null, $into) returns an empty non-paginator target; factory and
  configured item casts apply; untyped containers convert; float items
  widen integers.
- Anonymous-class PHPDoc names resolve in the declaring file's namespace,
  and item shorthands apply to any container after exact matches.
- Input is normalized once: SourceResolver reports unreadable input and
  the filling step decides between an error and validation.

Structure: extension resolution and reuse move from a by-reference memo
threaded through the engine into a per-operation CreationExtensions
object, cloned from an empty instance because a constructor call per
operation measurably slowed the direct path. A class docblock records
the engine's invariants. The Fill, reconciliation and construction phases
stay together because they share recorded decisions and re-enter each
other.

The README, data-objects documentation, porting guide and sync note
describe the kept union values, model nulls, constructor-only inputs,
cast input and finished-object factories.

Verified with the Data suite (988 tests), PHPStan, the SDK generator
suite (2186 tests) against this branch, and the benchmark harness
against aa08c27 on an idle machine: from -2% to +5% across the measured
scenarios, about 45 ns per root operation of it from the extensions
object.
…ey exposed

Port the next group of spatie/laravel-data tests (main at ce296f2) onto
Hypervel's Data package: the From* attribute tests (through Hypervel's
contextual attributes), CreationFactoryTest, DataTest, InjectPropertyValuesTest,
FillRouteParameterPropertiesDataPipeTest, MagicalCreationTest, MappingTest,
PipelineTest, CollectionAttributeWithAnotationsTest, the Model, Json and
FormRequest normalizer tests, CreationContextFactoryTest, WithDataTest and the
collection annotation reader dataset. Overlapping Hypervel tests are merged
under upstream names, and excluded upstream cases carry REMOVED comments.

Defects the tests exposed, each with a regression test:

- Contextual constructor values skipped conversion, so a
  RouteParameter('id') int failed for '/posts/123'. They are now converted like
  unvalidated input after the beforeCreation hooks, and resolved once in the
  class's build context through the new Container::resolveContextualParameters().
- Container construction, BoundMethod and the routing dispatchers called
  application code from strict files, so typed route parameters rejected
  numeric strings that Laravel accepts. NativeInvoker now applies PHP's native
  weak scalar conversion there; 'abc' still fails with a TypeError.
- Data scalar conversion used custom casts. It now follows PHP's weak typing
  through NativeScalar, keeping the 'true'/'false' and array conversions, so
  malformed values, including iterable items, fail with a TypeError.
- A collection class's own @extends annotation now gives a property its item
  type after DataCollectionOf and property annotations, with template bounds.
- A required data collection infers present instead of required, so an empty
  list passes as in Spatie.
- factory() accepts a CreationContext again and copies its options, never its
  hooks.
- FailOnUnknownFields checked the raw request body even when a custom
  normalizer produced the source, rejecting valid envelope input.
  SourceResolver now separates custom normalization from fixed resolution.

Upstream's optional FormRequestNormalizer is included. The default still reads
a form request like any other request, as upstream does.

The README, data-objects documentation and porting guide describe the
paginator source requirement, the missing required property failure, the
immutable CreationContext, data collection presence, scalar conversion and the
optional normalizer.

Verified with the Data suite (1136 tests), PHPStan and php-cs-fixer. Before
the final Data-only changes, the full parallel suite, FacadeDocblocksTest and
the SDK generator suite also ran against this branch.
Port spatie/laravel-data's ValidationTest (main at ce296f2) onto Hypervel's
Data package with all 105 cases, including the four upstream skips, whose
malformed expectations are corrected. Duplicates in the reference, Exists and
Unique tests move under the upstream names, and DataValidationAsserter gains
upstream's rule explosion and redirect, error-bag, messages and attributes
assertions. Database rules with query callbacks now compare by the query the
callbacks build, so the callback tests use the query builder the presence
verifier passes and assert real constraints.

Defects the tests exposed, each with a regression test:

- Lifecycle methods were called through "Class::method" strings, which the
  container reads as Class@method, so anonymous data classes failed. They are
  now array callables.
- Rule assembly now matches upstream on both the default and the
  configured-inferrer paths: inferred presence and type rules come first, and a
  declared attribute replaces the inferred rule of its type. nullable and
  sometimes are inferred independently, a declared presence rule drops only the
  inferred sometimes, and a supplied defaulted property is required.
- Backed-enum properties infer the Enum rule, a declared enum rule replaces it,
  and the accumulator compares Enum rules by state so uniform collections keep
  wildcard rules.
- Nested input Fill cannot read, including blank strings that skip
  non-implicit rules, now fails validation at its own path instead of creating
  an empty item or throwing a TypeError. An unresolved morph adds
  EnsurePropertyMorphable and cannot be constructed; root input still throws.
- A missing or null required data object compiles its children's rules. Class
  rules and configured inferrers share one context per node with its concrete
  input path, and an unobserved node receives an empty payload rather than its
  parent's.
- Validated and rules-only creation read only the mapped input name, as
  upstream does, so validation and construction read the same field.
  Unvalidated creation keeps the PHP-name fallback.
- Wildcard collection attributes are formatted like explicit ones in error
  messages; validator hooks can still replace the formatter.

The container documentation describes resolveContextualParameters(), the
data-objects page the mapped-name rule, and the README the integer rule
inferred for int properties.

Validated with the Data suite (1250 tests), composer analyse and
composer lint:fix.
…tests

Port spatie/laravel-data's remaining validation tests (main at ce296f2):
RulesTest with its full attribute dataset, RuleNormalizerTest,
RuleDenormalizerTest (commented out upstream, active here),
RequiredRuleInferrerTest and DataClassFromValidationPayloadResolverTest.
PasswordTest, ValidationAttributeTest and ValidationPathTest merge the
upstream cases under upstream names, and duplicated Hypervel tests are
consolidated.

Rules declared through #[Rule] were appended as strings, so
#[Rule('required|string')] duplicated the inferred required and string rules,
and rule inferrers could not see them by type. Upstream's RuleNormalizer and
ValidationRuleFactory are restored: the compiler converts a Rule attribute's
rules into typed validation attributes, so they replace inferred rules of the
same type. The factory mapping stays overridable through mapping().

Defects fixed so normalization never changes what a rule means, each with
regression coverage:

- Attribute factories silently dropped parameters they could not hold, such
  as integer:strict. StringValidationAttribute, Exists, Unique and Dimensions
  now reject parameters they would lose, and such rules stay as written.
- AcceptedIf, DeclinedIf and ExcludeIf turned '1' into 'true', changing what
  a string or integer dependent matches, and the date attributes threw a
  TypeError for any parseable date. They now keep parsed values as written for
  the validator to resolve; the unused parse helpers are removed.
- The Rule attribute accepts every rule form the validator accepts, including
  closures, conditional and compilable rules. Native rule objects and their
  query callbacks keep their identity.

Upstream's InvokableRule cases are removed with that deprecated contract;
ValidationRule cases replace them. Carbon 3's timezone: replaces tz:.

The data-objects page explains that Rule attributes replace inferred rules.

Validated with the Data suite (1669 tests), composer analyse and
composer lint:fix.
…fects they exposed

Port the transformation and output group of spatie/laravel-data tests (main at
ce296f2) onto Hypervel's Data package: AppendTest, EmptyTest, PartialsTest,
RequestTest, TransformationTest, WrapTest, the transformer tests including
SerializeTransformerTest, TransformationContextFactoryTest, DataContextTest,
InertiaLazyTest, FromContainerPropertyTest, PartialTest (as PartialTreeTest
cases) and the resolver tests, which run through the Hypervel classes that
replace the resolvers or through the transformed output. Overlapping Hypervel
tests are merged under upstream names, and excluded upstream cases carry
REMOVED comments.

This also completes an audit of the earlier exclusions and adapted
expectations. Defects found by the tests and the audit, each with a regression
test:

- Paginated collections and paginator properties now transform to Spatie's
  {data, links, meta} shape in toArray(), toJson() and responses. Responses
  build links and meta from the original paginator, so a cursor whose ordering
  field the output renames or hides no longer throws.
- A data collection property transforms the items of any iterable a lazy
  closure returns, and a data iterable at the maximum depth gives [].
- SerializeTransformer and withOptionalValues()/withoutOptionalValues() are
  restored; #[Give] accepts a property path.
- Contextual values are validated with their object, resolved once per
  prepared node through a names filter on Container::resolveContextualParameters(),
  and prepared again when a hook changes a node's morph class.
- A finished value or collection item applies the declared and class rules
  that target it, with their custom messages; field-only messages still follow
  mapped input names.
- defaultWrap() is restored, and nested data collections in responses keep
  their own wrapper as in Spatie.
- A data collection property without an item class no longer throws on first
  use.
- Items read from a collection by key or in a loop carry its partials without
  consuming them, and repeated reads no longer pile up partial copies.
- An included lazy property that resolves to Optional is omitted.
- Inertia's scroll metadata reads a paginated data collection's paginator.

Malformed partial paths given in code still throw instead of being ignored or
truncated, and TransformationContext stays final; the README records both. The
README, data-objects and container documentation and the porting guide are
updated.

Verified with the Data suite (1918 tests), PHPStan and php-cs-fixer, plus the
Container and Inertia suites and FacadeDocblocksTest earlier in the slice.
… and fix the defects they exposed

Port the remaining spatie/laravel-data tests (main at ce296f2) onto Hypervel's
Data package: DataPropertyTypeTest, DataMethodTest, DataParameterTest,
DataReturnTypeTest (in DataTypeFactoryTest and DataMethodTest),
DataAttributesCollectionTest, DataIterableAnnotationReaderTest, both Eloquent
cast tests and SerializeableTest, with upstream's migration and model
fixtures. Overlapping Hypervel tests are merged under upstream names, and
excluded upstream cases carry REMOVED comments. The structure cache, Livewire
and TypeScript transformer tests have REMOVED notes in DataServiceProviderTest,
because Hypervel has none of those integrations.

Defects found by the tests, each with a regression test:

- Input no longer reaches protected or private promoted constructor
  parameters, so from($request) cannot set an object's own state. A required
  one fails with CannotCreateData, and its class is not instantiated directly
  from resolved values.
- Another collection's annotation, such as @Property DataCollection<Item> on
  an array property, gives the property its items when nothing closer matches
  and no other collection annotation gives a different item type.
- The package's internal enums are string-backed, as in Spatie #896, so a
  creation failure's trace with arguments can be JSON-encoded.
- Abstract Eloquent data casts store the subtype's alias when one is
  registered and its class name otherwise, and read either, as Spatie does. An
  alias was required, so data stored without one could not be read. The stored
  type must be a subtype of the declared class, checked before its metadata is
  built, and then a concrete class, checked with that metadata, before from()
  runs.
- Data collection casts accept collections and other Arrayable values, as
  Spatie does. Collections keep their data objects, since toArray() would give
  their output form.

The unused DataTypeFactory::buildFromString() is removed. Two upstream
annotation fakes documented a parameter they did not declare, so it is added
and formatting keeps the tag; two clock-dependent date cases are made
deterministic. The README and data-objects documentation are updated.

Verified with the Data suite (2103 tests), PHPStan and php-cs-fixer, plus the
Inertia suite and AfterEachTestSubscriberTest earlier in the slice.
The header read opcache.enable_cli and opcache.jit from the ini settings,
which can differ from what is running: OPcache can be loaded but inactive,
and the JIT only runs when its mode and buffer allow it. It now reads
opcache_get_status(), reporting 0 and disabled when the extension is
missing or inactive.

Checked with OPcache off, OPcache on with JIT disabled, and OPcache on
with tracing JIT.
Reorganize the guide around common tasks, fill applicable coverage gaps against spatie/laravel-data docs at ce296f22861dc1237ce468754cc7f46d3ac34ad5, and use Laravel-style prose and examples. Preserve Hypervel-specific APIs and update README section links.

Resolve validation attribute field references through input-name metadata so conditional rules read the same mapped input as validation. Cover nested and root references, collection paths, custom inferrers and hooks, while leaving literal rule strings unchanged.

Validation: 2,136 Data tests and 5,954 assertions pass; composer analyse and composer lint:fix pass. Documentation examples and link, anchor, fence and JSON checks pass.
@coderabbitai

coderabbitai Bot commented Oct 4, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration
  • Configuration used: Repository: hypervel/components/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 6a0a10d9-f3c7-455f-ae9a-35086eda1a84

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@binaryfire

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Oct 4, 2026 •

Copy link
Copy Markdown
⚠️ Action not completed

Review skipped: 275 files exceed the limit of 100.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@binaryfire

Copy link
Copy Markdown
Member Author

@cubic-dev-ai review

@macroscopeapp

macroscopeapp Bot commented Oct 4, 2026 •

Copy link
Copy Markdown

Macroscope has since reviewed this pull request. An earlier review was skipped by a cost limit; a review has now completed, so that notice no longer applies.

@cubic-dev-ai

cubic-dev-ai Bot commented Oct 4, 2026

Copy link
Copy Markdown

@cubic-dev-ai review

@binaryfire I have started the AI code review. It will take a few minutes to complete.

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Complete Data APIs and repair creation, validation, and output

🐞 Bug fix ✨ Enhancement 🧪 Tests 📝 Documentation ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Restore supported Spatie Data APIs while retaining Hypervel’s fixed creation engine.
• Make validation and construction share normalized input and type decisions, fixing nested and
 mapped fields.
• Correct pagination, casts, and scalar dispatch; expand regression coverage and task-focused
 documentation.
Diagram

graph TD
  A["Input source"] --> B["Normalize input"] --> C["Construction state"] --> D["Compile validation"] --> E["Construct data"] --> F["Transform output"]
  G["Container context"] --> C
  C --> E
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Adopt Spatie’s configurable pipeline
  • ➕ Closer implementation parity
  • ➕ Supports custom data pipes directly
  • ➖ Replaces Hypervel’s fixed engine
  • ➖ Adds extension and lifecycle complexity to concurrent worker operations
2. Limit the change to isolated defect fixes
  • ➕ Smaller immediate review surface
  • ➕ Lower short-term migration risk
  • ➖ Leaves API gaps and inconsistent creation behavior
  • ➖ Makes upstream regression coverage harder to reuse

Recommendation: Keep the fixed engine and shared per-operation preparation: it addresses the underlying validation/construction divergence without adopting an incompatible pipeline. Review the framework-wide weak-scalar invocation and the creation-state reconciliation as explicit compatibility boundaries.

Files changed (266) +34588 / -3019

Enhancement (39) +890 / -56
Give.phpSupport property paths in Give +11/-1

Support property paths in Give

• Allows contextual Give attributes to select a property of their resolved value.

src/container/src/Attributes/Give.php

Container.phpExpose contextual parameters without constructing classes +51/-1

Expose contextual parameters without constructing classes

• Resolves selected contextual constructor attributes for callers using buildWith; construction also uses weak scalar invocation.

src/container/src/Container.php

Dimensions.phpAccept fluent dimensions rules +11/-1

Accept fluent dimensions rules

• Supports passing an existing dimensions rule through the attribute.

src/data/src/Attributes/Validation/Dimensions.php

Exists.phpAccept fluent Exists rules +5/-0

Accept fluent Exists rules

• Supports an existing database rule as an attribute value.

src/data/src/Attributes/Validation/Exists.php

Rule.phpExpose declared rules for normalization +10/-3

Expose declared rules for normalization

• Makes Rule attributes available to typed-rule normalization and replacement.

src/data/src/Attributes/Validation/Rule.php

Unique.phpAccept fluent Unique rules +5/-0

Accept fluent Unique rules

• Supports an existing database rule as an attribute value.

src/data/src/Attributes/Validation/Unique.php

BuiltinTypeCast.phpUpdate built-in cast arguments +2/-3

Update built-in cast arguments

• Conforms to the restored cast signature and declared-property input contract.

src/data/src/Casts/BuiltinTypeCast.php

Cast.phpRestore the custom cast signature +3/-2

Restore the custom cast signature

• Passes declared property values to casts alongside their other context.

src/data/src/Casts/Cast.php

CastableCast.phpForward declared values to castables +2/-3

Forward declared values to castables

• Adapts castable delegation to the expanded custom cast contract.

src/data/src/Casts/CastableCast.php

DateTimeInterfaceCast.phpUpdate date cast contract +2/-3

Update date cast contract

• Accepts the declared-property values supplied during casting.

src/data/src/Casts/DateTimeInterfaceCast.php

EnumCast.phpUpdate enum cast contract +2/-3

Update enum cast contract

• Accepts the declared-property values supplied during casting.

src/data/src/Casts/EnumCast.php

IterableItemCast.phpUpdate iterable item cast contract +3/-2

Update iterable item cast contract

• Propagates declared-property values through iterable item casts.

src/data/src/Casts/IterableItemCast.php

Uncastable.phpUpdate uncastable sentinel contract +2/-2

Update uncastable sentinel contract

• Keeps cast-decline behavior compatible with the revised cast interface.

src/data/src/Casts/Uncastable.php

BaseData.phpRestore creation and optional-value APIs +17/-7

Restore creation and optional-value APIs

• Exposes factory context and optional-value behavior through the shared Data concern.

src/data/src/Concerns/BaseData.php

WrappableData.phpRestore defaultWrap +3/-1

Restore defaultWrap

• Exposes default wrapping behavior for Data responses.

src/data/src/Concerns/WrappableData.php

DataMakeCommand.phpApply configurable make:data defaults +32/-2

Apply configurable make:data defaults

• Honors configured namespace and suffix and supports selecting a target namespace.

src/data/src/Console/DataMakeCommand.php

BaseData.phpDeclare restored base Data APIs +12/-3

Declare restored base Data APIs

• Aligns the base contract with factory and optional-value creation methods.

src/data/src/Contracts/BaseData.php

Data.phpExpose restored Data behavior +2/-0

Expose restored Data behavior

• Wires the concrete Data class to the expanded shared APIs.

src/data/src/Data.php

DataServiceProvider.phpRegister restored Data services +16/-3

Register restored Data services

• Connects new validation, configuration, and optional dumper behavior to the package.

src/data/src/DataServiceProvider.php

CannotResolveRouteParameterReference.phpReport unresolved route references +28/-0

Report unresolved route references

• Distinguishes missing route parameters from missing referenced properties.

src/data/src/Exceptions/CannotResolveRouteParameterReference.php

CouldNotCreateValidationRule.phpReport rule normalization failures +18/-0

Report rule normalization failures

• Provides an error for rules that cannot be converted safely.

src/data/src/Exceptions/CouldNotCreateValidationRule.php

FormRequestNormalizer.phpAdd an optional FormRequest normalizer +22/-0

Add an optional FormRequest normalizer

• Reads validated FormRequest data when explicitly configured.

src/data/src/Normalizers/FormRequestNormalizer.php

RuleInferrer.phpDefine configurable rule inferrers +18/-0

Define configurable rule inferrers

• Provides an extension contract for adjusting compiled property validation rules.

src/data/src/RuleInferrers/RuleInferrer.php

CreationContext.phpCarry optional-value creation preference +1/-0

Carry optional-value creation preference

• Adds context needed by restored factory options.

src/data/src/Support/Creation/CreationContext.php

CreationContextFactory.phpRestore factory creation options +29/-3

Restore factory creation options

• Supports optional values and configuration supplied through a CreationContext.

src/data/src/Support/Creation/CreationContextFactory.php

DataClass.phpRetain additional class metadata +2/-0

Retain additional class metadata

• Makes metadata required by reconciled creation and validation available.

src/data/src/Support/DataClass.php

DataProperty.phpRetain property metadata for creation +2/-0

Retain property metadata for creation

• Exposes information needed for consistent property preparation and validation.

src/data/src/Support/DataProperty.php

DataPropertyFactory.phpPrepare typed validation property metadata +18/-3

Prepare typed validation property metadata

• Builds property declarations used by rule normalization and creation.

src/data/src/Support/Factories/DataPropertyFactory.php

TransformationContext.phpCarry max-depth failure mode +4/-0

Carry max-depth failure mode

• Records whether reaching transformation depth throws or returns an empty result.

src/data/src/Support/Transformation/TransformationContext.php

TransformationContextFactory.phpConfigure depth behavior by output context +9/-1

Configure depth behavior by output context

• Applies configured depth behavior while keeping persistence casts strict.

src/data/src/Support/Transformation/TransformationContextFactory.php

PropertyRules.phpManage ordered typed property rules +109/-0

Manage ordered typed property rules

• Supports inferred-rule replacement by declared attribute type while preserving rule order.

src/data/src/Support/Validation/PropertyRules.php

AuthenticatedUserReference.phpResolve authenticated users in validation rules +37/-0

Resolve authenticated users in validation rules

• Adds a reference to the current authenticated user or one of its properties.

src/data/src/Support/Validation/References/AuthenticatedUserReference.php

ContainerReference.phpResolve container-backed rule values +37/-0

Resolve container-backed rule values

• Uses container bindings for validation references and fails when a binding cannot resolve.

src/data/src/Support/Validation/References/ContainerReference.php

RouteParameterReference.phpResolve route-backed rule values +50/-0

Resolve route-backed rule values

• Reads route parameters or their properties and reports missing required values.

src/data/src/Support/Validation/References/RouteParameterReference.php

RuleDenormalizer.phpCompile normalized validation attributes +14/-8

Compile normalized validation attributes

• Converts typed rule attributes and external references into validator rules.

src/data/src/Support/Validation/RuleDenormalizer.php

ValidationRuleFactory.phpConstruct typed validation attributes +281/-0

Construct typed validation attributes

• Maps supported rule names to attributes while retaining rules whose parameters cannot be represented safely.

src/data/src/Support/Validation/ValidationRuleFactory.php

SerializeTransformer.phpRestore SerializeTransformer +19/-0

Restore SerializeTransformer

• Adds serialization-based value transformation as a supported Data transformer.

src/data/src/Transformers/SerializeTransformer.php

Transformer.phpAlign transformer interface +0/-1

Align transformer interface

• Removes an obsolete interface constraint for supported transformer implementations.

src/data/src/Transformers/Transformer.php

App.phpExpose contextual resolution on App facade +1/-0

Expose contextual resolution on App facade

• Makes the container’s new contextual-parameter API available through the facade.

src/support/src/Facades/App.php

Bug fix (52) +3019 / -764
BoundMethod.phpInvoke bound methods with weak scalar conversion +5/-1

Invoke bound methods with weak scalar conversion

• Routes application method calls through the non-strict native invoker.

src/container/src/BoundMethod.php

NativeInvoker.phpAdd a non-strict application-code invoker +38/-0

Add a non-strict application-code invoker

• Provides callable and constructor entry points that retain PHP’s weak scalar conversion.

src/container/src/NativeInvoker.php

AcceptedIf.phpPreserve AcceptedIf comparison values +2/-10

Preserve AcceptedIf comparison values

• Stops changing the meaning of string comparison values during rule construction.

src/data/src/Attributes/Validation/AcceptedIf.php

After.phpAccept parseable After date values +1/-9

Accept parseable After date values

• Avoids rejecting valid date arguments because of an overly narrow parameter type.

src/data/src/Attributes/Validation/After.php

AfterOrEqual.phpAccept parseable AfterOrEqual dates +1/-9

Accept parseable AfterOrEqual dates

• Allows parseable date arguments without a type error.

src/data/src/Attributes/Validation/AfterOrEqual.php

Before.phpAccept parseable Before date values +1/-9

Accept parseable Before date values

• Allows parseable date arguments without a type error.

src/data/src/Attributes/Validation/Before.php

BeforeOrEqual.phpAccept parseable BeforeOrEqual dates +1/-9

Accept parseable BeforeOrEqual dates

• Allows parseable date arguments without a type error.

src/data/src/Attributes/Validation/BeforeOrEqual.php

DateEquals.phpAccept parseable DateEquals values +1/-9

Accept parseable DateEquals values

• Allows parseable date arguments without a type error.

src/data/src/Attributes/Validation/DateEquals.php

DeclinedIf.phpPreserve DeclinedIf comparison values +2/-10

Preserve DeclinedIf comparison values

• Keeps supplied comparison values unchanged when building the rule.

src/data/src/Attributes/Validation/DeclinedIf.php

ExcludeIf.phpPreserve ExcludeIf comparison values +2/-10

Preserve ExcludeIf comparison values

• Avoids changing string comparison values while building exclusion rules.

src/data/src/Attributes/Validation/ExcludeIf.php

StringValidationAttribute.phpAdd a string-rule fallback attribute +14/-0

Add a string-rule fallback attribute

• Retains validation strings that cannot safely become typed attributes.

src/data/src/Attributes/Validation/StringValidationAttribute.php

BaseDataCollectable.phpPropagate collection partials safely +32/-0

Propagate collection partials safely

• Carries partials onto accessed items without consuming selections or accumulating copies.

src/data/src/Concerns/BaseDataCollectable.php

ResponsableData.phpPass collection response context through +3/-1

Pass collection response context through

• Preserves response behavior and original collection information when building resources.

src/data/src/Concerns/ResponsableData.php

DataCollection.phpPreserve item partials on collection access +7/-1

Preserve item partials on collection access

• Ensures keyed and iterated items receive the collection’s partial selections.

src/data/src/DataCollection.php

AbstractDataEloquentCast.phpRead and write abstract cast aliases or class names +11/-20

Read and write abstract cast aliases or class names

• Uses an alias when registered and a class name otherwise; rejects invalid or abstract stored subtypes before construction.

src/data/src/Eloquent/AbstractDataEloquentCast.php

DataCollectionEloquentCast.phpAccept Arrayable collection cast values +10/-2

Accept Arrayable collection cast values

• Broadens collection-cast input while retaining data item handling.

src/data/src/Eloquent/DataCollectionEloquentCast.php

DataPropertyOperation.phpGive property operations string-backed values +6/-6

Give property operations string-backed values

• Makes operation information safe to include in JSON-encoded error traces.

src/data/src/Enums/DataPropertyOperation.php

CannotCastData.phpAlign cast errors with supported morph envelopes +3/-19

Align cast errors with supported morph envelopes

• Removes alias-only assumptions from abstract cast failures.

src/data/src/Exceptions/CannotCastData.php

CannotCreateData.phpReport additional creation failures +16/-4

Report additional creation failures

• Adds failure paths for invalid creation and named-factory outcomes.

src/data/src/Exceptions/CannotCreateData.php

CannotPerformPartialOnDataField.phpClarify invalid nested partial errors +1/-1

Clarify invalid nested partial errors

• Aligns the exception with partial-path validation.

src/data/src/Exceptions/CannotPerformPartialOnDataField.php

DataCollectionResource.phpBuild pagination metadata from original items +19/-9

Build pagination metadata from original items

• Adds links and metadata without asking transformed cursor rows for their ordering fields; preserves collection response hooks.

src/data/src/Http/Resources/DataCollectionResource.php

NormalizedModel.phpDistinguish missing model columns +22/-3

Distinguish missing model columns

• Reads attributes exposed by overridden accessors while treating unselected columns as absent.

src/data/src/Normalizers/Normalized/NormalizedModel.php

UnknownProperty.phpAlign missing-value handling +2/-2

Align missing-value handling

• Keeps unknown normalized properties distinct from explicitly provided values.

src/data/src/Normalizers/Normalized/UnknownProperty.php

DataIterableAnnotationReader.phpRead collection classes’ own item annotations +41/-0

Read collection classes’ own item annotations

• Recognizes an iterable class’s own @extends declaration when determining item type.

src/data/src/Support/Annotations/DataIterableAnnotationReader.php

AutoLazyReplayMode.phpMake lazy replay modes string-backed +3/-3

Make lazy replay modes string-backed

• Keeps creation trace values JSON-encodable.

src/data/src/Support/Creation/AutoLazyReplayMode.php

ConstructionState.phpRecord prepared creation decisions per operation +253/-1

Record prepared creation decisions per operation

• Stores normalized input, selected union types, mappings, contextual values, and deferred factories for shared validation and construction.

src/data/src/Support/Creation/ConstructionState.php

CreationExtensions.phpScope creation extensions to an operation +222/-0

Scope creation extensions to an operation

• Resolves and reuses casts, normalizers, and lazy attributes within one creation operation rather than across worker requests.

src/data/src/Support/Creation/CreationExtensions.php

CreationMode.phpMake creation modes string-backed +4/-4

Make creation modes string-backed

• Keeps creation-failure traces JSON-encodable.

src/data/src/Support/Creation/CreationMode.php

DataCreator.phpUnify preparation, validation, and construction +874/-324

Unify preparation, validation, and construction

• Prepares normalized input once, tracks union selections and mapped fields, supports contextual values and explicit casts, and fixes nested collections and factories.

src/data/src/Support/Creation/DataCreator.php

DataInstantiator.phpSafely map constructor inputs +40/-7

Safely map constructor inputs

• Passes non-property arguments by name and prevents payloads from setting private or protected promoted state.

src/data/src/Support/Creation/DataInstantiator.php

NativeScalar.phpConvert scalar Data values using PHP rules +45/-0

Convert scalar Data values using PHP rules

• Converts valid weakly typed scalars while preserving TypeError for incompatible values.

src/data/src/Support/Creation/NativeScalar.php

SourceResolver.phpReconcile normalized source precedence +24/-15

Reconcile normalized source precedence

• Supports later-payload precedence and evaluates unknown fields against the normalized source.

src/data/src/Support/Creation/SourceResolver.php

ValueCaster.phpUse selected types and revised casts +5/-9

Use selected types and revised casts

• Aligns value casting with prepared type decisions and the restored cast signature.

src/data/src/Support/Creation/ValueCaster.php

DataClassRepository.phpRefine Data metadata caching +6/-14

Refine Data metadata caching

• Keeps class metadata reusable without sharing mutable operation state.

src/data/src/Support/DataClassRepository.php

DataPropertyType.phpResolve accepted and ambiguous union types +84/-0

Resolve accepted and ambiguous union types

• Selects property types from input and rejects values accepted by multiple competing containers.

src/data/src/Support/DataPropertyType.php

DataClassFactory.phpImprove class and lifecycle metadata +46/-20

Improve class and lifecycle metadata

• Supports anonymous classes and reconciles metadata used by factories, validation, and construction.

src/data/src/Support/Factories/DataClassFactory.php

DataTypeFactory.phpImprove iterable and union type extraction +29/-32

Improve iterable and union type extraction

• Resolves declared item and property types more consistently.

src/data/src/Support/Factories/DataTypeFactory.php

PartialsDefinition.phpPreserve partial definitions across item reads +27/-2

Preserve partial definitions across item reads

• Avoids consuming or duplicating selections when applying them to collection items.

src/data/src/Support/Partials/PartialsDefinition.php

DataTransformer.phpCorrect nested, lazy, and paginated output +185/-64

Correct nested, lazy, and paginated output

• Transforms iterable items and pagination consistently, preserves nested wrapping, and applies depth and invalid-partial settings.

src/data/src/Support/Transformation/DataTransformer.php

PartialTree.phpSafely propagate nested partial selections +28/-2

Safely propagate nested partial selections

• Improves traversal and reuse of partial selections during transformation.

src/data/src/Support/Transformation/PartialTree.php

PhpDocTypeNameResolver.phpResolve more PHPDoc item type names +50/-12

Resolve more PHPDoc item type names

• Improves type resolution for collection annotations and namespaced declarations.

src/data/src/Support/Types/PhpDocTypeNameResolver.php

CompiledValidation.phpPreserve validated and unvalidated paths correctly +17/-8

Preserve validated and unvalidated paths correctly

• Carries compilation results needed to restore legitimate constructor inputs without bypassing applicable rules.

src/data/src/Support/Validation/CompiledValidation.php

DataValidationCompiler.phpCompile rules against prepared normalized input +618/-85

Compile rules against prepared normalized input

• Combines inferred and declared rules by type, maps field references, validates nested and finished values, and resolves scoped inferrers per compilation.

src/data/src/Support/Validation/DataValidationCompiler.php

DataValidator.phpValidate complete normalized input +18/-10

Validate complete normalized input

• Passes undeclared companion fields to validation while limiting construction to validated declared values.

src/data/src/Support/Validation/DataValidator.php

EnsurePropertyMorphable.phpValidate morphable property types +24/-0

Validate morphable property types

• Checks that a selected morph class is valid for its declared property.

src/data/src/Support/Validation/EnsurePropertyMorphable.php

RuleNormalizer.phpNormalize declared rules without changing meaning +131/-0

Normalize declared rules without changing meaning

• Turns representable Rule contents into typed attributes and leaves unsupported strings and custom rules intact.

src/data/src/Support/Validation/RuleNormalizer.php

ValidationAccumulator.phpTrack additional validation paths +23/-0

Track additional validation paths

• Records constructor input paths so restoration cannot override validated results.

src/data/src/Support/Validation/ValidationAccumulator.php

WrapExecutionType.phpMake wrap modes string-backed +4/-4

Make wrap modes string-backed

• Makes internal operation values safe for JSON traces.

src/data/src/Support/Wrapping/WrapExecutionType.php

ScrollMetadata.phpRead paginated Data collection metadata +8/-1

Read paginated Data collection metadata

• Uses the underlying paginator when producing Inertia scroll metadata.

src/inertia/src/ScrollMetadata.php

CallableDispatcher.phpConvert callable route scalars +2/-1

Convert callable route scalars

• Invokes route callables through the weak-typing entry point.

src/routing/src/CallableDispatcher.php

Controller.phpConvert controller action scalars +4/-1

Convert controller action scalars

• Uses weak scalar invocation for direct callAction dispatch.

src/routing/src/Controller.php

ControllerDispatcher.phpConvert dispatched controller scalars +4/-1

Convert dispatched controller scalars

• Calls controller actions with Laravel-compatible weak scalar semantics.

src/routing/src/ControllerDispatcher.php

Tests (165) +29321 / -1917
ContainerCallTest.phpCover weak scalar container calls +24/-0

Cover weak scalar container calls

• Checks valid numeric strings and incompatible scalar arguments.

tests/Container/ContainerCallTest.php

ContextualAttributeBindingTest.phpCover contextual resolution without construction +110/-0

Cover contextual resolution without construction

• Tests selected attributes, callbacks, and contextual binding behavior.

tests/Container/ContextualAttributeBindingTest.php

AppendTest.phpCover appended Data output +127/-0

Cover appended Data output

• Adds upstream-style append and transformation scenarios.

tests/Data/AppendTest.php

AttributeTest.phpAlign attribute expectations +1/-2

Align attribute expectations

• Adjusts existing attribute assertions to restored behavior.

tests/Data/Attributes/AttributeTest.php

FromAuthenticatedUserPropertyTest.phpCover authenticated-user property injection +44/-0

Cover authenticated-user property injection

• Tests extracting contextual user properties for Data values.

tests/Data/Attributes/FromAuthenticatedUserPropertyTest.php

FromAuthenticatedUserTest.phpCover authenticated-user injection +100/-0

Cover authenticated-user injection

• Exercises user-derived Data creation and failure cases.

tests/Data/Attributes/FromAuthenticatedUserTest.php

FromContainerPropertyTest.phpCover container property injection +67/-0

Cover container property injection

• Tests contextual property extraction from resolved bindings.

tests/Data/Attributes/FromContainerPropertyTest.php

FromContainerTest.phpCover container-sourced Data values +97/-0

Cover container-sourced Data values

• Exercises binding resolution during Data creation.

tests/Data/Attributes/FromContainerTest.php

FromRouteParameterPropertyTest.phpCover route parameter properties +68/-0

Cover route parameter properties

• Tests property extraction from contextual route parameters.

tests/Data/Attributes/FromRouteParameterPropertyTest.php

FromRouteParameterTest.phpCover route parameter injection +65/-0

Cover route parameter injection

• Checks Data values derived from route parameters.

tests/Data/Attributes/FromRouteParameterTest.php

PasswordTest.phpExercise password rule compatibility +27/-7

Exercise password rule compatibility

• Expands fluent password validation coverage.

tests/Data/Attributes/Validation/PasswordTest.php

RulesTest.phpCover declared validation attributes +134/-0

Cover declared validation attributes

• Adds cases for supported rule attributes and their compilation.

tests/Data/Attributes/Validation/RulesTest.php

ValidationAttributeTest.phpReplace obsolete validation-attribute assertions +13/-179

Replace obsolete validation-attribute assertions

• Removes expectations tied to the previous attribute conversion behavior.

tests/Data/Attributes/Validation/ValidationAttributeTest.php

BuiltinTypeCastTest.phpCover scalar casting behavior +72/-16

Cover scalar casting behavior

• Tests weak conversion and incompatible built-in values.

tests/Data/Casts/BuiltinTypeCastTest.php

DateTimeInterfaceCastTest.phpBroaden date casting regressions +176/-49

Broaden date casting regressions

• Exercises date conversion under the reconciled cast contract.

tests/Data/Casts/DateTimeInterfaceCastTest.php

EnumCastTest.phpBroaden enum casting regressions +50/-45

Broaden enum casting regressions

• Checks enum conversion and invalid input behavior.

tests/Data/Casts/EnumCastTest.php

CollectionAttributeWithAnotationsTest.phpCover annotated collection properties +65/-0

Cover annotated collection properties

• Checks item types derived from collection annotations.

tests/Data/CollectionAttributeWithAnotationsTest.php

WrappableDataTest.phpCover restored wrapping behavior +34/-4

Cover restored wrapping behavior

• Tests default and per-instance Data wrapping.

tests/Data/Concerns/WrappableDataTest.php

DataMakeCommandTest.phpCover make:data defaults +54/-4

Cover make:data defaults

• Tests namespace, suffix, and command overrides.

tests/Data/Console/DataMakeCommandTest.php

CreationFactoryTest.phpCover creation factories +255/-0

Cover creation factories

• Adds factory options, contexts, and creation behavior cases.

tests/Data/CreationFactoryTest.php

CreationTest.phpAdd broad Data creation coverage +2255/-0

Add broad Data creation coverage

• Exercises nested input, contextual values, types, casts, and creation hooks.

tests/Data/CreationTest.php

DataCollectionTest.phpCover collection creation and access +262/-20

Cover collection creation and access

• Adds collection-item, pagination, and partial-propagation regressions.

tests/Data/DataCollectionTest.php

FillRouteParameterPropertiesDataPipeTest.phpCover route-context creation behavior +156/-0

Cover route-context creation behavior

• Brings route parameter property cases into the Data suite.

tests/Data/DataPipes/FillRouteParameterPropertiesDataPipeTest.php

DataServiceProviderTest.phpCheck Data service registration +2/-2

Check Data service registration

• Adjusts registration assertions for the expanded package services.

tests/Data/DataServiceProviderTest.php

DataTest.phpCover core Data APIs +151/-0

Cover core Data APIs

• Tests restored Data-level creation and representation behavior.

tests/Data/DataTest.php

DataCollectionEloquentCastTest.phpCover collection Eloquent casts +406/-166

Cover collection Eloquent casts

• Exercises Arrayable input, storage, and cast hydration.

tests/Data/Eloquent/DataCollectionEloquentCastTest.php

DataEloquentCastTest.phpCover abstract and concrete Eloquent casts +379/-153

Cover abstract and concrete Eloquent casts

• Checks aliases, class-name fallback, subtype validation, and persistence.

tests/Data/Eloquent/DataEloquentCastTest.php

EmptyTest.phpCover empty Data creation +85/-0

Cover empty Data creation

• Tests empty object and collection behavior.

tests/Data/EmptyTest.php

AbstractData.phpProvide an abstract cast fixture +11/-0

Provide an abstract cast fixture

• Defines a declared abstract Data type for morph-cast tests.

tests/Data/Fixtures/AbstractData/AbstractData.php

AbstractDataA.phpProvide an abstract cast subtype +16/-0

Provide an abstract cast subtype

• Adds a concrete Data subtype for cast cases.

tests/Data/Fixtures/AbstractData/AbstractDataA.php

AbstractDataB.phpProvide another abstract cast subtype +16/-0

Provide another abstract cast subtype

• Adds a second concrete subtype for cast cases.

tests/Data/Fixtures/AbstractData/AbstractDataB.php

AbstractPropertyMorphableData.phpProvide a morphable property fixture +34/-0

Provide a morphable property fixture

• Defines an abstract property for selected-subtype tests.

tests/Data/Fixtures/AbstractPropertyMorphableData.php

ConfidentialDataCast.phpProvide a custom confidentiality cast +21/-0

Provide a custom confidentiality cast

• Supports custom-cast creation tests.

tests/Data/Fixtures/Casts/ConfidentialDataCast.php

MeaningOfLifeCast.phpProvide a scalar custom cast +29/-0

Provide a scalar custom cast

• Supports explicit cast precedence and signature tests.

tests/Data/Fixtures/Casts/MeaningOfLifeCast.php

StringToUpperCast.phpProvide a string transformation cast +37/-0

Provide a string transformation cast

• Supports declared-values and cast-order tests.

tests/Data/Fixtures/Casts/StringToUpperCast.php

CircData.phpProvide recursive Data fixture +19/-0

Provide recursive Data fixture

• Supports circular-reference and depth tests.

tests/Data/Fixtures/CircData.php

CustomCollection.phpProvide custom collection fixture +11/-0

Provide custom collection fixture

• Supports custom collection creation cases.

tests/Data/Fixtures/Collections/CustomCollection.php

SimpleDataCollection.phpProvide typed collection fixture +15/-0

Provide typed collection fixture

• Supports collection property and item tests.

tests/Data/Fixtures/Collections/SimpleDataCollection.php

SimpleDataCollectionWithAnotations.phpProvide annotated collection fixture +17/-0

Provide annotated collection fixture

• Supplies a collection-owned item annotation for type inference tests.

tests/Data/Fixtures/Collections/SimpleDataCollectionWithAnotations.php

ComplicatedData.phpProvide nested Data fixture +43/-0

Provide nested Data fixture

• Exercises complex creation and transformation cases.

tests/Data/Fixtures/ComplicatedData.php

BindsRouteParameters.phpProvide route binding test concern +32/-0

Provide route binding test concern

• Supports route-sourced Data creation tests.

tests/Data/Fixtures/Concerns/BindsRouteParameters.php

CustomDataCollection.phpProvide custom Data collection fixture +11/-0

Provide custom Data collection fixture

• Supports custom collectable behavior tests.

tests/Data/Fixtures/DataCollections/CustomDataCollection.php

DataValidationAsserter.phpAdd Data validation assertion helper +189/-0

Add Data validation assertion helper

• Makes upstream-derived rule and validation expectations testable.

tests/Data/Fixtures/DataValidationAsserter.php

DataWithMapper.phpProvide mapped Data fixture +21/-0

Provide mapped Data fixture

• Supports input/output mapping tests.

tests/Data/Fixtures/DataWithMapper.php

DataWithMultipleArgumentCreationMethod.phpProvide multi-argument creation fixture +26/-0

Provide multi-argument creation fixture

• Supports named creation-method argument tests.

tests/Data/Fixtures/DataWithMultipleArgumentCreationMethod.php

DataWithNulla...

@cubic-dev-ai cubic-dev-ai Bot 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.

19 issues found across 275 files

Confidence score: 2/5

  • Existing apps with a published data.php miss the new key, and DataConfig reads it without a fallback, so upgrades can fail. Add a fallback or otherwise handle configs published before this change.
  • DataCreator can drop earlier nested values when a later source provides the same parent array, which can break validation rules that depend on those values. Preserve nested keys when merging sources.
  • DataIterableAnnotationReader ignores @implements, so iterable collection properties can lose their item type and nested data conversion. Read the matching @implements tag as well as @extends.
  • ContainerReference treats a missing or misspelled property as null, which can silently make database constraints match null rows. Throw when the property cannot be resolved.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/data/src/Normalizers/Normalized/NormalizedModel.php">

<violation number="1" location="src/data/src/Normalizers/Normalized/NormalizedModel.php:73">
P2: A null returned by an overridden `getAttribute()` is treated as absent unless the raw attributes or built-in mutators declare the key. Data creation can therefore retain a property default instead of using the virtual null; use an override-aware presence check before discarding it.</violation>
</file>

<file name="tests/Data/DataCollectionTest.php">

<violation number="1" location="tests/Data/DataCollectionTest.php:309">
P2: `all()` returns the transformed item representation, not the `CollectionPartialData` object, so this assertion fails. Assert the selected array (`[['name' => 'first']]`) instead.</violation>
</file>

<file name="tests/Data/Fixtures/NestedLazyData.php">

<violation number="1" location="tests/Data/Fixtures/NestedLazyData.php:25">
P2: `fromString()` advertises a late-static return but always constructs `NestedLazyData`; calling it on a subclass therefore throws a return-type `TypeError`. Construct with `new static` to honor the declared return type.</violation>
</file>

<file name="src/data/src/Eloquent/AbstractDataEloquentCast.php">

<violation number="1" location="src/data/src/Eloquent/AbstractDataEloquentCast.php:97">
P2: An alias can shadow a class-name envelope: `createMorphEnvelope()` writes the FQCN when that class has no alias, but this lookup remaps it to the alias target, so reads can return a different subtype. Make class-name and alias resolution unambiguous or reject colliding morph-map entries.</violation>
</file>

<file name="src/data/src/Support/Validation/References/RouteParameterReference.php">

<violation number="1" location="src/data/src/Support/Validation/References/RouteParameterReference.php:44">
P2: This throws when a route parameter has a present property whose value is `null`, preventing nullable route fields from being used as rule parameters. Distinguish an absent path from a present `null` before raising this exception.</violation>
</file>

<file name="src/data/src/Support/Annotations/DataIterableAnnotationReader.php">

<violation number="1" location="src/data/src/Support/Annotations/DataIterableAnnotationReader.php:71">
P2: This ignores item types declared with `@implements`, so directly implemented iterable collection properties lose their item type and nested data conversion. Read the matching `@implements` tag as well as `@extends`.</violation>
</file>

<file name="src/inertia/src/ScrollMetadata.php">

<violation number="1" location="src/inertia/src/ScrollMetadata.php:38">
P2: A `JsonResource` wrapping either paginated data collection still takes this arm, so the collection reaches the paginator checks and metadata generation throws. Unwrap the resource first, then normalize the resulting data collection.</violation>
</file>

<file name="src/data/src/Support/Types/PhpDocTypeNameResolver.php">

<violation number="1" location="src/data/src/Support/Types/PhpDocTypeNameResolver.php:124">
P2: Namespace declarations on the same line overwrite one another here, so an anonymous class in an earlier namespace block can resolve its PHPDoc types against a later namespace. Preserve declaration order and enough source-position information to distinguish declarations on the same line.</violation>
</file>

<file name="tests/Data/Fixtures/SimpleDataCollection.php">

<violation number="1" location="tests/Data/Fixtures/SimpleDataCollection.php:16">
P2: `toJson()` discards caller-supplied JSON flags, preventing callers from requesting options such as `JSON_UNESCAPED_UNICODE`; preserve them while adding pretty-printing with `$options | JSON_PRETTY_PRINT`.</violation>
</file>

<file name="tests/Data/Attributes/Validation/RulesTest.php">

<violation number="1" location="tests/Data/Attributes/Validation/RulesTest.php:33">
P2: This freezes CarbonImmutable globally for the rest of the PHPUnit process, so later tests using `now()` observe May 16, 2020. Reset the clock in `tearDown()` after these tests.</violation>
</file>

<file name="src/data/src/Support/Validation/PropertyRules.php">

<violation number="1" location="src/data/src/Support/Validation/PropertyRules.php:65">
P2: `removeType(Required::class)` does not remove other requiring rules because this check recognizes only rule instances. Detect when the class string names a `RequiringRule` implementation as well, so class-based removal matches the method contract.</violation>
</file>

<file name="tests/Data/Casts/BuiltinTypeCastTest.php">

<violation number="1" location="tests/Data/Casts/BuiltinTypeCastTest.php:85">
P2: `NAN` is coerced to an integer by PHP's weak scalar conversion, so this case does not throw `TypeError` and the test fails. Replace it with a value PHP cannot coerce to `int`, such as an object.</violation>
</file>

<file name="src/data/src/Support/Creation/CreationContext.php">

<violation number="1" location="src/data/src/Support/Creation/CreationContext.php:42">
P2: Inserting this parameter here shifts every existing positional argument after `disableMagicalCreation`, so callers passing `ignoredMagicalMethods` positionally now get a type error. Append the new parameter after the existing constructor parameters to preserve compatibility.</violation>
</file>

<file name="src/data/src/Support/Validation/References/ContainerReference.php">

<violation number="1" location="src/data/src/Support/Validation/References/ContainerReference.php:32">
P2: A missing or misspelled property resolves to `null`, so database constraints using this reference can silently filter on null rows instead of failing. Detect an unresolved property and throw before returning it.</violation>
</file>

<file name="src/data/config/data.php">

<violation number="1" location="src/data/config/data.php:47">
P1: Existing apps with a published pre-PR `data.php` do not receive this new key because `mergeConfigFrom()` shallow-merges top-level config. `DataConfig` reads it without a fallback, so upgrading those apps now fails during provider boot; preserve defaults for missing keys or otherwise migrate published configs.</violation>
</file>

<file name="src/data/src/Support/Creation/ConstructionState.php">

<violation number="1" location="src/data/src/Support/Creation/ConstructionState.php:435">
P2: Resetting an item clears only its override, but `selectedType()` then falls back to the collection template; a missing union-valued auto-lazy default can therefore be cast using a sibling's branch. Mask inherited selections when resetting an item.</violation>
</file>

<file name="tests/Data/Eloquent/DataEloquentCastTest.php">

<violation number="1" location="tests/Data/Eloquent/DataEloquentCastTest.php:267">
P2: This assertion compares the `text` column as an exact JSON string, but transformation emits inherited `variant` before subclass `a`, so the test fails despite correct storage. Expect `variant` before `a`.</violation>
</file>

<file name="src/data/src/Support/Creation/DataCreator.php">

<violation number="1" location="src/data/src/Support/Creation/DataCreator.php:2968">
P2: This replaces a scalar ancestor with an empty array while writing a nested property, silently changing malformed input such as `profile: 'bad'` into an object-shaped payload. Skip the nested write when an ancestor is not an array so validation or construction can reject the original shape.

(Based on your team's feedback about nested payload paths.)</violation>

<violation number="2" location="src/data/src/Support/Creation/DataCreator.php:2994">
P2: This shallow merge drops earlier nested keys when a later source supplies the same parent array, so undeclared values used by validation rules, such as confirmation or conditional fields, disappear. Merge nested arrays recursively while preserving later-source precedence.</violation>
</file>

Note: This PR contains a large number of files. cubic selects up to 200 of the highest-priority eligible files for this review, so some files may not have been reviewed.

Re-trigger cubic

Comment thread src/data/config/data.php
* Rule inferrers adjust the validation rules of every data property after
* the package's fixed rule inference. Only add your own inferrers here.
*/
'rule_inferrers' => [],

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1: Existing apps with a published pre-PR data.php do not receive this new key because mergeConfigFrom() shallow-merges top-level config. DataConfig reads it without a fallback, so upgrading those apps now fails during provider boot; preserve defaults for missing keys or otherwise migrate published configs.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/data/config/data.php, line 47:

<comment>Existing apps with a published pre-PR `data.php` do not receive this new key because `mergeConfigFrom()` shallow-merges top-level config. `DataConfig` reads it without a fallback, so upgrading those apps now fails during provider boot; preserve defaults for missing keys or otherwise migrate published configs.</comment>

<file context>
@@ -31,11 +40,20 @@
+     * Rule inferrers adjust the validation rules of every data property after
+     * the package's fixed rule inference. Only add your own inferrers here.
+     */
+    'rule_inferrers' => [],
+
     /*
</file context>

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Not changing this. mergeConfigFrom() merges the package defaults into the published config at the top level, so a published data.php without rule_inferrers still gets the default for it. Only nested arrays under a key the app already defines are replaced as a whole, and rule_inferrers is a top-level key.

Comment thread src/data/src/Support/Validation/RuleNormalizer.php Outdated
}

// Without a getter, unselected columns and unknown names also read as null, but they are absent rather than supplied.
if ($value === null

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: A null returned by an overridden getAttribute() is treated as absent unless the raw attributes or built-in mutators declare the key. Data creation can therefore retain a property default instead of using the virtual null; use an override-aware presence check before discarding it.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/data/src/Normalizers/Normalized/NormalizedModel.php, line 73:

<comment>A null returned by an overridden `getAttribute()` is treated as absent unless the raw attributes or built-in mutators declare the key. Data creation can therefore retain a property default instead of using the virtual null; use an override-aware presence check before discarding it.</comment>

<file context>
@@ -54,10 +55,28 @@ protected function fetchNewProperty(string $name, DataProperty $dataProperty): m
+        }
+
+        // Without a getter, unselected columns and unknown names also read as null, but they are absent rather than supplied.
+        if ($value === null
+            && ! array_key_exists($name, $this->model->getAttributes())
+            && ! $this->model->hasAnyGetMutator($name)
</file context>

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Not changing this. A null for a name that isn't a model attribute and has no accessor looks the same as a column that wasn't selected, and unselected columns are treated as missing so the property keeps its default. Virtual attributes are supported through accessors, and those are detected even when they return null.

$this->assertSame($item, $collection[0]);
$this->assertSame($item, $collection[0]);
$this->assertSame([$item], iterator_to_array($collection));
$this->assertSame([$item], $collection->all());

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: all() returns the transformed item representation, not the CollectionPartialData object, so this assertion fails. Assert the selected array ([['name' => 'first']]) instead.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At tests/Data/DataCollectionTest.php, line 309:

<comment>`all()` returns the transformed item representation, not the `CollectionPartialData` object, so this assertion fails. Assert the selected array (`[['name' => 'first']]`) instead.</comment>

<file context>
@@ -78,22 +276,66 @@ public function testConstructorAndOffsetSetBypassAnOverriddenPublicFromMethod():
+        $this->assertSame($item, $collection[0]);
+        $this->assertSame($item, $collection[0]);
+        $this->assertSame([$item], iterator_to_array($collection));
+        $this->assertSame([$item], $collection->all());
+
+        $this->assertCount(1, $item->getPartialsDefinition()->resolve($item)['only']);
</file context>
Suggested change
$this->assertSame([$item], $collection->all());
$this->assertSame([['name' => 'first']], $collection->all());

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This test passes. all() returns the stored items, which are the data objects themselves, not their transformed arrays.

*/
public static function fromString(string $string): static
{
return new self(Lazy::create(fn (): SimpleData => SimpleData::from($string)));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: fromString() advertises a late-static return but always constructs NestedLazyData; calling it on a subclass therefore throws a return-type TypeError. Construct with new static to honor the declared return type.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At tests/Data/Fixtures/NestedLazyData.php, line 25:

<comment>`fromString()` advertises a late-static return but always constructs `NestedLazyData`; calling it on a subclass therefore throws a return-type `TypeError`. Construct with `new static` to honor the declared return type.</comment>

<file context>
@@ -0,0 +1,27 @@
+     */
+    public static function fromString(string $string): static
+    {
+        return new self(Lazy::create(fn (): SimpleData => SimpleData::from($string)));
+    }
+}
</file context>
Suggested change
return new self(Lazy::create(fn (): SimpleData => SimpleData::from($string)));
return new static(Lazy::create(fn (): SimpleData => SimpleData::from($string)));

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Not changing this fixture. Nothing extends NestedLazyData or calls fromString() through a subclass, so the static return type only ever sees NestedLazyData itself.

$node['mappings'] = [];
$node['children'] = [];
unset($node['autoLazy'], $node['paginatorSource']);
unset($node['selectedTypes'], $node['autoLazy'], $node['paginatorSource'], $node['namedFactory'], $node['contextualPrepared']);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: Resetting an item clears only its override, but selectedType() then falls back to the collection template; a missing union-valued auto-lazy default can therefore be cast using a sibling's branch. Mask inherited selections when resetting an item.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/data/src/Support/Creation/ConstructionState.php, line 435:

<comment>Resetting an item clears only its override, but `selectedType()` then falls back to the collection template; a missing union-valued auto-lazy default can therefore be cast using a sibling's branch. Mask inherited selections when resetting an item.</comment>

<file context>
@@ -329,7 +432,7 @@ public function resetNodeStructure(): void
         $node['mappings'] = [];
         $node['children'] = [];
-        unset($node['autoLazy'], $node['paginatorSource']);
+        unset($node['selectedTypes'], $node['autoLazy'], $node['paginatorSource'], $node['namedFactory'], $node['contextualPrepared']);
 
         if ($this->pathContainsItem()) {
</file context>

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Not changing this. Every supplied value records its own selection before it's cast. A missing value with an auto-lazy default returns before any cast, and a reset item records its selections again when it's filled, so no value is cast with a sibling's type.

])->id;

$this->assertDatabaseHas($modelClass::class, [
'data' => json_encode(['a' => 'foo', 'variant' => 'foo']),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: This assertion compares the text column as an exact JSON string, but transformation emits inherited variant before subclass a, so the test fails despite correct storage. Expect variant before a.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At tests/Data/Eloquent/DataEloquentCastTest.php, line 267:

<comment>This assertion compares the `text` column as an exact JSON string, but transformation emits inherited `variant` before subclass `a`, so the test fails despite correct storage. Expect `variant` before `a`.</comment>

<file context>
@@ -52,39 +70,323 @@ protected function defineEnvironment(Application $app): void
+        ])->id;
+
+        $this->assertDatabaseHas($modelClass::class, [
+            'data' => json_encode(['a' => 'foo', 'variant' => 'foo']),
+        ]);
+
</file context>
Suggested change
'data' => json_encode(['a' => 'foo', 'variant' => 'foo']),
'data' => json_encode(['variant' => 'foo', 'a' => 'foo']),

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This test passes, so the stored JSON already has the key order the assertion expects.

Comment thread src/data/src/Support/Factories/DataTypeFactory.php Outdated
$target = &$input;

foreach ($property->inputPath($this->propertyInputKey($property, $context)) as $segment) {
if (! is_array($target)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: This replaces a scalar ancestor with an empty array while writing a nested property, silently changing malformed input such as profile: 'bad' into an object-shaped payload. Skip the nested write when an ancestor is not an array so validation or construction can reject the original shape.

(Based on your team's feedback about nested payload paths.)

View Feedback

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/data/src/Support/Creation/DataCreator.php, line 2968:

<comment>This replaces a scalar ancestor with an empty array while writing a nested property, silently changing malformed input such as `profile: 'bad'` into an object-shaped payload. Skip the nested write when an ancestor is not an array so validation or construction can reject the original shape.

(Based on your team's feedback about nested payload paths.) </comment>

<file context>
@@ -2503,7 +2905,101 @@ protected function propertyInputKey(
+            $target = &$input;
+
+            foreach ($property->inputPath($this->propertyInputKey($property, $context)) as $segment) {
+                if (! is_array($target)) {
+                    $target = [];
+                }
</file context>

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Not changing this. It only happens with several payloads, a prepareData hook, and a dotted input name whose parent a later payload gives as a scalar. In that case the hook sees the value that resolution selected, which is the value the object receives.


foreach ($sources as $source) {
if (is_array($source)) {
$input = $input === [] ? $source : array_replace($input, $source);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: This shallow merge drops earlier nested keys when a later source supplies the same parent array, so undeclared values used by validation rules, such as confirmation or conditional fields, disappear. Merge nested arrays recursively while preserving later-source precedence.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/data/src/Support/Creation/DataCreator.php, line 2994:

<comment>This shallow merge drops earlier nested keys when a later source supplies the same parent array, so undeclared values used by validation rules, such as confirmation or conditional fields, disappear. Merge nested arrays recursively while preserving later-source precedence.</comment>

<file context>
@@ -2503,7 +2905,101 @@ protected function propertyInputKey(
+
+        foreach ($sources as $source) {
+            if (is_array($source)) {
+                $input = $input === [] ? $source : array_replace($input, $source);
+            }
+        }
</file context>
Suggested change
$input = $input === [] ? $source : array_replace($input, $source);
$input = $input === [] ? $source : array_replace_recursive($input, $source);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Not changing this. Payloads combine per top-level key, with later payloads taking precedence, as documented. A deep merge would change what a later payload's nested value means: it would no longer replace the earlier one.

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Oct 4, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. Custom-context dates fail to parse 🐞 Bug ≡ Correctness
Description
DataCreator::factory() copies several options from the supplied CreationContext but omits
dateFormats and dateTimezone. When a caller passes a context with a custom date format or
timezone to Data::factory($context), the new factory uses configuration defaults instead, so valid
dates can fail casting and parsed dates can receive the wrong timezone.
Code

src/data/src/Support/Creation/DataCreator.php[R106-109]

+        if ($creationContext !== null) {
+            return (new CreationContextFactory($this, $this->config, $class))
+                ->validationStrategy($creationContext->validationStrategy)
+                ->withPropertyNameMapping($creationContext->mapPropertyNames)
Evidence
The public factory method forwards the optional context, but the new copy path does not retain its
date settings. The factory then reads both settings from configuration, while date casting uses the
resulting context's format and timezone.

src/data/src/Concerns/BaseData.php[139-154]
src/data/src/Support/Creation/DataCreator.php[104-114]
src/data/src/Support/Creation/CreationContext.php[34-55]
src/data/src/Support/Creation/CreationContextFactory.php[348-371]
src/data/src/Support/Creation/ValueCaster.php[81-115]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new factory overload loses a supplied creation context's date format and timezone, changing how date properties are cast.
## Fix Focus Areas
- src/data/src/Support/Creation/DataCreator.php[104-114]
- src/data/src/Support/Creation/CreationContextFactory.php[348-371]
## Recommended Fix
Carry `dateFormats` and `dateTimezone` from the supplied context into the new factory and use them when building its creation context. Add a test for `Data::factory($context)->from()` with a non-default date format and timezone.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Hook-replaced nested input skips custom normalizers 🐞 Bug ≡ Correctness
Description
fillHookNode() reads a hook-introduced nested value with SourceResolver::resolve($payload) alone
and never applies the operation's configured normalizers from $extensions->normalizers(...). The
ordinary path in fillGeneralNode() does call SourceResolver::normalize($payload, $normalizers).
So when a beforeValidation-style hook replaces a nested data or collection item input with a
source only a custom normalizer can read, the hook path either throws
CannotCreateData::noNormalizerFound() or keeps the raw value, even though the same input succeeds
outside the hook path.
Code

src/data/src/Support/Creation/DataCreator.php[R1068-1079]

+        $source = SourceResolver::resolve($payload);
+
+        if ($source === null) {
+            // Nested input Fill cannot read stays as given, so validation reports it. Beneath a cast,
+            // the cast or a named factory may still read it.
+            if ($deferred || ($compilesRules && $state->depth() > 0)) {
+                $state->writeNodeValue($payload);
-            if ($payload instanceof $class) {
-                return $payload;
+                return null;
         }
+
+            throw CannotCreateData::noNormalizerFound($class, $payload);
Evidence
The general fill path gets the per-operation normalizers and calls normalize(). The hook-node path
calls only the fixed resolve(), which returns null for any object that is not a built-in source
type, and then throws noNormalizerFound. Custom normalizers are applied only through normalize().

src/data/src/Support/Creation/DataCreator.php[808-824]
src/data/src/Support/Creation/DataCreator.php[1051-1079]
src/data/src/Support/Creation/SourceResolver.php[25-86]
src/data/src/Support/Creation/CreationExtensions.php[128-160]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`fillHookNode()` resolves a hook-introduced payload with `SourceResolver::resolve()` and skips the configured normalizers. `fillGeneralNode()` applies them through `SourceResolver::normalize()`, so the two paths read the same input differently.
## Fix Focus Areas
- src/data/src/Support/Creation/DataCreator.php[1051-1079]
- src/data/src/Support/Creation/DataCreator.php[808-824]
## Recommended Fix
In `fillHookNode()`, get `$normalizers = $extensions->normalizers($dataClass, $state->context);` and replace `SourceResolver::resolve($payload)` with `SourceResolver::normalize($payload, $normalizers)`. This matches how `fillGeneralNode()` reads its payloads. Also add a regression test where a hook sets a nested value that only a custom normalizer can read.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can turn on the rule miner and Qodo learns your standards from review history

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread src/data/src/Support/Creation/DataCreator.php
Comment thread src/data/src/Support/Creation/DataCreator.php
Rule strings were kept whole whenever they contained "regex:" anywhere,
so a string such as "required|not_regex:/^a/" reached the rule factory
as one unknown rule. The normalizer and denormalizer now keep a string
whole only when it starts with regex: or not_regex:, since that pattern
may contain a pipe. Other strings split on pipes like Laravel's rule
parser.

A child path's own terminal wildcard now counts as continuing the
partial selection, so "artist.*" next to "*" still checks the nested
names under artist. Merging two partial trees keeps the nested property
lists of both trees instead of recomputing them from the merged result.

Contextual parameter resolution skips variadic parameters. An override
is passed to the constructor as a single argument, so a resolved list
for a variadic parameter would arrive as one nested array; the container
already spreads the contextual value when it builds the class.
resolveContextualParameters() is now part of the container contract,
and the Data instantiator depends on the contract again.

When a property declares several collection item annotations, the
fallback item type is chosen by comparing the resolved types. An
imported name and a fully qualified name for the same item class now
agree, so array properties keep their Data item type.

Smaller changes:

- The test migration uses an unsigned big integer for fake_model_id,
  matching the referenced key.
- The removeType() docblock explains how requiring rules are matched.
- The container reference comment explains why an unresolvable binding
  fails.
- The Data creation hooks documentation states that values returned by
  the hooks are treated as prepared input.
- The container documentation notes that variadic parameters are
  resolved when the class is built.
- docs/todo.md records the planned removal of pipe-separated rule
  strings in favor of rule arrays.

Validation: formatting, static analysis, the Data and Container test
suites, the facade docblock test and the route dependency resolver test
pass.
@binaryfire

Copy link
Copy Markdown
Member Author

@macroscope-app review

@macroscopeapp

macroscopeapp Bot commented Oct 4, 2026

Copy link
Copy Markdown

Manual reviews triggered for commit 9b8fe6a:

All prior checks · these links stay valid even if you push more commits.

@binaryfire

Copy link
Copy Markdown
Member Author

@cubic-dev-ai review

@macroscopeapp

macroscopeapp Bot commented Oct 4, 2026

Copy link
Copy Markdown

Review started; results will be posted in the check runs.

@cubic-dev-ai

cubic-dev-ai Bot commented Oct 4, 2026

Copy link
Copy Markdown

@cubic-dev-ai review

@binaryfire I have started the AI code review. It will take a few minutes to complete.

Comment thread src/data/src/Support/Validation/RuleDenormalizer.php Outdated
Comment thread src/data/src/Support/Creation/DataCreator.php
Comment thread src/data/src/Support/Creation/DataCreator.php
Comment thread src/data/src/Support/Creation/DataInstantiator.php
Comment thread src/data/src/Support/Creation/DataCreator.php
Comment thread src/data/src/Support/Creation/CreationContext.php
@macroscopeapp

macroscopeapp Bot commented Oct 4, 2026

Copy link
Copy Markdown

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This is a broad Data-package feature and engine refactor affecting request creation, validation, casting, transformation, persistence, routing, and container behavior across hundreds of files. An unresolved concrete defect in regex rule handling, along with additional creation and compatibility concerns, leaves material runtime risk requiring human review.

Not approved because:

  • automatic approval is not enabled for this workspace

Notes:

  • The correctness review posted 6 findings. Your repo's Minimum Blocking Severity is Off, so they do not automatically block approval.

Enable approvability here. You can add or adjust custom eligibility rules. Learn more.

@cubic-dev-ai cubic-dev-ai Bot 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.

6 issues found across 277 files

Confidence score: 3/5

  • DataInstantiator.php: Valid input can fail with propertyMissing when a child constructor skips its declaring parent constructor. Accept the supplied value for that inherited promoted property.
  • DataCreator.php: Hook-introduced inputs can fail when they need an operation’s custom normalizer, and hook replacements can be lost before casting. Apply the normalizers and update construction state with the replacement.
  • docs/todo.md: Removing string-rule support would break the documented public API for #[Rule('required|string')]. Preserve string-rule compatibility at public boundaries and normalize internally.
  • DataCreator.php: Nested date casts can silently fall back to global settings instead of the creation context’s formats and timezone. Copy both date settings into the nested factory.

You’re at about 95% of the monthly reviewed-line limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="docs/todo.md">

<violation number="1" location="docs/todo.md:73">
P2: This would remove a documented public API, not just change internal normalization: `#[Rule('required|string')]` is explicitly supported. Preserve string-rule compatibility at public boundaries and normalize internally, or specify a versioned deprecation and migration.</violation>
</file>

<file name="src/container/src/Container.php">

<violation number="1" location="src/container/src/Container.php:1775">
P2: An unknown class produces an empty recipe here, so this method silently returns `[]` instead of the `BindingResolutionException` raised by `build()` for the same target. Check `classExists` before iterating so invalid names do not look like classes without contextual parameters.</violation>
</file>

<file name="src/data/src/Support/Creation/DataInstantiator.php">

<violation number="1" location="src/data/src/Support/Creation/DataInstantiator.php:109">
P1: An inherited promoted property is skipped even when the child constructor did not call its declaring parent constructor, so valid input then fails with `propertyMissing`. Accept the supplied value when the property is still uninitialized, while preserving values assigned by the parent constructor.</violation>
</file>

<file name="src/data/src/Support/Creation/DataCreator.php">

<violation number="1" location="src/data/src/Support/Creation/DataCreator.php:114">
P2: Copy `$creationContext->dateFormats` and `$creationContext->dateTimezone` when creating the nested factory; otherwise date casting falls back to global configuration.</violation>

<violation number="2" location="src/data/src/Support/Creation/DataCreator.php:1068">
P2: Apply the operation's configured normalizers before fixed resolution here, matching `fillGeneralNode()`; hook-introduced nested inputs that only a custom normalizer can read otherwise fail creation.</violation>

<violation number="3" location="src/data/src/Support/Creation/DataCreator.php:1806">
P2: Write the replacement to construction state even when it is not finished; otherwise the cast receives the stale or missing value instead of the hook's replacement.</violation>
</file>

Note: This PR contains a large number of files. cubic selects up to 200 of the highest-priority eligible files for this review, so some files may not have been reviewed.

Re-trigger cubic

$value = $properties[$property->name] ?? null;

// A property promoted by an ancestor constructor belongs to the constructor chain, so input never replaces it.
if (! $property->isPromoted

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1: An inherited promoted property is skipped even when the child constructor did not call its declaring parent constructor, so valid input then fails with propertyMissing. Accept the supplied value when the property is still uninitialized, while preserving values assigned by the parent constructor.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/data/src/Support/Creation/DataInstantiator.php, line 109:

<comment>An inherited promoted property is skipped even when the child constructor did not call its declaring parent constructor, so valid input then fails with `propertyMissing`. Accept the supplied value when the property is still uninitialized, while preserving values assigned by the parent constructor.</comment>

<file context>
@@ -85,15 +103,30 @@ public function instantiate(DataClass $dataClass, array $properties): BaseData
+            $value = $properties[$property->name] ?? null;
+
+            // A property promoted by an ancestor constructor belongs to the constructor chain, so input never replaces it.
+            if (! $property->isPromoted
+                && array_key_exists($property->name, $properties)
+                && ! $value instanceof Optional
</file context>
Suggested change
if (! $property->isPromoted
if ((! $property->isPromoted || ! $property->reflection->isInitialized($data))

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Not changing this. Promoted properties belong to the constructor that declares them, so input is never assigned to them directly, as in Spatie's DataFromArrayResolver. If a child constructor doesn't call the parent's, the parent's promoted properties stay unset, as they would in plain PHP. Assigning them from outside the class would also fail for readonly properties.

Comment thread docs/todo.md

## Validation

- Remove pipe-separated validation rule strings in favor of arrays of rules across both `hypervel/validation` and `hypervel/data`, including Data's rule normalizer, denormalizer and `#[Rule]` handling. Update callers, tests and documentation together; preserve pipes inside individual regex patterns.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: This would remove a documented public API, not just change internal normalization: #[Rule('required|string')] is explicitly supported. Preserve string-rule compatibility at public boundaries and normalize internally, or specify a versioned deprecation and migration.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At docs/todo.md, line 73:

<comment>This would remove a documented public API, not just change internal normalization: `#[Rule('required|string')]` is explicitly supported. Preserve string-rule compatibility at public boundaries and normalize internally, or specify a versioned deprecation and migration.</comment>

<file context>
@@ -70,6 +70,7 @@
 
 ## Validation
 
+- Remove pipe-separated validation rule strings in favor of arrays of rules across both `hypervel/validation` and `hypervel/data`, including Data's rule normalizer, denormalizer and `#[Rule]` handling. Update callers, tests and documentation together; preserve pipes inside individual regex patterns.
 - Adopt Brick Math 0.20's bounded parsing API for untrusted numeric validation inputs once the supported dependency graph permits it. Ramsey UUID currently limits Brick Math to 0.18, which has neither bounded parsing nor 0.20's parser-backtracking fix, so an intermediate upgrade would not address this issue. Preserve the configurable exponent-range behavior and cover delegated and compiled rules, oversized mantissas and exponents, malformed input, and `multiple_of`; do not add a local parsing workaround or disguise an incompatible Brick version with a Composer alias.
 
</file context>
Suggested change
- Remove pipe-separated validation rule strings in favor of arrays of rules across both `hypervel/validation` and `hypervel/data`, including Data's rule normalizer, denormalizer and `#[Rule]` handling. Update callers, tests and documentation together; preserve pipes inside individual regex patterns.
- Normalize pipe-separated validation rules to arrays internally across `hypervel/validation` and `hypervel/data`, including Data's rule normalizer, denormalizer and `#[Rule]` handling. Preserve public string-rule compatibility and pipes inside regex patterns; update callers, tests and documentation together.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Not changing this. The entry records a planned breaking change, and this PR doesn't make it. When it's made, callers, tests and documentation change with it, as the entry says.

*/
public function resolveContextualParameters(string $concrete, ?array $names = null): array
{
$recipe = $this->getBuildRecipe($concrete);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: An unknown class produces an empty recipe here, so this method silently returns [] instead of the BindingResolutionException raised by build() for the same target. Check classExists before iterating so invalid names do not look like classes without contextual parameters.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/container/src/Container.php, line 1775:

<comment>An unknown class produces an empty recipe here, so this method silently returns `[]` instead of the `BindingResolutionException` raised by `build()` for the same target. Check `classExists` before iterating so invalid names do not look like classes without contextual parameters.</comment>

<file context>
@@ -1756,6 +1756,55 @@ public function buildWith(Closure|string $concrete, array $parameters = []): mix
+     */
+    public function resolveContextualParameters(string $concrete, ?array $names = null): array
+    {
+        $recipe = $this->getBuildRecipe($concrete);
+        $resolutionState = $this->getOrCreateResolutionState();
+        $resolutionState->buildStack[] = $concrete;
</file context>
Suggested change
$recipe = $this->getBuildRecipe($concrete);
$recipe = $this->getBuildRecipe($concrete);
if (! $recipe->classExists) {
throw new BindingResolutionException("Target class [{$concrete}] does not exist.");
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Not changing this. The values are resolved for a class that is then built with buildWith(), and buildWith() throws BindingResolutionException for a class that doesn't exist. A check here would report the same error one call earlier.

Comment thread src/data/src/Support/Transformation/PartialTree.php Outdated
Comment on lines +1806 to +1808
if ($property->isFinishedValue($value)) {
$state->writeFinishedPropertyValue($inputPath, $value);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: Write the replacement to construction state even when it is not finished; otherwise the cast receives the stale or missing value instead of the hook's replacement.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/data/src/Support/Creation/DataCreator.php, line 1806:

<comment>Write the replacement to construction state even when it is not finished; otherwise the cast receives the stale or missing value instead of the hook's replacement.</comment>

<file context>
@@ -1539,6 +1799,17 @@ protected function reconcileProperty(
+            // Without validation, the cast receives the hook's value exactly as given.
+            $state->clearChildStructure($property->name);
+
+            if ($property->isFinishedValue($value)) {
+                $state->writeFinishedPropertyValue($inputPath, $value);
+            }
</file context>
Suggested change
if ($property->isFinishedValue($value)) {
$state->writeFinishedPropertyValue($inputPath, $value);
}
if ($property->isFinishedValue($value)) {
$state->writeFinishedPropertyValue($inputPath, $value);
} else {
$state->writePropertyValue($inputPath, $value);
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Not changing this. applyPayloadHooks() replaces the operation's payload with the hook's return value before properties are reconciled, so the cast reads the replacement from there. writeFinishedPropertyValue() only marks a value as already built. I checked this with a cast that records its input, and it receives the afterValidation() replacement.

->withOptionalValues($creationContext->useOptionalValues)
->ignoreMagicalMethod(...$creationContext->ignoredMagicalMethods)
->withCastCollection($creationContext->casts)
->withNormalizers(...$creationContext->normalizers);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: Copy $creationContext->dateFormats and $creationContext->dateTimezone when creating the nested factory; otherwise date casting falls back to global configuration.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/data/src/Support/Creation/DataCreator.php, line 114:

<comment>Copy `$creationContext->dateFormats` and `$creationContext->dateTimezone` when creating the nested factory; otherwise date casting falls back to global configuration.</comment>

<file context>
@@ -77,18 +88,32 @@ public function __construct(
+                ->withOptionalValues($creationContext->useOptionalValues)
+                ->ignoreMagicalMethod(...$creationContext->ignoredMagicalMethods)
+                ->withCastCollection($creationContext->casts)
+                ->withNormalizers(...$creationContext->normalizers);
+        }
+
</file context>

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Not changing this. A CreationContext is only built by CreationContextFactory::get(), which always takes dateFormats and dateTimezone from the Data config. An operation can't set its own date settings, so a nested factory built from the same config gets the same values.

return $this->invokeNamedObjectFactory($dataClass, ...$match);
}

$source = SourceResolver::resolve($payload);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: Apply the operation's configured normalizers before fixed resolution here, matching fillGeneralNode(); hook-introduced nested inputs that only a custom normalizer can read otherwise fail creation.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/data/src/Support/Creation/DataCreator.php, line 1068:

<comment>Apply the operation's configured normalizers before fixed resolution here, matching `fillGeneralNode()`; hook-introduced nested inputs that only a custom normalizer can read otherwise fail creation.</comment>

<file context>
@@ -950,44 +1041,61 @@ protected function tryCreateDirectArrayNode(
+            return $this->invokeNamedObjectFactory($dataClass, ...$match);
+        }
+
+        $source = SourceResolver::resolve($payload);
+
+        if ($source === null) {
</file context>
Suggested change
$source = SourceResolver::resolve($payload);
$normalizers = $extensions->normalizers($dataClass, $state->context);
$source = SourceResolver::normalize($payload, $normalizers) ?? SourceResolver::resolve($payload);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Not changing this. Values returned by creation hooks are treated as prepared input, so normalizers, prepareForPipeline() and prepareData hooks don't run on them, as the Data guide documents. A hook that adds nested input returns it in the shape the data class reads.

The previous change kept a rule string whole only when it started with
"regex:" or "not_regex:". Laravel's rule parser also treats "notregex"
as a regex rule and compares rule names without regard to case, so
"notregex:/a|b/" was split on its pipe, and "Regex:/a|b/" had been
split before that change too. The normalizer and denormalizer now
compare the lowercased first rule name against the names Laravel uses.

Numeric partial path segments are stored as integer keys, which lets
"meta.0" select an array item. Code that assumed string keys threw a
TypeError instead:

- merging two partial trees passed an integer key to child();
- the nested partial check passed an integer name to the missing
  property exception;
- the request query string resolver passed an integer field to
  findProperty(), so a request such as "?only=0" caused a server error.

child() and findProperty() accept integer keys, the nested property list
holds strings, and the tree docblocks describe integer keys. A numeric
segment where a data property is expected follows the usual rules for
unknown properties, and the query string resolver treats it as an
unknown name.

Validation: formatting, static analysis and the Data test suite pass.
Comment thread src/data/src/Support/Transformation/PartialTree.php
@binaryfire
binaryfire merged commit c19900e into 0.4 Oct 5, 2026
51 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant