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 Bundle Laravel Package

opportus/object-mapper-bundle

Symfony bundle that integrates the opportus/object-mapper library, registering mapper services and configuration in the dependency injection container to simplify object-to-object mapping. Helps reduce boilerplate when converting DTOs, entities, and domain objects in PHP applications.

View on GitHub
Deep Wiki
Context7

Getting Started

Reality check first. This bundle has 2 stars, zero dependents, and its last release was May 2022. Before you build anything on it, open composer.json in the repo and confirm the supported PHP and Symfony versions against your project — a 2022 bundle will typically require PHP 8.0/8.1, which may rule out a modern Symfony 7 app. If it does not install cleanly, treat the assessment below as a description of a pattern you'll copy rather than a dependency you'll adopt.

Install and register:

composer require opportus/object-mapper-bundle

The bundle should auto-register via Symfony Flex; if not, add it to config/bundles.php. Then look at, in this order:

  1. ObjectMapperBundle.php in the repo root — shows the DI extension class and the config root key (opportus_object_mapper).
  2. DependencyInjection/Configuration.php — the authoritative schema for what you can configure. This is your real documentation; the README is thin.
  3. DependencyInjection/ObjectMapperExtension.php — tells you which service IDs are registered and which compiler passes run, i.e. how custom controls/strategies are collected.
  4. The underlying library opportus/object-mapper in vendor/ — the bundle is a thin DI wrapper; almost all behaviour (routes, points, controls, strategies) lives there.

First use case: entity → API response DTO. Instead of hand-writing UserResponse::fromEntity(), you describe the mapping once and inject the mapper:

# config/packages/opportus_object_mapper.yaml
opportus_object_mapper:
    mappings:
        App\Entity\User:
            target: App\Dto\UserResponse
            # point/strategy definitions per the Configuration.php schema
final class UserController
{
    public function __construct(private ObjectMapperInterface $mapper) {}

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

Inject ObjectMapperInterface (not the concrete class) — that keeps you swappable if you later migrate to Symfony's own symfony/object-mapper or manual hydrators. Run one trivial mapping end-to-end before configuring anything ambitious; the failure mode for a mis-specified point is an exception at map time, not at cache-warm time.

Implementation Patterns

Explicit route-per-pair. The library is point-based, not convention-based. Every source→target class pair needs its own route, and every property you want copied needs a point (a source property path, a target property path, and the controls that guard it). There is no "map everything with the same name" fallback unless a default point/strategy does it in your version — verify rather than assume.

Strategy selection is the main design decision. Three strategies cover most day-to-day work:

  • Property strategy — plain setter-based assignment. Use for mutable DTOs and entities.
  • Constructor strategy — required for immutable targets: readonly properties, value objects, DTOs with a private constructor. Without it, constructor-promoted targets silently stay unpopulated or throw.
  • Adder strategy — for collection-typed targets (addTag()), which prevents you from replacing an already-initialised collection.

Pick per-mapping, not globally. A codebase mapping entities to mutable admin DTOs and to immutable API DTOs needs both strategies registered.

Compose with Messenger and form/request handling. The highest-leverage integration is at the boundary: map an HTTP request DTO to a command in a controller, map a domain entity to a read model in a query handler. Both are one-directional and stateless, which is where a point-based mapper behaves predictably. Avoid using it inside an entity's own mutation logic — that inverts the dependency and makes the mapper a hidden coupling.

Register custom controls/strategies as services. This is the bundle's reason to exist over the raw library: controls and strategies are DI-managed, so they can take dependencies (a validator, an enum caster, a clock). Check ObjectMapperExtension.php for the exact tag or config key used to collect them — usually a tag like opportus.object_mapper.control with a priority attribute. Write the control, tag it, and it should be picked up in every mapping that reaches that point.

Wrap it behind a thin application port. Define your own App\Shared\Mapper interface with map(object $source, string $targetClass): object and one adapter implementing it via the bundle. Two benefits: your call sites stop importing a two-star vendor namespace, and swapping the implementation later touches one file.

Gotchas and Tips

Nothing is coerced. If the source property is int and the target expects string, expect a type control failure, not a cast. Convert in a control or strategy, or normalise the source first. This catches people coming from symfony/serializer's denormalizer, which is far more forgiving.

Nullability is the most common runtime failure. A source property that can be null mapped to a non-nullable target parameter fails a nullable/type control. Decide per point: skip, default, or fail loudly. Write the null case in a test for every mapping — it's the bug that reaches production.

No automatic deep mapping. Nested objects are copied by reference (or rejected by a type control) unless you define a route for the nested pair and treat it as its own mapping. For a graph of three or four DTOs this means four routes; plan for it or flatten the DTO.

Immutability requires the constructor strategy. readonly properties have no setter, so a property strategy will fail or no-op depending on version. Also check that the constructor-parameter ordering/path configuration matches your DTO's signature — mismatch produces a confusing exception naming a point, not a parameter.

Debugging: read the exception literally. Failures name the specific point (source class + path → target class + path) and the control that rejected it. Start there before suspecting the container. For container-level problems, bin/console debug:container opportus and debug:container --tag=<control tag> tell you whether your custom control was actually registered; a typo in the tag fails silently with "mapping just doesn't copy that field."

No compile-time validation of the mapping graph. Because points are resolved at map time, a typo in a property name is a runtime error on that code path only — it will pass CI if no test exercises that route. Compensate with a data provider that maps one instance of every configured source class to its target and asserts a non-empty result. That single test is worth more than any amount of config review.

Version drift is a real risk. With no 2023+ release and no dependents, security and PHP-version fixes are on you. Fork it if you commit: the library is small and the point/route/control model is simple enough to maintain. If you only need "copy fields from A to B," Symfony's native symfony/object-mapper (or a 40-line reflection helper) may be the better long-term bet — but this package's explicit control layer is genuinely useful when you need guarded mapping with per-field validation rather than best-effort copying.

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.
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route