cuyz/valinor
Valinor maps raw inputs like JSON and plain arrays into strongly typed PHP objects, validating every value along the way. It supports native types plus PHPStan and Psalm advanced types, gives precise human-readable errors, and can normalize objects back into formats like JSON or CSV.
Architecture fit
Illuminate\Validation is rule-based, operates on arrays, and produces messages shaped for form UX. Valinor derives its contract from PHP type declarations plus PHPStan/Psalm docblocks (list<City>, shaped arrays, generics, integer ranges) and fails construction on violation. The TPM framing must be: Laravel validation for HTTP semantics and user-facing messages; Valinor for constructing guaranteed-valid domain objects at non-HTTP boundaries and for typed internal contracts.spatie/laravel-data. If the team already standardizes on laravel-data, adopting Valinor in parallel creates two competing hydration idioms and a permanent "which one?" decision tax. This is a build-vs-buy call that should be made once, in an ADR, before any code lands.Integration feasibility
composer require cuyz/valinor, bind a configured Mapper (and optionally Normalizer) as singletons in a service provider, add a config/valinor.php for strictness flags (allowScalarValueCasting, the newer per-type allowCastingToInteger() etc.), cache path, and custom constructors.MapFromKey, MapWithPrefix, MapAsInt/MapAsBool, MapExplodedStringToList (query params, CSV columns), MapArrayToList (sparse/associative → list), MapFromJson (JSON-in-a-column), MapToDateTimeFromFormat. These are the exact shapes Laravel forms and legacy integrations throw at you.MappingError must be adapted into a Laravel ValidationException (or a domain exception) so the API's existing 422 contract and error envelope are unchanged. Write that adapter once, in the platform layer.NormalizeKeyTo, NormalizeToSingleValue, IgnoreOnNormalization) is viable for outbound payloads — notably IgnoreOnNormalization for passwords/secrets — and is a credible partial substitute for hand-written API Resources where the output is simple.Technical risk
dependents in the supplied metadata while the repo shows 1.5k stars and active releases almost certainly reflects the measuring index (e.g., a specific dependent-graph source), not real usage. Validate with Packagist download counts and a GitHub issue/PR responsiveness check before treating it as a risk signal.app/Domain and app/Data, plus mapper-level type checks at boot in tests.Key questions
spatie/laravel-data, and if so, what specific gap justifies a second hydration library?composer.json constraint cover all of them (including the oldest LTS still in the matrix)?Stack fit
composer.json before the ADR is finalized, particularly if any service is still on PHP 8.0.CuyZ\Valinor\Mapper (built from a configured MapperBuilder) as a singleton in a dedicated ValinorServiceProvider, driven by config/valinor.php. Keep strictness settings centralized — never let individual call sites call new MapperBuilder().MapperBuilder configurators (configureWith(...)) and normalizer configurators in the same provider so the policy is one file, one review.app/Data or app/Domain/*/Data), are final readonly, are not Eloquent models, and are constructed only via the mapper.Migration path (strangler, not big bang)
Arr::get + validator, or laravel-data). Output: ADR with a go/no-go and a documented fallback (fork/vendored copy) if the project stalls.MappingError → ValidationException adapter, and base test helpers (assertMappingFails()).list<T>, array shapes, integer ranges), naming, where configurators are allowed vs. forbidden, how errors are wrapped.Compatibility
#[MapFromKey] / #[MapWithPrefix] are useful for bridging legacy snake_case or prefixed columns without polluting domain property names — but treat them as adapters to be removed when upstream naming is fixed.Sequencing constraints
Maintenance
^2.6) and upgrade deliberately (quarterly cadence suggested). Because configurators are attribute-based and attributes appear in domain code, minor upgrades can require touch-ups across DTOs — budget a small upgrade task per cycle rather than assuming zero-cost.Support
Scaling
Failure modes
MappingError → 500. Highest-probability incident; mitigated by the single global adapter plus a handler-level safety net.allowScalarValueCasting() or blanket allowCastingTo* can mask upstream data bugs and turn "wrong" into "quietly coerced." Prefer per-property configurator attributes over global relaxation.Ramp-up
MappingError, and PHPStan error count held at zero in the DTO namespace.How can I help you explore Laravel packages today?