Tags: std-out/simple-data-objects
Tags
feat: add fromResult()/fromValidatedResult() — never-throw error accu… …mulation Safe-parse alternative to from()/tryFrom(): tries every parameter and collects every failure into a HydrationResult instead of aborting on the first one, with dot-path keys for nested BaseData/#[DataCollection] errors. Compiled and cached separately from the hydration hot path, so from()/tryFrom() pay nothing for it.
feat: add #[Computed] — derived, method-backed fields in serialization
- Public, non-static, zero-required-parameter methods only; a method requiring parameters throws at metadata-build time.
- Default output key is the method name, subject to the class-level #[TransformKeys] strategy; override with #[Computed('custom_key')].
- Return value goes through the same ValueNormalizer as any other field — nested BaseData, enums, and Collections serialize correctly.
- Ignored on input, so from(toArray()) keeps working; #[RejectUnknownKeys] treats the computed key as known.
- Works on constructor-based, constructor-less, and hybrid classes.
feat: add MapPropertyName aliases + #[MapInputName]/#[MapOutputName] - #[MapPropertyName] now accepts multiple input keys; first one present in the data wins on hydration, first alias still wins on output. - New #[MapInputName]/#[MapOutputName] map hydration and serialization keys independently (aliases supported on the input side too). Cannot be combined with #[MapPropertyName] on the same property. - from(toArray()) roundtrip stays intact: whenever the output key diverges from the declared input names, it's auto-accepted as a fallback input alias. - #[RejectUnknownKeys] treats every alias and the output key as known.
feat: add pagination envelope + #[WrapIn] response wrapping
HasLaravelIntegration::paginatedCollection() wraps a Laravel paginator into {"data":[...],"meta":{...},"links":{...}}, hydrating items through the existing compiled hydrator (TypedDataCollection::of()). No new hard dependency: PaginatedDataCollection type-hints only Illuminate\Contracts\Pagination\LengthAwarePaginator, already covered by illuminate/contracts; illuminate/pagination stays require-dev.
#[WrapIn] wraps a single object's toResponse() payload under a key without touching toArray()/toJson() — from(toArray()) still round-trips. toResponse() also gains status/headers parameters.
feat: add #[InferRules] — auto-infer validation rules from property t… …ypes Opt-in per class: ClassMetaFactory derives `required`/`nullable` + a type rule (string/integer/numeric/boolean/array/Rule::enum) from each property's PHP type, and cascades dot-notation rules into nested BaseData/DataCollection fields. #[Rules] still replaces the inferred rule by default, or appends to it with `merge: true`. Self-referential and cyclic BaseData graphs are guarded against infinite recursion.
- **Dropped support for Laravel 10 and 11.** - Both major versions are now fully blocked by Composer's security-advisory policy. - Every `laravel/framework` release on the 10.x and 11.x lines (including the newest patches) is currently flagged by Packagist's advisory database. - As a result, Composer 2.9+ refuses to install them regardless of the requested patch. - **Updated minimum requirements:** The minimum supported Laravel range is now 12.x – 13.x.
Add #[RejectUnknownKeys]: strict-mode input validation Hydration now throws DataHydrationException when the input contains a key the class doesn't recognize, instead of silently ignoring it — the PHP equivalent of serde's deny_unknown_fields or zod's .strict(). A key is "known" if it matches a parameter's input name, i.e. after #[MapPropertyName]/#[TransformKeys] has been applied, not the raw PHP property name. The check compiles directly into the generated hydrator as a var_export'd known-keys array plus one array_diff_key() call, so classes that don't use the attribute get byte-identical generated code (confirmed by diffing HydratorCompiler's output before/after, and via the benchmark suite). It runs against the caller's raw input before any #[Pipe] transformation and before individual fields are checked for missing values, and is wired into all three hydration entry points: from(), fromLazy(), and the constructor-less/hybrid populate path. DataHydrationException gained a $unknownKeys array field instead of a new exception subclass — the class is final and every other hydration error is already a static factory on it, so a field keeps this one consistent with the codebase rather than introducing a parallel exception hierarchy. Rejected at metadata-build time when combined with #[Flatten] (a flattened nested DTO consumes keys the parent has no fixed set to check against) or #[Discriminator] (a dispatcher class never hydrates itself, so it has nothing to check).
Add WireableData trait: Livewire integration decoupled from livewire/… …livewire WireableData adds toLivewire()/fromLivewire(), delegating to the existing toArray()/from() round trip so casts (enums, DateTimeCast, custom casts, ...) apply the same way they do everywhere else — a data object can be used directly as a public Livewire component property. Fully decoupled, matching HasLaravelIntegration: the package has no dependency on livewire/livewire, and the trait does not implement \Livewire\Wireable itself — that interface is untyped, so the trait's methods already satisfy it structurally without referencing the class at all. The consuming class adds `implements \Livewire\Wireable` on its own, which is the only place livewire/livewire needs to be installed. Tested without a real livewire/livewire dependency: tests/Fixtures/FakeLivewireWireable.php mirrors the interface's exact (untyped) method shape, and a fixture genuinely `implements` it — a PHP compile error would surface immediately if the trait ever drifted out of structural compatibility. Adds docs/features/livewire.md and a composer.json suggest entry.
Add #[Discriminator]: polymorphic hydration for abstract data classes An abstract BaseData class can now map a discriminator field's value to concrete subclasses: PaymentMethodData::from(['type' => 'card', ...]) returns a CardPaymentData. Resolution happens inside the compiled hydrator as a single array lookup — no reflection at runtime — and the target class's own cached hydrator takes over from there. Every entry point dispatches: from(), tryFrom(), fromJson(), collection(), lazyCollection(), and nested/collection properties typed as the abstract base. fromLazy() resolves the concrete class eagerly (a lazy ghost's class is fixed at creation) while hydration itself stays deferred. fromValidated() and validate() delegate first so the concrete subclass's own #[Rules] apply, not the abstract base's. Supports string and integer map keys, BackedEnum input values, an optional fallback class for missing/unmapped values, and multi-level hierarchies where a map target is itself a #[Discriminator] class. Misconfiguration — a non-abstract class, an empty map, an unknown or foreign target class, an abstract target without its own #[Discriminator] — is rejected at metadata-build time, never on a live hydration. The compiled dispatcher persists through the metadata file cache like any other hydrator. Confirmed no regression on the existing benchmark suite (hydration/serialization numbers unchanged); the dispatch step itself costs one extra from() call on top of direct hydration.
PreviousNext