Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Valinor Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

Architecture fit

  • Valinor is a dependency-free, framework-agnostic PHP library that does exactly two things: hydrate + validate raw input (arrays, JSON, CSV-derived data) into typed objects, and normalize objects back into array/JSON/CSV. It has no framework coupling, no container assumptions, and no runtime dependencies — it is a boundary library, not an application framework.
  • In a Laravel codebase its natural architectural slot is the anti-corruption layer at the edges: HTTP request payloads, webhook bodies, third-party API responses, queue job payloads, console command input, and JSON config/feature-flag hydration. It does not touch Eloquent, the container, routing, or the HTTP kernel, so it can be adopted per-boundary rather than globally.
  • It is not a replacement for Laravel's validator. 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.
  • There is a direct overlap with 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

  • Integration cost is genuinely low: 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.
  • The 2.6.0 configurator set is what makes it practical for messy Laravel input rather than only pristine JSON: 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.
  • Error handling is the only real wiring work: 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.
  • The normalizer side (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.
  • Real feasibility risks: the type contract lives in docblocks, so correctness depends on PHPStan/Psalm actually running in CI; the mapper's reflection/metadata compilation needs caching in production; and PHP version support must be verified against your Laravel version matrix before committing.

Technical risk

  • Bus factor / governance (medium-high). Effectively a single-maintainer project. MIT and zero dependencies mean the worst case is a fork, not a dead end — but that contingency should be acknowledged, not assumed away.
  • Adoption signal ambiguity (low-medium). 0 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.
  • Docblock-as-contract (medium). Types expressed in comments degrade silently if static analysis is not enforced. Mitigation: a PHPStan level gate on app/Domain and app/Data, plus mapper-level type checks at boot in tests.
  • Two validation systems (medium). Coexistence with FormRequests and/or laravel-data creates duplicated rules and divergent error messages if not governed.
  • Performance characteristics (low-medium). Mapping is CPU-bound metadata + reflection work at request boundaries; the library advertises performance work (Blackfire) and supports compilation caching, but cache warmth and invalidation on deploy become your operational responsibility.
  • Error-message coupling (low). Valinor's messages are human-readable and precise but not in Laravel's format; if they leak to API clients, you inherit a second message contract.

Key questions

  1. What is the primary target — inbound ingestion (webhooks, third-party APIs, queue payloads), outbound normalization, or replacing hand-rolled DTO hydration? The answer changes the sequencing entirely.
  2. Do we already use spatie/laravel-data, and if so, what specific gap justifies a second hydration library?
  3. What PHP and Laravel versions are in support, and does Valinor's composer.json constraint cover all of them (including the oldest LTS still in the matrix)?
  4. What is the CI static-analysis baseline today, and is the team willing to raise PHPStan strictness on the boundary namespaces?
  5. Is there an existing owner/team for the "validation & serialization platform" surface, or does this create a new orphan dependency?
  6. What is the acceptable error-message contract — internal-only, or surfaced to API consumers?
  7. What are the p95 latency budgets on the endpoints we'd instrument, and what's the cost of a 1–3 ms mapping step relative to DB time?

Integration Approach

Stack fit

  • Laravel 10+/PHP 8.1+ is the assumed baseline; confirm the exact constraint from composer.json before the ADR is finalized, particularly if any service is still on PHP 8.0.
  • Bind 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().
  • Register MapperBuilder configurators (configureWith(...)) and normalizer configurators in the same provider so the policy is one file, one review.
  • DTOs live in a dedicated namespace (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)

  1. Spike + ADR (1 week). One engineer, one real payload, compare against the current approach (manual Arr::get + validator, or laravel-data). Output: ADR with a go/no-go and a documented fallback (fork/vendored copy) if the project stalls.
  2. Platform plumbing. Service provider, config file, mapper cache directory + cache clear in the deploy pipeline, a MappingError → ValidationException adapter, and base test helpers (assertMappingFails()).
  3. Pilot on a single low-blast-radius boundary — ideally an inbound webhook or a read-only third-party API client, not a user-facing checkout flow.
  4. Conventions. Publish a short DTO style guide: docblock type syntax (list<T>, array shapes, integer ranges), naming, where configurators are allowed vs. forbidden, how errors are wrapped.
  5. Expand boundary by boundary — queue job payloads, console input, config hydration — each as its own PR with a test asserting invalid input is rejected.
  6. Evaluate the normalizer for outbound payloads only after inbound is proven; do not migrate API Resources in the same release.
  7. Deprecate hand-rolled hydrators as each boundary moves, so the codebase never carries two live idioms for one path.

Compatibility

  • No conflict with Eloquent, the container, or the HTTP kernel; the DTO layer sits between the request and the domain.
  • If constructor injection is used inside mapped classes, prefer plain constructors over container-resolved services — mapped classes should be data, not service locators.
  • Watch for double validation (FormRequest rules and Valinor) on the same endpoint; assign each concern explicitly: FormRequest owns HTTP-facing rules and messages, Valinor owns object invariants.
  • #[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

  • Cache warmup and cache clearing must land with the first production use, not after.
  • PHPStan strictness on the DTO namespace must land before DTOs proliferate, otherwise retrofitting types is expensive.
  • The error adapter must land before the first user-facing endpoint, or you will accidentally ship Valinor's message format to clients.

Operational Impact

Maintenance

  • MIT, zero runtime dependencies, semver 2.x with a regular release cadence — low ongoing maintenance burden and a small supply-chain surface.
  • Pin to a minor (^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.
  • Single-maintainer concentration means you should keep the integration shallow and non-exotic (no deep reliance on undocumented internals) so a future fork is a version constraint change, not a rewrite.

Support

  • No commercial support; community via GitHub issues. Internal support model: one named owner for the "typed boundary" surface, an ADR, a style guide, and a documented error-wrapping convention. Without that, the library becomes tribal knowledge.
  • Failure diagnosis is friendly by design — Valinor's error messages are precise and path-aware, which measurably reduces the "why did this payload explode?" triage time compared to manual array plumbing.

Scaling

  • Mapping cost is CPU at the boundary. Enable the mapper definition cache, warm it during deploy, and clear it as an explicit release step; stale caches after deploy are the most likely scaling-adjacent incident.
  • Memory scales with payload size because objects are materialized eagerly. Enforce pagination/batch limits on list endpoints and prefer streaming for large normalization jobs rather than building full arrays in memory.
  • Instrument p95 latency on the pilot endpoints before and after, using the same APM you already run (Blackfire is the library's own reference tool). A 1–3 ms envelope per request is the expectation to validate, not assume.
  • Horizontal scaling is unaffected — the library is stateless; the only shared state is the cache directory, which must be per-release/immutable in containerized deploys.

Failure modes

  • Uncaught MappingError → 500. Highest-probability incident; mitigated by the single global adapter plus a handler-level safety net.
  • Silent contract drift. A docblock changed without a test still "works" until a payload shape changes; mitigated by PHPStan gating and explicit invalid-input tests per boundary.
  • Over-permissive casting config. allowScalarValueCasting() or blanket allowCastingTo* can mask upstream data bugs and turn "wrong" into "quietly coerced." Prefer per-property configurator attributes over global relaxation.
  • Cache staleness after deploy → wrong type definitions in production; own it in the release runbook.
  • Large payload OOM on bulk import/webhook fan-in endpoints.
  • Message leakage of Valinor's error text into public API responses.

Ramp-up

  • A mid-level Laravel developer is productive with basic object mapping in roughly half a day to a day; fluency with shaped arrays, generics, integer ranges, and configurator selection is closer to a week.
  • The real learning curve is not the API — it's thinking in types. Teams accustomed to defensive array access take time to trust that an object which exists is an object that is valid.
  • Budget: a 60–90 minute internal walkthrough, a one-page DTO style guide checked into the repo, and 2–3 pilot PRs with review. That investment is what keeps the adoption from stalling at "one team uses it in one place."
  • Success measures for the first quarter: number of inbound boundaries with typed DTOs, count of hand-rolled hydrators deleted, mapping-error rate at the pilot boundary (should be near-zero in steady state and visible when upstream changes), zero 500s attributable to MappingError, and PHPStan error count held at zero in the DTO namespace.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
roundly-consulting/kubernetes-api-for-laravel
roundly-consulting/plausible-for-laravel
gvp/gvp-laravel-template
metasyncsite/laravel-metasync-client
roundly-consulting/onboarding-for-laravel
andydefer/laravel-notification
adachsoft/cli-process-tester
jhaskell13/laraflow
sebastienheyd/boilerplate-media-manager
ccast/tagixo-primix
pretendtrue/laravel-builder
sawmainek/laravel-scout-tntsearch-driver
tomshaw/laravel-calendar-table
dibakar/laravel-ownership
steadrun/laravel-monitor
labyrinthe/connect-laravel
elmogy/layan
drewdan/sentinel-client
dskripchenko/laravel-llm
fatryst/nuonuo