Repository navigation
Complete the Data package API and fix creation, validation and output defects - #53
Conversation
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.
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configuration
You can disable this status message by setting the Use the checkbox below for a quick retry:
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. Comment |
PR Summary by QodoComplete the Data API and fix creation, validation, and output defects
AI Description
Diagram
High-Level Assessment
Files changed (268)
|
|
Code Review by Qodo
1. Wrapped data pages break infinite scroll
|
| $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(), |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
| // 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); | ||
| } |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
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.
This brings Hypervel's Data package in line with the current
spatie/laravel-dataAPI 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'forint $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,AuthenticatedUserReferenceandContainerReferencefor validation attributes. An unresolvable container binding throws instead of resolving tonull, which could otherwise turn a database rule's constraint intowhereNull.#[Rule]normalization. A#[Rule]attribute's rules become typed validation attributes, so#[Rule('required|string')]replaces the inferredrequiredandstringrules 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|.rule_inferrers,ignore_invalid_partials,throw_when_max_transformation_depth_reached,var_dumper_caster_modeand computed-property exception options, andmake:datanamespace and suffix defaults. Rule inferrers are resolved for each compilation, so a scoped inferrer follows the current coroutine.prepareForPipeline(),SerializeTransformer,withOptionalValues()andwithoutOptionalValues(),defaultWrap(),factory()accepting aCreationContext, and the optionalFormRequestNormalizer.{data, links, meta}shape intoArray(),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
confirmedalways failed and rules such asrequired_ifsilently passed. Validated output still excludes undeclared keys.'123'becomes123for anintproperty, while'abc'fails with aTypeError. Contextual values such as#[RouteParameter('id')] int $idare converted the same way.from($request)cannot set an object's internal state.getAttribute()are read, and columns that were not selected are treated as missing.FailOnUnknownFieldschecks the normalized source, so a custom normalizer's envelope input is no longer rejected.collect(null, $into)returns an empty target, and a collection class's own@extendsannotation gives a property its item type.Validation
Enumrule.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 fromrules()are passed to the validator as written.TypeError. A missing ornullrequired data object now compiles its children's rules.integer:strict, keep the rule as written;AcceptedIf,DeclinedIfandExcludeIfno longer turn'1'into'true'; and the date attributes no longer throw aTypeErrorfor a parseable date.Output
Optionalis omitted, and nested data collections in responses keep their own wrapper.TypeErrorwhen 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.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
Arrayablevalues, and the package's internal enums are string-backed so a creation failure's trace can be JSON-encoded.Framework changes
BoundMethod, the controller and callable dispatchers, andController::callAction()call application code through a small invoker that leaves outstrict_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 tobuildWith(). Variadic parameters are left for the build to resolve, because an override is passed as a single argument.#[Give]accepts a property path, likeRouteParameterandCurrentUser.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:
nullattributes staynull.200for every method; set201inwithResponse()when something was created.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.yamlrecords 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,
FacadeDocblocksTestand 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-dataand 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
RouteParameterReference,AuthenticatedUserReference,ContainerReference,#[Rule]normalization, configurablerule_inferrers,withOptionalValues()/withoutOptionalValues(),defaultWrap(),SerializeTransformer,prepareForPipeline(), theFormRequestNormalizer, andmake:datanamespace and suffix defaults.confirmedworks and validation and construction read the same field; validated output still excludes undeclared keys.'123'becomes anint,'abc'raisesTypeError— including collection items and contextual attribute values.Enum, and conditional references resolve through mapped input names. Rule strings split on pipes like Laravel's parser unless they start withregex:ornot_regex:.{data, links, meta}shape intoArray(),toJson(), and responses.Framework changes and intentional differences
'123'forint $id;Container::resolveContextualParameters()resolves contextual attribute values without building their class and skips variadic parameters, and#[Give]accepts a property path.Arrayablevalues.200for every method — set201inwithResponse()after creating something — and contextual attribute values always win over payload input.Written for commit 9b8fe6a. Summary will update on new commits.
Note
Complete Data package creation, validation, and transformation pipeline
ConstructionStateand resolved through the newCreationExtensionsregistry in DataCreator.phpRuleNormalizer/RuleDenormalizer, field references resolve through mapped input names, configured rule inferrers can add or remove rules, and preserved values restore from validator dataCannotPerformPartialOnDataFieldContainer::resolveContextualParameters,NativeInvokerweak-typed construction, andGiveattribute property-path extractionmake:datanamespace/suffix defaultsConstructionState(Castimplementations must update);DataTypeFactory::buildFromString,InvalidDataDeclaration::nonPublicPromotedProperty,CannotFindDataClass::forTypeable, and severalCannotCastDatafactories were removed; scalar coercion now follows PHP weak typing so non-numeric strings raiseTypeErrorMacroscope summarized 036bb01.