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

Getting Started

symfony/object-mapper is a small, framework-agnostic, attribute-driven object-to-object mapper. In a Laravel app it is a library, not a Laravel package: there is no service provider, facade, config file, validation layer, or auto-discovery. Treat it as a lower-level tool you wire yourself.

Install a version compatible with your PHP runtime. The 8.x line requires PHP 8.4; 7.4 is the safer choice for typical Laravel apps on PHP 8.2/8.3. Pin a recent patch — bugfixes land frequently and the early patches of each minor were rough:

composer require symfony/object-mapper:^7.4
# or, on PHP 8.4:
composer require symfony/object-mapper:^8.1.8

Then bind the mapper as a singleton. The constructor is small but has changed across versions, so verify vendor/symfony/object-mapper/ObjectMapper.php after install:

// app/Providers/AppServiceProvider.php

use Symfony\Component\ObjectMapper\ObjectMapper;
use Symfony\Component\ObjectMapper\ObjectMapperInterface;
use Symfony\Component\ObjectMapper\Metadata\ClassMetadataFactory;

public function register(): void
{
    $this->app->singleton(ObjectMapperInterface::class, function () {
        // Check the installed constructor; this is the common shape.
        return new ObjectMapper(new ClassMetadataFactory());
    });
}

First use case: replace hand-written API DTO construction in a controller.

use Symfony\Component\ObjectMapper\Attribute\Map;

final class UserData
{
    public int $id;

    #[Map(source: 'firstName')]
    public string $givenName;

    #[Map(source: 'lastName')]
    public string $familyName;
}

// Controller
public function show(User $user, ObjectMapperInterface $mapper): UserData
{
    return $mapper->map($user, UserData::class);
}

Where to look first:

  1. Symfony’s docs: https://symfony.com/doc/current/object_mapper.html
  2. The Map attribute class: vendor/symfony/object-mapper/Attribute/Map.php
  3. The component tests under vendor/symfony/object-mapper/Tests if shipped in your install
  4. A single failing mapping test in your own app — attribute behavior is easier to learn from a tiny DTO than from a full model

Implementation Patterns

Use it at the boundary, not in the domain. The best day-to-day pattern is a thin adapter service per aggregate or use case. Controllers and jobs depend on your adapter, not directly on ObjectMapperInterface:

final class UserDataMapper
{
    public function __construct(private ObjectMapperInterface $mapper) {}

    public function fromModel(User $user): UserData
    {
        return $this->mapper->map($user, UserData::class);
    }
}

This gives you one place to test mapping rules and one place to swap the implementation later. It is also the natural place to wrap the mapper yourself (logging, timing, null handling) instead of leaning on Symfony’s DI decoration — see the lazy-ghost note in Gotchas.

Response DTOs are the highest-value use case. Map Eloquent models to API resource DTOs instead of returning models directly or manually constructing arrays. Keep DTOs explicit; do not expose every model column by convention just because the names match.

Use transformers for value objects and type changes. The mapper does not magically cast strings to DateTimeImmutable, enums, or money objects. Add a transformer:

final class DateTransformer
{
    public static function toImmutable(?string $value): ?\DateTimeImmutable
    {
        return $value ? new \DateTimeImmutable($value) : null;
    }
}

final class OrderData
{
    #[Map(source: 'created_at', transform: [DateTransformer::class, 'toImmutable'])]
    public \DateTimeImmutable $createdAt;
}

Use constructor promotion for immutable DTOs. Readonly/promoted properties work best when the target is built through its constructor. Put #[Map(source: '...')] on the promoted property/parameter and keep constructor parameter names aligned with source properties where possible.

Handle polymorphism with if: conditions, not controller branches. Multiple target classes are supported via class-level #[Map(target: ..., if: ...)]. Keep those conditions small and covered by tests. If you find yourself writing complex expression strings, move the decision into a dedicated mapper service instead.

Nested objects and collections. Nested DTOs can be mapped when types line up. For collections, prefer an explicit transformer until you have verified the collection behavior for your version; the 7.4 line added collection transformer support, but collections are where version-specific bugs tend to appear. Transformers that need to map nested objects can implement ObjectMapperAwareInterface to receive the owning mapper.

Mapping onto an existing object is overwrite-oriented. $mapper->map($input, $user) mutates the target. It is not a PATCH merge. For partial updates, map to a DTO with nullable fields and apply only non-null values explicitly, or use conditions to skip properties.

Use it in tests and factories. Mapping fixtures to domain objects is a clean way to reduce test setup boilerplate, especially for command/query objects and event payloads.

Octane and long-running workers. Bind the mapper as a singleton. Attribute metadata is cached in memory in recent versions, which is good for Octane, but deploy-time code reloads still matter. Do not store request state in the mapper or in custom transformers.

Gotchas and Tips

  • No Laravel integration ships with it. You own the binding, configuration, and lifecycle. There is no config/object_mapper.php, no Artisan command, and no validation. Pair it with FormRequests for input validation.
  • It maps objects to objects, not arrays to objects. $request->validated() cannot be passed directly. Build a source DTO/command explicitly, or use a Laravel-native data package for request hydration.
  • Eloquent as a source is convenient but sharp. Magic attributes and accessors may resolve through property access, but the behavior is not the same as toArray(). Be explicit with #[Map(source: '...')], eager-load relationships to avoid N+1 queries, and test accessors/casts carefully.
  • Eloquent as a target is a security risk. Direct property writes can bypass $fillable, mutators, events, and validation. Never map untrusted request data directly onto models. Map to a DTO, validate it, then persist explicitly.
  • Readonly and promoted properties need constructor mapping. Older patch releases had several bugs around promoted readonly properties, nested mapping, and constructor initialization. Pin a recent patch: at minimum 7.4.5/8.0.5 for conditions on constructor arguments, 7.4.6/8.0.6 if you use class-level transforms with nested mapping, and 8.1.8 if you decorate the mapper and map into nested lazy-ghost targets.
  • Decorating/wrapping the mapper interacts with nested targets. v8.1.8 fixes an issue (symfony/symfony#66410) where nested lazy-ghost targets were not constructed when the mapper was decorated. If you wrap ObjectMapperInterface yourself — which this assessment recommends — be aware that nested-object construction paths were affected on earlier 8.1.x patches, so verify nested mappings through your wrapper with a test rather than assuming the inner mapper is reached transparently.
  • Multiple target classes need conditions. Older versions errored when multiple targets had no condition. Even on fixed versions, design one clear condition per target and test the fallback path.
  • Attribute strings fail at runtime. A typo in source: or target: will not be caught by PHPStan or your IDE. Write one mapping test per DTO; use class constants for class names where possible.
  • Missing and uninitialized source properties were buggy in early releases. Recent versions skip uninitialized values and handle missing source properties more gracefully, but do not rely on that as validation. Decide explicitly what should happen when a source property is absent.
  • Performance is fine for normal API payloads, not automatically for bulk work. Metadata is cached, but mapping still involves reflection and property access. Benchmark if you map thousands of objects per request; for imports, hand-written mapping or chunked processing may be faster.
  • Debugging is easiest by reducing the mapping. Catch the mapper’s mapping exception, inspect the property path, and simplify to a single property. Check visibility, constructor signatures, and whether the property is initialized.
  • Extension points are transformer and condition classes. If they need dependencies, verify whether your version resolves them through a container; if not, keep them dependency-free or register them manually. Use ObjectMapperAwareInterface when a transformer needs to recursively map.
  • Release cadence is high and patch-level detail matters. Recent activity is almost entirely bug #… fixes to nested/constructor/decorator edge cases. Before bumping, skim the changelog between your pin and the target version, and keep a small suite of mapping tests per DTO so regressions surface immediately.
  • The 36-star count is misleading. This is a Symfony split repository; development happens in the symfony/symfony monorepo. Check monorepo PRs and changelogs, and treat the component as young but actively maintained.
  • Encapsulate it. Because the component is relatively new and not Laravel-native, keep it behind your own mapper services. If it does not fit, swapping to spatie/laravel-data or hand-written mapping should be a small change, not a rewrite.
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.
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
animalyoung/laravel-influxdb
onix-solutions/sms-telera
lonely-lights/prosetta
oxygen/data
oxygen/core
jengo/base
xiloryx/log-collector
a21ns1g4ts/filament-stripe
la-php/laravel-admin