Skip to content

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

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.


Summary by cubic

Brings Hypervel's Data package in line with spatie/laravel-data and fixes a long list of creation, validation, and output defects. Spatie's test suite is now merged into the Data tests, with a regression test for every defect it exposed. The fixed creation engine is retained rather than Spatie's configurable pipeline, and the Data guide is rewritten around common tasks.

Restored APIs and creation fixes

  • Restores RouteParameterReference, AuthenticatedUserReference, ContainerReference, #[Rule] normalization, configurable rule_inferrers, withOptionalValues()/withoutOptionalValues(), defaultWrap(), SerializeTransformer, prepareForPipeline(), the FormRequestNormalizer, and make:data namespace and suffix defaults.
  • Validation now receives the complete normalized input, so confirmed works and validation and construction read the same field; validated output still excludes undeclared keys.
  • Scalars follow PHP's weak typing — '123' becomes an int, 'abc' raises TypeError — including collection items and contextual attribute values.
  • Union properties record the type the input selects per item, so a normalized child is not mistaken for an array and ambiguous container types are rejected.
  • Rules assemble as in Spatie: inferred presence and type rules first, declared attributes replace same-type inferences, backed enums infer Enum, and conditional references resolve through mapped input names. Rule strings split on pipes like Laravel's parser unless they start with regex: or not_regex:.
  • A child path's own terminal wildcard continues a partial selection, merged partial trees keep both sides' nested lists, and fallback collection item annotations compare resolved types.
  • Paginated collections and paginator properties transform to a {data, links, meta} shape in toArray(), toJson(), and responses.

Framework changes and intentional differences

  • Container and routing calls now apply PHP's weak scalar conversion like Laravel, so a typed route parameter accepts '123' for int $id; Container::resolveContextualParameters() resolves contextual attribute values without building their class and skips variadic parameters, and #[Give] accepts a property path.
  • Eloquent abstract casts store the subtype's alias when registered or its class name otherwise, read either, and reject non-concrete subtypes; data collection casts accept collections and other Arrayable values.
  • Responses use 200 for every method — set 201 in withResponse() after creating something — and contextual attribute values always win over payload input.
  • The README lists the remaining differences from Spatie and why; malformed partial paths in code throw instead of being silently ignored.

Written for commit 9b8fe6a. Summary will update on new commits.

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-backup/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 6af8e8d9-48a3-4b9d-9988-07724c5e9064

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.

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

Copy link
Copy Markdown

PR Summary by Qodo

Complete the Data API and fix creation, validation, and output defects

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

Grey Divider

AI Description

• Restores applicable Laravel Data APIs while keeping Hypervel’s fixed, operation-scoped creation
 engine.
• Fixes input preparation, validation, scalar conversion, pagination, partials, and Eloquent casts.
• Adds upstream and regression coverage, updates configuration, and rewrites the Data guide.
Diagram

graph TD
  A["Normalized input"] --> B["Creation state"] --> C["Validation compiler"] --> D["Data objects"] --> E["Data transformer"] --> F["Responses"]
  B --> G["Casting and construction"] --> D
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Adopt Spatie’s configurable pipeline
  • ➕ Would more directly inherit upstream pipe extension points.
  • ➖ Would replace Hypervel’s fixed creation engine and complicate shared type decisions and worker-scoped performance.
2. Patch defects independently in each phase
  • ➕ Smaller initial changes to existing components.
  • ➖ Validation, casts, and construction could continue to disagree about mapped input and union types.

Recommendation: Keep the fixed engine and shared per-operation preparation: it addresses the underlying cross-phase inconsistencies without adopting an incompatible pipeline. Review the shared-state and validation boundaries especially closely.

Files changed (268) +34591 / -3196

Enhancement (33) +957 / -38
Give.phpSupport property paths in Give +11/-1

Support property paths in Give

• Allows contextual Give values to select a property path.

src/container/src/Attributes/Give.php

Container.phpResolve contextual values before construction +51/-1

Resolve contextual values before construction

• Exposes contextual constructor values without building the class and uses weak scalar invocation for construction.

src/container/src/Container.php

Dimensions.phpAccept dimensions rule objects +11/-1

Accept dimensions rule objects

• Supports normalized dimensions validation rules.

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

Exists.phpSupport normalized Exists rules +5/-0

Support normalized Exists rules

• Accepts existing database rule objects during validation normalization.

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

Rule.phpExpose Rule contents for normalization +10/-3

Expose Rule contents for normalization

• Lets declared Rule attributes participate in typed-rule replacement.

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

Unique.phpSupport normalized Unique rules +5/-0

Support normalized Unique rules

• Accepts existing database rule objects during validation normalization.

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

BuiltinTypeCast.phpAlign built-in cast signature +2/-3

Align built-in cast signature

• Passes the declared property-value view required by the revised cast contract.

src/data/src/Casts/BuiltinTypeCast.php

Cast.phpExtend the custom cast contract +3/-2

Extend the custom cast contract

• Adds declared property values to custom cast calls.

src/data/src/Casts/Cast.php

CastableCast.phpAlign castable cast signature +2/-3

Align castable cast signature

• Adapts castable types to the revised cast contract.

src/data/src/Casts/CastableCast.php

DateTimeInterfaceCast.phpAlign date cast signature +2/-3

Align date cast signature

• Adapts date conversion to the revised cast contract.

src/data/src/Casts/DateTimeInterfaceCast.php

EnumCast.phpAlign enum cast signature +2/-3

Align enum cast signature

• Adapts enum conversion to the revised cast contract.

src/data/src/Casts/EnumCast.php

Uncastable.phpAlign uncastable result signature +2/-2

Align uncastable result signature

• Updates the cast sentinel for the revised contract.

src/data/src/Casts/Uncastable.php

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

Restore Data creation and optional-value APIs

• Adds preparation and factory behavior used by the reconciled creation flow.

src/data/src/Concerns/BaseData.php

WrappableData.phpRestore default wrapping API +3/-1

Restore default wrapping API

• Provides the class-level default wrapper behavior.

src/data/src/Concerns/WrappableData.php

DataMakeCommand.phpHonor configured make:data defaults +32/-2

Honor configured make:data defaults

• Applies namespace and suffix defaults while retaining command-line overrides.

src/data/src/Console/DataMakeCommand.php

BaseData.phpExpand the BaseData API contract +12/-3

Expand the BaseData API contract

• Declares the restored creation and factory operations.

src/data/src/Contracts/BaseData.php

Data.phpExpose restored Data behavior +2/-0

Expose restored Data behavior

• Connects the concrete Data class to the expanded package API.

src/data/src/Data.php

CannotResolveRouteParameterReference.phpReport unresolved route references +28/-0

Report unresolved route references

• Adds explicit failures for missing route parameters and referenced properties.

src/data/src/Exceptions/CannotResolveRouteParameterReference.php

CouldNotCreateValidationRule.phpReport rule-normalization failures +18/-0

Report rule-normalization failures

• Adds a dedicated exception for validation rules that cannot be created.

src/data/src/Exceptions/CouldNotCreateValidationRule.php

FormRequestNormalizer.phpAdd optional FormRequest normalizer +22/-0

Add optional FormRequest normalizer

• Reads validated FormRequest input when the normalizer is enabled.

src/data/src/Normalizers/FormRequestNormalizer.php

RuleInferrer.phpDefine configurable rule inferrers +18/-0

Define configurable rule inferrers

• Introduces the extension contract for per-compilation rule inference.

src/data/src/RuleInferrers/RuleInferrer.php

CreationContext.phpTrack optional-value creation setting +1/-0

Track optional-value creation setting

• Carries the restored optional-value choice through creation context.

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

CreationContextFactory.phpExpand creation factory controls +29/-3

Expand creation factory controls

• Supports optional values and creating factories from an existing context.

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

DataClass.phpRecord additional Data metadata +2/-0

Record additional Data metadata

• Exposes metadata needed by the reconciled creation and validation paths.

src/data/src/Support/DataClass.php

DataProperty.phpRecord property creation metadata +2/-0

Record property creation metadata

• Adds property information needed to coordinate input and validation.

src/data/src/Support/DataProperty.php

PropertyRules.phpReplace inferred rules by type +109/-0

Replace inferred rules by type

• Provides ordered property rules whose declared types supersede inferred counterparts.

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

AuthenticatedUserReference.phpReference authenticated users in rules +37/-0

Reference authenticated users in rules

• Resolves current-user values for validation attributes.

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

ContainerReference.phpReference container bindings in rules +37/-0

Reference container bindings in rules

• Resolves binding values and fails instead of silently substituting null when unavailable.

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

RouteParameterReference.phpReference route parameters in rules +50/-0

Reference route parameters in rules

• Resolves route values and optional property paths with explicit missing-value failures.

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

RuleNormalizer.phpNormalize declared rules to typed attributes +131/-0

Normalize declared rules to typed attributes

• Converts representable rules for type-aware replacement while passing other rules through unchanged.

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

ValidationRuleFactory.phpMap validation strings to attributes +281/-0

Map validation strings to attributes

• Creates typed attributes for supported rule names and retains unsupported forms.

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

SerializeTransformer.phpRestore SerializeTransformer +19/-0

Restore SerializeTransformer

• Adds the transformer supported by the upstream Data API.

src/data/src/Transformers/SerializeTransformer.php

App.phpExpose contextual container resolution +1/-0

Expose contextual container resolution

• Adds facade typing for the new container method.

src/support/src/Facades/App.php

Bug fix (54) +2923 / -777
BoundMethod.phpInvoke bound methods with weak scalar typing +5/-1

Invoke bound methods with weak scalar typing

• Routes application callbacks through the native invoker.

src/container/src/BoundMethod.php

NativeInvoker.phpAdd weak-typing invocation boundary +38/-0

Add weak-typing invocation boundary

• Calls and constructs application code from a non-strict PHP file so native scalar conversion applies.

src/container/src/NativeInvoker.php

AcceptedIf.phpPreserve AcceptedIf comparison values +2/-10

Preserve AcceptedIf comparison values

• Stops coercing conditional rule parameters into different values.

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

After.phpAccept parseable After dates +1/-9

Accept parseable After dates

• Removes date handling that rejected valid parseable inputs.

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

AfterOrEqual.phpAccept parseable AfterOrEqual dates +1/-9

Accept parseable AfterOrEqual dates

• Removes date handling that rejected valid parseable inputs.

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

Before.phpAccept parseable Before dates +1/-9

Accept parseable Before dates

• Removes date handling that rejected valid parseable inputs.

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

BeforeOrEqual.phpAccept parseable BeforeOrEqual dates +1/-9

Accept parseable BeforeOrEqual dates

• Removes date handling that rejected valid parseable inputs.

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

DateEquals.phpAccept parseable DateEquals dates +1/-9

Accept parseable DateEquals dates

• Removes date handling that rejected valid parseable inputs.

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

DeclinedIf.phpPreserve DeclinedIf comparison values +2/-10

Preserve DeclinedIf comparison values

• Keeps conditional parameters in their original form.

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

ExcludeIf.phpPreserve ExcludeIf comparison values +2/-10

Preserve ExcludeIf comparison values

• Keeps conditional parameters in their original form.

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

StringValidationAttribute.phpPreserve unsupported rule-string parameters +14/-0

Preserve unsupported rule-string parameters

• Retains rule strings when an attribute cannot represent their parameters without changing meaning.

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

IterableItemCast.phpCast declared iterable items +3/-2

Cast declared iterable items

• Aligns item casting with selected collection types and the revised cast contract.

src/data/src/Casts/IterableItemCast.php

BaseDataCollectable.phpPreserve partials when reading collections +32/-0

Preserve partials when reading collections

• Carries collection selections onto accessed items without repeatedly accumulating copies.

src/data/src/Concerns/BaseDataCollectable.php

ResponsableData.phpAlign Data response construction +3/-1

Align Data response construction

• Passes transformed collection data through the corrected resource response path.

src/data/src/Concerns/ResponsableData.php

DataCollection.phpHandle collection item partials +7/-1

Handle collection item partials

• Keeps partial selections attached to items read from Data collections.

src/data/src/DataCollection.php

AbstractDataEloquentCast.phpRead and write abstract Data types without aliases +11/-20

Read and write abstract Data types without aliases

• Stores an alias when available or a class name otherwise, validating concrete subtypes before creation.

src/data/src/Eloquent/AbstractDataEloquentCast.php

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

Accept Arrayable collection cast values

• Supports collections and other Arrayable inputs for Eloquent collection casts.

src/data/src/Eloquent/DataCollectionEloquentCast.php

DataPropertyOperation.phpMake property operations string-backed +6/-6

Make property operations string-backed

• Allows failure traces containing operations to be JSON-encoded.

src/data/src/Enums/DataPropertyOperation.php

CannotCastData.phpRevise cast failure cases +3/-19

Revise cast failure cases

• Aligns morph-cast errors with alias-or-class storage and subtype checks.

src/data/src/Exceptions/CannotCastData.php

CannotCreateData.phpReport additional creation failures +16/-4

Report additional creation failures

• Adds error paths for invalid named factories and ambiguous or invalid input.

src/data/src/Exceptions/CannotCreateData.php

CannotPerformPartialOnDataField.phpClarify invalid partial failures +1/-1

Clarify invalid partial failures

• Updates the error reported for partial paths targeting unsuitable fields.

src/data/src/Exceptions/CannotPerformPartialOnDataField.php

DataCollectionResource.phpBuild pagination from original paginator +19/-9

Build pagination from original paginator

• Adds links and metadata without replacing paginator rows with transformed output, preserving cursor fields.

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

NormalizedModel.phpDistinguish missing model attributes +22/-3

Distinguish missing model attributes

• Reads overridden model accessors while treating unselected columns as absent.

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

UnknownProperty.phpAlign unknown-property handling +2/-2

Align unknown-property handling

• Updates the missing-value representation used during normalization.

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

DataIterableAnnotationReader.phpRead collection class annotations +41/-0

Read collection class annotations

• Obtains item types from a collection class’s own extends annotation.

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

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

Make lazy replay modes string-backed

• Keeps creation-state diagnostics serializable.

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

ConstructionState.phpShare prepared creation decisions +253/-1

Share prepared creation decisions

• Records mapped input, selected types, deferred factories, and validation decisions per operation.

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

CreationExtensions.phpScope creation extensions per operation +222/-0

Scope creation extensions per operation

• Caches casts, normalizers, and lazy attributes for one creation operation without sharing them across requests.

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

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

Make creation modes string-backed

• Keeps creation traces JSON-serializable.

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

DataCreator.phpReconcile Data preparation and construction +874/-324

Reconcile Data preparation and construction

• Uses one prepared input and recorded type decisions across validation, casting, nested creation, and construction.

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

DataInstantiator.phpConstruct with safe named and contextual input +40/-7

Construct with safe named and contextual input

• Handles non-property parameters, rejects payload writes to non-public promoted properties, and prepares contextual scalars.

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

NativeScalar.phpAdd native scalar conversion helper +45/-0

Add native scalar conversion helper

• Applies PHP-compatible weak scalar conversion to Data properties and collection items.

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

SourceResolver.phpCorrect normalized source precedence +24/-15

Correct normalized source precedence

• Uses normalized input for unknown-field checks and handles later payload precedence.

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

ValueCaster.phpAlign value casting with selected types +5/-9

Align value casting with selected types

• Passes declared values to casts and avoids premature nested construction.

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

DataClassRepository.phpKeep metadata cache worker-safe +6/-14

Keep metadata cache worker-safe

• Adjusts class metadata caching for the revised lifecycle and anonymous classes.

src/data/src/Support/DataClassRepository.php

DataPropertyType.phpSelect accepted union and collection types +84/-0

Select accepted union and collection types

• Determines the input-selected type and rejects ambiguous container matches.

src/data/src/Support/DataPropertyType.php

DataClassFactory.phpBuild reconciled class metadata +46/-20

Build reconciled class metadata

• Handles constructor visibility, lifecycle methods, and restored declaration behavior.

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

DataPropertyFactory.phpBuild corrected property metadata +18/-3

Build corrected property metadata

• Records mapped input, cast, and collection declaration information.

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

DataTypeFactory.phpImprove reflected and annotated types +29/-32

Improve reflected and annotated types

• Reconciles union and collection item types for construction and validation.

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

PartialsDefinition.phpPreserve collection partial selections +27/-2

Preserve collection partial selections

• Applies nested selections consistently when items are read or transformed.

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

DataTransformer.phpCorrect partial and pagination transformation +185/-64

Correct partial and pagination transformation

• Handles lazy iterables, optional values, nested wrappers, depth options, and paginated output.

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

PartialTree.phpValidate and propagate nested partials +28/-2

Validate and propagate nested partials

• Supports nested selection checks and consistent partial traversal.

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

PhpDocTypeNameResolver.phpResolve complex annotation names +50/-12

Resolve complex annotation names

• Improves PHPDoc type resolution for collection declarations and namespaces.

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

CompiledValidation.phpPreserve only appropriate validated input +17/-8

Preserve only appropriate validated input

• Adjusts how compiled validation retains constructor fields and excludes undeclared output.

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

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

Compile rules against complete prepared input

• Orders inferred and declared rules, maps field references, and validates nested and finished values consistently.

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

DataValidator.phpValidate complete normalized payloads +18/-10

Validate complete normalized payloads

• Passes undeclared input to the validator while returning only appropriate validated fields.

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

EnsurePropertyMorphable.phpCheck morphable property types +24/-0

Check morphable property types

• Adds validation for acceptable concrete types when resolving polymorphic properties.

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

RuleDenormalizer.phpPreserve declared validation semantics +14/-8

Preserve declared validation semantics

• Converts normalized attributes back to validator rules without altering unsupported parameters.

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

ValidationAccumulator.phpTrack compiled validation paths +23/-0

Track compiled validation paths

• Records rules and constructor input paths for safe restoration after validation.

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

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

Make wrapping modes string-backed

• Keeps output-operation diagnostics serializable.

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

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

Read paginated Data collection metadata

• Uses the underlying paginator for Inertia scroll information.

src/inertia/src/ScrollMetadata.php

CallableDispatcher.phpCoerce callable route scalars +2/-1

Coerce callable route scalars

• Invokes callable routes through the weak-typing boundary.

src/routing/src/CallableDispatcher.php

Controller.phpCoerce callAction scalars +4/-1

Coerce callAction scalars

• Invokes controller actions with native weak scalar conversion.

src/routing/src/Controller.php

ControllerDispatcher.phpCoerce dispatched controller scalars +4/-1

Coerce dispatched controller scalars

• Uses the native invoker for controller actions.

src/routing/src/ControllerDispatcher.php

Documentation (7) +1180 / -273
sync.yamlRecord upstream review revisions +12/-11

Record upstream review revisions

• Records checked-through revisions and dates for Data and other tracked upstream projects.

docs/upstream-sync/sync.yaml

README.mdDocument Data compatibility boundaries +39/-5

Document Data compatibility boundaries

• Explains supported behavior and intentional differences from Spatie.

src/data/README.md

Transformer.phpRemove obsolete transformer note +0/-1

Remove obsolete transformer note

• Removes a comment that incorrectly described SerializeTransformer as unavailable.

src/data/src/Transformers/Transformer.php

container.mdDocument contextual resolution +27/-1

Document contextual resolution

• Explains resolveContextualParameters and Give property paths.

src/docs/container.md

data-objects.mdRewrite task-oriented Data guide +1080/-252

Rewrite task-oriented Data guide

• Documents creation, validation, casting, transformation, responses, configuration, and intentional behavior.

src/docs/data-objects.md

porting-from-laravel.mdDocument Data porting differences +20/-3

Document Data porting differences

• Lists behavior to review when moving Laravel Data code to Hypervel.

src/docs/porting-from-laravel.md

README.mdClarify benchmark environment +2/-0

Clarify benchmark environment

• Documents runtime settings relevant to Data measurements.

tests/Benchmarks/Data/README.md

Other (174) +29531 / -2108
.php-cs-fixer.phpAdjust formatting configuration +3/-0

Adjust formatting configuration

• Adds formatting rules needed by the reconciled code.

.php-cs-fixer.php

data.phpExpose reconciled Data options +70/-3

Expose reconciled Data options

• Adds rule-inferrer, partial, depth, dumper, computed-property, normalizer, and generator settings.

src/data/config/data.php

DataServiceProvider.phpRegister new Data services and options +16/-3

Register new Data services and options

• Wires reconciliation services and configured integrations into the provider.

src/data/src/DataServiceProvider.php

DataConfig.phpLoad restored Data configuration +36/-1

Load restored Data configuration

• Exposes new rule, partial, depth, dumper, and generator options to package services.

src/data/src/Support/DataConfig.php

TransformationContext.phpTrack depth failure policy +4/-0

Track depth failure policy

• Carries whether reaching maximum transformation depth should throw.

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

TransformationContextFactory.phpConfigure transformation depth behavior +9/-1

Configure transformation depth behavior

• Builds contexts with the new depth policy while retaining strict persistence behavior.

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

benchmark.phpReport runtime and shorten expensive samples +69/-6

Report runtime and shorten expensive samples

• Displays effective OPcache and JIT status and reduces expensive benchmark durations.

tests/Benchmarks/Data/benchmark.php

ContainerCallTest.phpTest weak container invocation +24/-0

Test weak container invocation

• Checks scalar conversion when the container calls application code.

tests/Container/ContainerCallTest.php

ContextualAttributeBindingTest.phpTest contextual resolution and Give paths +110/-0

Test contextual resolution and Give paths

• Covers resolving selected attributes without construction and selecting property paths.

tests/Container/ContextualAttributeBindingTest.php

AppendTest.phpTest appended Data output +127/-0

Test appended Data output

• Adds upstream-aligned appended-property scenarios.

tests/Data/AppendTest.php

AttributeTest.phpAlign attribute expectations +1/-2

Align attribute expectations

• Adjusts attribute coverage to the reconciled API.

tests/Data/Attributes/AttributeTest.php

FromAuthenticatedUserPropertyTest.phpTest current-user property injection +44/-0

Test current-user property injection

• Covers authenticated-user values used in Data properties.

tests/Data/Attributes/FromAuthenticatedUserPropertyTest.php

FromAuthenticatedUserTest.phpTest current-user creation injection +100/-0

Test current-user creation injection

• Covers authenticated-user values during Data creation.

tests/Data/Attributes/FromAuthenticatedUserTest.php

FromContainerPropertyTest.phpTest container property injection +67/-0

Test container property injection

• Covers container-resolved Data property values.

tests/Data/Attributes/FromContainerPropertyTest.php

FromContainerTest.phpTest container creation injection +97/-0

Test container creation injection

• Covers contextual container values during creation.

tests/Data/Attributes/FromContainerTest.php

FromRouteParameterPropertyTest.phpTest route property injection +68/-0

Test route property injection

• Covers route-resolved Data property values.

tests/Data/Attributes/FromRouteParameterPropertyTest.php

FromRouteParameterTest.phpTest route creation injection +65/-0

Test route creation injection

• Covers route values and contextual scalar conversion.

tests/Data/Attributes/FromRouteParameterTest.php

PasswordTest.phpExpand password attribute coverage +27/-7

Expand password attribute coverage

• Tests password validation using normalized rules.

tests/Data/Attributes/Validation/PasswordTest.php

RulesTest.phpTest declared Rule attributes +134/-0

Test declared Rule attributes

• Covers rule normalization and replacement of inferred types.

tests/Data/Attributes/Validation/RulesTest.php

ValidationAttributeTest.phpConsolidate validation attribute expectations +13/-179

Consolidate validation attribute expectations

• Removes duplicated checks now covered by the broader upstream-aligned suite.

tests/Data/Attributes/Validation/ValidationAttributeTest.php

BuiltinTypeCastTest.phpTest scalar and built-in casts +72/-16

Test scalar and built-in casts

• Adds weak scalar conversion and invalid-input cases.

tests/Data/Casts/BuiltinTypeCastTest.php

DateTimeInterfaceCastTest.phpExpand date cast coverage +176/-49

Expand date cast coverage

• Reconciles date and time conversion cases with the cast contract.

tests/Data/Casts/DateTimeInterfaceCastTest.php

EnumCastTest.phpExpand enum cast coverage +50/-45

Expand enum cast coverage

• Tests enum input behavior under the revised creation flow.

tests/Data/Casts/EnumCastTest.php

CollectionAttributeWithAnotationsTest.phpTest annotated collection properties +65/-0

Test annotated collection properties

• Covers deriving item classes from collection annotations.

tests/Data/CollectionAttributeWithAnotationsTest.php

AppendableDataTest.phpRemove duplicate append assertions +1/-93

Remove duplicate append assertions

• Leaves focused coverage after moving broader cases into upstream-aligned tests.

tests/Data/Concerns/AppendableDataTest.php

EmptyDataTest.phpConsolidate empty Data coverage +2/-84

Consolidate empty Data coverage

• Removes duplicate cases covered by the new empty-data suite.

tests/Data/Concerns/EmptyDataTest.php

WrappableDataTest.phpTest default and nested wrapping +34/-4

Test default and nested wrapping

• Expands wrapper behavior and response assertions.

tests/Data/Concerns/WrappableDataTest.php

DataMakeCommandTest.phpTest generator defaults +54/-4

Test generator defaults

• Covers configured namespace, suffix, and command overrides.

tests/Data/Console/DataMakeCommandTest.php

CreationFactoryTest.phpTest restored factory API +255/-0

Test restored factory API

• Covers creation contexts, optional values, and factory behavior.

tests/Data/CreationFactoryTest.php

CreationTest.phpAdd comprehensive creation coverage +2255/-0

Add comprehensive creation coverage

• Ports upstream creation cases and adds regressions for prepared input, unions, casts, and constructors.

tests/Data/CreationTest.php

DataCollectionTest.phpTest Data collection creation and reads +262/-20

Test Data collection creation and reads

• Covers collection item behavior, partials, and nullable collection targets.

tests/Data/DataCollectionTest.php

FillRouteParameterPropertiesDataPipeTest.phpTest route-parameter filling +156/-0

Test route-parameter filling

• Ports route-injection cases to verify equivalent fixed-engine behavior.

tests/Data/DataPipes/FillRouteParameterPropertiesDataPipeTest.php

DataServiceProviderTest.phpUpdate provider assertions +2/-2

Update provider assertions

• Checks registrations under the expanded configuration.

tests/Data/DataServiceProviderTest.php

DataTest.phpTest core Data APIs +151/-0

Test core Data APIs

• Adds upstream-aligned Data object behavior checks.

tests/Data/DataTest.php

DataCollectionEloquentCastTest.phpTest collection persistence casts +406/-166

Test collection persistence casts

• Covers Arrayable values, morph envelopes, and collection round trips.

tests/Data/Eloquent/DataCollectionEloquentCastTest.php

DataEloquentCastTest.phpTest concrete and abstract Data casts +379/-153

Test concrete and abstract Data casts

• Covers alias-or-class storage, subtype rejection, and persistence round trips.

tests/Data/Eloquent/DataEloquentCastTest.php

EmptyTest.phpTest empty Data representations +85/-0

Test empty Data representations

• Adds upstream-aligned empty-object behavior.

tests/Data/EmptyTest.php

AbstractData.phpAdd abstract Data fixture +11/-0

Add abstract Data fixture

• Defines a declared base type for morph and cast tests.

tests/Data/Fixtures/AbstractData/AbstractData.php

AbstractDataA.phpAdd abstract Data subtype fixture A +16/-0

Add abstract Data subtype fixture A

• Provides a concrete subtype for morph tests.

tests/Data/Fixtures/AbstractData/AbstractDataA.php

AbstractDataB.phpAdd abstract Data subtype fixture B +16/-0

Add abstract Data subtype fixture B

• Provides another concrete subtype for morph tests.

tests/Data/Fixtures/AbstractData/AbstractDataB.php

AbstractPropertyMorphableData.phpAdd morphable property fixture +34/-0

Add morphable property fixture

• Exercises abstract property type selection.

tests/Data/Fixtures/AbstractPropertyMorphableData.php

ConfidentialDataCast.phpAdd confidential-data cast fixture +21/-0

Add confidential-data cast fixture

• Supplies custom casting behavior for creation tests.

tests/Data/Fixtures/Casts/ConfidentialDataCast.php

MeaningOfLifeCast.phpAdd numeric cast fixture +29/-0

Add numeric cast fixture

• Exercises custom cast inputs and values.

<a href='https://github.com/hy...

@greptile-apps

greptile-apps Bot commented Oct 4, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 5/5

[High risk] Overhauls data object creation, validation, and container resolution.

The PR appears safe to merge based on the changes since the previous review and the status of earlier findings.

Summary

The PR expands the Data package’s creation, validation, and output APIs, with supporting container and routing changes. Since the previous review, it adjusts numeric partial-path handling and preserves regex rule aliases containing pipes.

Reviews (3) · Last reviewed commit: "Fix regex rule names and numeric partial..."

Comment thread src/data/src/Http/Resources/DataCollectionResource.php
@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. Wrapped data pages break infinite scroll 🐞 Bug ≡ Correctness
Description
ScrollMetadata::fromPaginator() unwraps a JsonResource or a Data paginated collection in a
single match, so it never unwraps both layers. When a scroll value is
JsonResource::make($paginatedDataCollection), metadata extraction reaches the unsupported-value
exception instead of the underlying paginator.
Code

src/inertia/src/ScrollMetadata.php[R37-40]

+        $paginator = match (true) {
+            $value instanceof JsonResource => $value->resource,
+            // The Data package is optional; its paginated collections own their paginator.
+            $value instanceof PaginatedDataCollection, $value instanceof CursorPaginatedDataCollection => $value->items(),
Evidence
A JSON resource stores its wrapped collection in resource; items() is needed to obtain that
collection's paginator. The single match stops after unwrapping the resource, while the subsequent
checks accept only paginator instances. Scroll properties use this method for default metadata.

src/http/src/Resources/Json/JsonResource.php[56-59]
src/data/src/PaginatedDataCollection.php[59-63]
src/inertia/src/ScrollMetadata.php[37-61]
src/inertia/src/ScrollProp.php[92-101]

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

## Issue description
A JSON resource wrapping a paginated Data collection is unwrapped only once, leaving a Data collection where a paginator is required for infinite-scroll metadata.
## Fix Focus Areas
- src/inertia/src/ScrollMetadata.php[37-43]
## Recommended Fix
Unwrap a `JsonResource` first, then independently unwrap a Data paginated collection through `items()`. Add a test for a paginated Data collection wrapped in `JsonResource`.

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


2. Database rules can match the wrong rows 🐞 Bug ≡ Correctness
Description
ContainerReference::getValue() returns data_get($dependency, $this->property) unchecked, so a
property that is missing or null on the resolved service comes back as null. A WhereConstraint
given that reference passes null to DatabaseRule::where(), which turns it into
whereNull($column). The exists/unique check then runs against rows where the column is NULL,
the same wrong-row outcome the new code's own comment says this reference must prevent.
Code

src/data/src/Support/Validation/References/ContainerReference.php[R28-33]

+        // An unresolvable binding must fail; a null rule parameter would make database constraints check the wrong rows.
+        $dependency = Container::getInstance()->make($this->dependency, $this->parameters);
+
+        if ($this->property !== null) {
+            return data_get($dependency, $this->property);
+        }
Evidence
The comment on line 28 says a null rule parameter makes database constraints check the wrong rows,
but only failures in make() are covered. A null property path still returns null. Database
constraints resolve references through parseExternalReference(), and DatabaseRule::where()
converts a null value into whereNull. RouteParameterReference throws
propertyOnParameterNotFound when its property is null, so the two references behave differently.

src/data/src/Support/Validation/References/ContainerReference.php[26-36]
src/data/src/Support/Validation/Constraints/WhereConstraint.php[18-30]
src/data/src/Support/Validation/Constraints/DatabaseConstraint.php[21-23]
src/validation/src/Rules/DatabaseRule.php[74-93]
src/data/src/Support/Validation/References/RouteParameterReference.php[38-48]

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

## Issue description
`ContainerReference::getValue()` returns `null` when the configured property is missing or null on the resolved dependency. When that value reaches a database `where` constraint, `DatabaseRule::where()` turns it into `whereNull`, so `exists`/`unique` validation checks the wrong rows.
## Fix Focus Areas
- src/data/src/Support/Validation/References/ContainerReference.php[26-36]
- src/data/src/Exceptions/CannotResolveRouteParameterReference.php[1-28]
## Recommended Fix
Do what `RouteParameterReference` does: when `$this->property !== null` and `data_get($dependency, $this->property)` returns `null`, throw a descriptive exception (for example, a new `CannotResolveContainerReference::propertyNotFound($dependency, $property)`). Optionally add a `nullable` constructor flag for callers who really want `null`. Add a test showing that a missing property throws.

ⓘ 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 on lines +37 to +40
$paginator = match (true) {
$value instanceof JsonResource => $value->resource,
// The Data package is optional; its paginated collections own their paginator.
$value instanceof PaginatedDataCollection, $value instanceof CursorPaginatedDataCollection => $value->items(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

1. Wrapped data pages break infinite scroll 🐞 Bug ≡ Correctness

ScrollMetadata::fromPaginator() unwraps a JsonResource or a Data paginated collection in a
single match, so it never unwraps both layers. When a scroll value is
JsonResource::make($paginatedDataCollection), metadata extraction reaches the unsupported-value
exception instead of the underlying paginator.
Agent Prompt
## Issue description
A JSON resource wrapping a paginated Data collection is unwrapped only once, leaving a Data collection where a paginator is required for infinite-scroll metadata.

## Fix Focus Areas
- src/inertia/src/ScrollMetadata.php[37-43]

## Recommended Fix
Unwrap a `JsonResource` first, then independently unwrap a Data paginated collection through `items()`. Add a test for a paginated Data collection wrapped in `JsonResource`.

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

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 paginated data collection is already a response resource, so it's passed to infinite scroll directly. Wrapping it in a JsonResource isn't a supported combination.

Comment on lines +28 to +33
// An unresolvable binding must fail; a null rule parameter would make database constraints check the wrong rows.
$dependency = Container::getInstance()->make($this->dependency, $this->parameters);

if ($this->property !== null) {
return data_get($dependency, $this->property);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

2. Database rules can match the wrong rows 🐞 Bug ≡ Correctness

ContainerReference::getValue() returns data_get($dependency, $this->property) unchecked, so a
property that is missing or null on the resolved service comes back as null. A WhereConstraint
given that reference passes null to DatabaseRule::where(), which turns it into
whereNull($column). The exists/unique check then runs against rows where the column is NULL,
the same wrong-row outcome the new code's own comment says this reference must prevent.
Agent Prompt
## Issue description
`ContainerReference::getValue()` returns `null` when the configured property is missing or null on the resolved dependency. When that value reaches a database `where` constraint, `DatabaseRule::where()` turns it into `whereNull`, so `exists`/`unique` validation checks the wrong rows.

## Fix Focus Areas
- src/data/src/Support/Validation/References/ContainerReference.php[26-36]
- src/data/src/Exceptions/CannotResolveRouteParameterReference.php[1-28]

## Recommended Fix
Do what `RouteParameterReference` does: when `$this->property !== null` and `data_get($dependency, $this->property)` returns `null`, throw a descriptive exception (for example, a new `CannotResolveContainerReference::propertyNotFound($dependency, $property)`). Optionally add a `nullable` constructor flag for callers who really want `null`. Add a test showing that a missing property throws.

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

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.

Keeping this. A property that is null is a real value, and the database rule turns it into a null check, which is what that value means. data_get() can't tell a missing property from one that is null, so throwing here would also reject valid null values. A container binding that can't be resolved does throw, and the comment now explains why.

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.
Comment thread src/data/src/Support/Validation/RuleNormalizer.php Outdated
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.
@binaryfire
binaryfire merged commit 90a9c26 into 0.4 Oct 5, 2026
53 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