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

Object Mapper Laravel Package

symfony/object-mapper

Symfony Object Mapper maps one object to another using PHP attributes, giving you a clean, declarative way to handle DTOs and transformations without boilerplate. Part of the Symfony ecosystem.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

Architecture fit

symfony/object-mapper is a first-party Symfony component whose job is narrow and well-bounded: given a source object, produce a target object, driven by PHP attributes (MapFrom/MapTo, conditions, transforms, class-level transforms, collection embedding). It is a domain-neutral mapping kernel, not an integration layer. That is architecturally clean but means it brings no Laravel opinions whatsoever — no service provider, no config file, no facade, no Eloquent awareness.

For a Laravel codebase the fit is therefore "good primitive, wrong ecosystem." The value proposition — removing hand-written User::fromModel($model) boilerplate between Eloquent models, domain objects, and API resources — is real, but the component's happy path assumes Symfony's DI compiler pass (registerAttributeForAutoconfiguration) to discover transforms and register mapping metadata. Laravel's container does not run that compiler. You will be hand-constructing MappingConfiguration / the mapper instance in a service provider and manually registering every transformer and condition callback. That is a one-time ~50–150 line integration, but it is our code to own and it is the part the docs do not cover.

The second architectural gap is the source side. Eloquent models expose attributes through __get/__isset magic and attribute casting, not declared properties or typed getters. Mapping from an Eloquent model is where I expect the sharp edges (uninitialized/lazy values, Carbon casting, appended accessors). Mapping between plain DTOs and domain objects — the actual DTO pattern — is well within scope and low risk.

Integration feasibility

Feasible in a day for a competent engineer, with three concrete constraints:

  1. Branch/version selection drives PHP floor. Symfony 8.x requires PHP ≥ 8.4; Symfony 7.4.x targets PHP 8.2/8.3. Most Laravel 11/12 estates sit on 8.2–8.3, so we would consume the 7.4 line, which is still receiving backported bugfixes (7.4.4–7.4.9 releases are in the evidence). Confirm this explicitly before committing — and note that the newest fixes are landing on 8.x first (see below), so 7.4 backport timing is now an explicit diligence item, not an assumption.
  2. No Laravel-native docs, tests, or examples. The component is documented in the Symfony docs tree only. Every integration question (container wiring, where to hook bootstrapping, how to cache metadata under Octane) is undocumented for our stack.
  3. Metadata/attribute discovery is the unknown. Recent changelogs show fixes in exactly this area — nested classes with promoted readonly properties, embedded objects via property access, bypassing lazy ghosts on class transform, applying conditions to constructor arguments, and now (v8.1.8) constructing nested lazy-ghost targets when the mapper is decorated. Those are Doctrine-flavored concerns, but they signal the mapper's object-graph traversal is still being hardened — and the latest one lands squarely in the wrapping pattern we recommend.

Technical risk

  • Low governance risk. MIT, Symfony core team, security process, predictable release train, long support windows. This is not an abandoned package.
  • Medium integration risk. Zero dependents and 36 stars are not meaningful adoption signals — this repository is a read-only subtree split of symfony/symfony, so stars and issue activity accrue to the monorepo. Do not use these metrics to judge traction; equally, do not use them to claim traction. We have no visibility into how many production Laravel apps run this, because almost certainly very few do.
  • Medium maturity risk (unchanged by this release). The component only left experimental status in the 7.4/8.0 cycle. The changelog remains dense with behavioral bugfixes (skip reading uninitialized values, fix mapping for missing source properties, handle non existing property errors, initialize lazy objects). The move from 8.1.0-BETA3 (our prior baseline) to a stable 8.1.8 inside a single patch cadence tells us two things: the 8.x branch is now stable and in patch maintenance (good — the beta risk we priced in a release ago has retired), and it is still shipping behavioral fixes rather than only cosmetic ones (the ongoing tax). A wrong mapping is often a silently null field, not an exception, so an active bugfix cadence translates to "pin patches, watch releases."
  • Low-to-medium risk concentration on the decoration pattern. v8.1.8 fixes a defect that only manifests when the mapper is decorated. Our recommended integration is exactly that: our own App\Mapping\ObjectMapper interface wrapping the vendor mapper. This is not a reason to abandon wrapping — wrapping remains the single most important design decision here — but it does mean the wrapper configuration is a known-bug surface, and we must confirm whether this fix is backported to the 7.4 line we would actually run.
  • High opportunity cost vs. incumbent. spatie/laravel-data already owns this problem space in Laravel, with validation, casts, resource transformation, Eloquent factories, and an ecosystem of community answers. Choosing the Symfony component means choosing a thinner feature set in exchange for upstream neutrality.

Key questions to resolve before a decision

  1. Which PHP version do our target apps run, and does that force the 7.4 branch — and is 7.4 still receiving fix backports at the cadence we need? Specifically: is the v8.1.8 decorated-mapper/lazy-ghost fix backported to 7.4.x, and if not, does our integration exercise that path?
  2. Is our primary pain Eloquent → DTO or DTO ↔ domain object? If the former, we are outside the component's best-supported path and should prototype against real magic-attribute models before estimating.
  3. Does spatie/laravel-data already satisfy 80% of the use case? If so, what specifically fails — nested transformation, conditional mapping, or performance?
  4. Do we run Octane/Swoole? If yes, we must validate that attribute metadata caching is per-worker-safe and does not leak between requests.
  5. Who owns the integration shim (service provider, transformer registration, error handling)? It is not vendor-supported.
  6. If we adopt, what is our PHP 8.4 trigger date? The 8.x line is now the actively-patched branch; staying on 7.4 indefinitely means progressively consuming backports or frozen behavior.

Integration Approach

Stack fit

The component fits a Laravel app only as a library inside a hexagonal boundary, not as infrastructure. Wrap it: App\Mapping\ObjectMapper interface (our own), with an implementation delegating to Symfony\Component\ObjectMapper\ObjectMapper. Consumers depend on our interface. This costs ~20 lines and preserves the ability to swap to spatie/laravel-data, jms/serializer, or hand-rolled constructors without touching call sites — the single most important decision here given the maturity profile. Note that v8.1.8's fix directly concerns decorated mappers, so our wrapper should be as thin as possible: delegation, not composition of behavior, so we stay on the vendor's tested path.

Recommended scope for a first adoption: DTO assembly at the edges (HTTP response DTOs from domain objects, request payloads → command objects). Avoid mapping from Eloquent models and avoid conditional/multi-target mapping on day one — those are precisely the surfaces with the most recent bugfixes.

Migration path

Incremental and reversible, because the mapper is a pure function of two objects. There is no schema change, no data migration, no persistence coupling. A mapper can be introduced and removed per-class without cross-cutting impact. That property is what makes a time-boxed spike cheap: worst case we delete a branch.

Compatibility

  • PHP: 7.4 line for PHP 8.2/8.3; 8.x line only on PHP ≥ 8.4 (8.1.8 and its predecessors are PHP 8.4+). This is a hard gate, and it is the gate this release does not move.
  • Symfony packages: the component pulls Symfony contracts/DI-adjacent dependencies. Laravel already ships several Symfony components (http-foundation, console, process, var-dumper, error-handler, finder, mime), so version alignment with Laravel's pinned Symfony ranges must be verified with composer why-not. Conflict risk is moderate and detectable at install time.
  • Laravel: no official support statement; treat as unsupported-by-vendor.

Sequencing

  1. Spike (2–3 days): install on the 7.4 branch, wire the service provider, map one real domain object → one response DTO with a nested collection (nested/decorated paths are where the fixes keep landing). Record what the wiring actually took.
  2. Benchmark against the incumbent (1 day): same slice in spatie/laravel-data. Compare LOC, error clarity, and runtime cost.
  3. Vertical slice in production code (1 sprint): one bounded context, behind our interface, with tests asserting field-level equality rather than object equality.
  4. Decision gate: expand, or revert to the incumbent. Define the gate criteria up front (e.g., boilerplate reduction ≥ 50% in the slice, no unhandled failure mode in a week of staging).
  5. Broader rollout only after the gate, context by context, never as a big-bang refactor.

Operational Impact

Maintenance

Ongoing cost is low for the dependency itself and non-zero for our shim. The vendor maintains the component; we maintain the container wiring, the transformer registrations, and the abstraction layer. Budget roughly 1–2 engineer-days per Symfony minor release to validate our integration against changelog deltas — a real but modest tax, and one that only exists because we are outside the default framework path. Pin to ~7.4.0 and consume patches deliberately rather than floating. Because the 7.4 line is now downstream of 8.x fixes, add a standing quarterly check: is a fix we need stranded on 8.x? That shifts a little effort from "read the changelog" to "read two changelogs."

Support

Support is upstream-only and indirect: issues are filed against the symfony/symfony monorepo, not this repository, and there is no Laravel community corpus to search. When a mapping misbehaves, our engineers will be reading Symfony source. That is acceptable for a team comfortable with framework internals and a poor fit for a team that is not — the internal support burden is the real cost driver here, and it should be stated plainly to stakeholders rather than buried.

Scaling

Mapping is reflection- and attribute-driven rather than code-generated. The 7.4/8.0 line added in-memory attribute caching, which mitigates per-request reflection cost. Expect this to be adequate for typical API workloads and to be a poor fit for hot loops over large collections — mapping thousands of rows through the mapper per request will show up in profiles where hand-written constructors would not. Guidance: map at the boundary (one DTO per request/record), never inside a per-row batch loop without measuring. Verify behavior under Octane, where a long-lived worker holds whatever caches the component builds.

Failure modes

Ranked by likelihood × impact:

Failure mode Signal Mitigation
Silent null / unpopulated target field Wrong data in API response, no exception Field-level assertions in tests; no "map and trust"
Nested target not constructed under a decorated/wrapped mapper Missing nested object or 500 on nested DTOs Fixed in v8.1.8 on 8.x — confirm 7.4 backport; keep wrapper thin; test nested collections explicitly in the spike
Uninitialized typed property error 500 on partial source objects Only map fully-hydrated sources; avoid Eloquent magic
Constructor argument conditions skipped Subtle wrong-value bugs Recent fix landed; pin ≥ 7.4.5 and test constructor paths
Circular object graphs Recursion / memory blowup Explicit depth limits; forbid bidirectional mappings
Type coercion failure (string → DateTime/Carbon) Mapping exception mid-request Explicit transformers, not implicit conversion
Lazy-load explosion on Eloquent sources Latency/N+1 regression Do not map from models; eager-load and map from DTOs
Shim breakage on Symfony upgrade Fatal at boot Version pin + upgrade checklist

Ramp-up

Conceptual ramp-up is genuinely low — attributes are readable, and a mid-level engineer can write a mapping in minutes. Debugging ramp-up is where the cost sits: when a mapping fails, the stack trace runs through reflection and metadata resolution, and the failure often presents as a missing value rather than a clear error. Countermeasures: a team-internal mapping test convention (assert every target field explicitly), a short internal doc capturing the service-provider wiring, and a named owner for the integration layer.

Bottom line for the roadmap: v8.1.8 is a patch-level bugfix with no breaking changes, no new features, and no deprecations — it does not move the decision. It does, however, sharpen the picture in two ways: the 8.x line has graduated from the beta we assessed a release ago into stable patch maintenance (modestly reducing the "young component" risk and giving us a credible PHP 8.4 destination), while the nature of the fixes remains behavioral and now touches the decorated-mapper topology our integration would use. The verdict stands: a technically credible, well-governed component with a clean abstraction story, but an ecosystem-foreign dependency for a Laravel estate, adopted against a mature Laravel-native incumbent, with a hardening bugfix cadence and no vendor support for our stack. Justify it on upstream neutrality and Symfony-alignment if those are actual strategic goals; do not justify it on stars, adoption, or a boilerplate-reduction claim without the spike and the head-to-head benchmark behind it. Approve as a time-boxed spike with a defined revert path, not as a standard — and add the 7.4-backport question to the spike's exit criteria.

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.
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
stevenay/myanfont
gmostafa/php-graphql-client
xeviant/paystack
snowsoft/faker
meditationsteps/blogetc