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

Automapper Plus Bundle Laravel Package

mark-gerarts/automapper-plus-bundle

Symfony bundle integrating AutoMapper+ for object mapping. Register mappings with AutoMapperConfiguratorInterface, autowire the automapper_plus.mapper service or AutoMapperInterface, and use automatic tagging with service autoconfiguration. Supports Symfony 3.4 through 8.x.

View on GitHub
Deep Wiki
Context7

Getting Started

First, a reality check. This is a Symfony bundle, not a Laravel package. There is no extra.laravel.providers, no package discovery, and the AutoMapperPlusBundle class registers itself by being pushed into Symfony's $bundles array or a Flex recipe. Dropping it into a Laravel app pulls in a second service container for zero benefit.

What is reusable is the underlying library: mark-gerarts/automapper-plus, which is plain framework-agnostic PHP. The bundle adds exactly three things worth copying:

  1. A service alias for the mapper (AutoMapperInterface).
  2. A convention for registering mappings via configurator classes implementing AutoMapperConfiguratorInterface.
  3. A config block that maps onto the library's Options object.

All three can be reproduced in ~40 lines of Laravel service-provider code.

Minimal setup for a Laravel app

composer require mark-gerarts/automapper-plus
<?php // app/Providers/AutoMapperServiceProvider.php

namespace App\Providers;

use App\Mapping\AutoMapperConfiguratorInterface;
use AutoMapperPlus\AutoMapper;
use AutoMapperPlus\AutoMapperInterface;
use AutoMapperPlus\Configuration\AutoMapperConfig;
use Illuminate\Support\ServiceProvider;

class AutoMapperServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->app->singleton(AutoMapperConfig::class, function () {
            $config = new AutoMapperConfig();

            // Mirror the bundle's "tagged services + priority" behaviour.
            foreach (config('automapper.configurators', []) as $class) {
                $this->app->make($class)->configure($config);
            }

            return $config;
        });

        $this->app->singleton(AutoMapperInterface::class, fn ($app) => new AutoMapper(
            $app->make(AutoMapperConfig::class)
        ));

        $this->app->alias(AutoMapperInterface::class, 'automapper');
    }
}
<?php // config/automapper.php
return [
    'configurators' => [
        \App\Mapping\OrderMappingConfig::class,
        \App\Mapping\UserMappingConfig::class,
    ],
];

Then inject AutoMapperPlus\AutoMapperInterface anywhere — controllers, actions, jobs, Inertia props builders.

Where to look first. Skip the bundle README's Symfony wiring sections and read the core library README's mapping DSL (registerMapping, forMember, Operation, reverseMap, mapMultiple). The bundle README is still useful as a design template: copy the "one configurator class per domain, configure(AutoMapperConfigInterface $config)" convention verbatim.

First use case. Keep it narrow: an Eloquent model → response DTO for one API endpoint, replacing an ad-hoc toArray() shuffle. That gives you a measurable before/after and a template to copy before you commit to the abstraction everywhere.

Implementation Patterns

Configurator-per-domain, not god-config

The bundle's real contribution is the convention: many small classes, each implementing a configure() method, discovered by tag. Reproduce the discovery by listing classes in config/automapper.php (or scanning app/Mapping with a marker interface). Prefer one configurator per aggregate — OrderMappingConfig, InvoiceMappingConfig — so a diff tells you exactly which contract changed.

class OrderMappingConfig implements AutoMapperConfiguratorInterface
{
    public function configure(AutoMapperConfigInterface $config): void
    {
        $config->registerMapping(Order::class, OrderDto::class)
            ->forMember('totalFormatted', fn (Order $o) => number_format($o->total_cents / 100, 2))
            ->forMember('customerName', Operation::fromProperty('customer.name'))
            ->reverseMap(); // OrderDto -> Order, for PATCH-style updates
    }
}

Map to DTOs, never expose Eloquent models

The pattern that pays off daily: controller → action class → $mapper->map($model, OrderDto::class) → return the DTO. Eloquent's lazy attributes and $hidden/$appends behaviour stop leaking into your API contract, and the DTO becomes the single place where serialization shape is defined. It also makes ->load() requirements explicit, because a missing relation throws rather than silently emitting null.

Use mapToObject() for updates, mapMultiple() for lists

// PATCH: mutate an existing entity without nuking untouched fields
$mapper->mapToObject($request->toDto(), $order);

// Index endpoint
$dtos = $mapper->mapMultiple($orders, OrderDto::class);

mapToObject is the one most teams miss and then hand-roll with $model->fill() plus a whitelist — same job, less drift.

Custom closure vs. reusable operation

Inline forMember closures are fine for one-offs (fullName, totalFormatted). The moment the same transformation appears in three configurators, extract it into a named callable class and reference it. That's your extension point for date formatting, money, and enum casting — the things that would otherwise be duplicated in every mapping.

Nested paths need a property accessor

Operation::fromProperty('customer.address.city') only works with a property-accessor that understands paths. On Symfony you install the bundle's SymfonyPropertyAccessorBridge; on Laravel, composer require symfony/property-access and set $config->getOptions()->setPropertyAccessor(...) inside your configurator. Without it, nested paths fail at runtime, not at boot.

Testing

Add a mapping test per configurator: build a fixture source object, map it, assert the DTO's properties. These tests are cheap and they are the only place mapping bugs surface before production — the abstraction swallows type errors otherwise. Run them in CI alongside the configurator files so a renamed model property fails the build instead of a request.

Rollout

Pilot one module behind a config flag, benchmark it, then expand. Keep every configurator in a single namespace (App\Mapping) so that if you later swap to spatie/laravel-data or hand-rolled mappers, the blast radius is one directory.

Gotchas and Tips

Don't install the bundle in Laravel. composer require mark-gerarts/automapper-plus-bundle will resolve (it's framework-agnostic enough to install), but nothing will wire it up. Registering AutoMapperPlusBundle::class in config/app.php does not work — it's a Symfony Bundle subclass expecting Symfony's kernel. Take the library only.

Pin the version and own the risk. 65 stars, 0 dependents, single maintainer, no SLA. Any composer update can break you. "mark-gerarts/automapper-plus": "1.*" with a committed lockfile, plus a note in your ADR that the mapping layer is internally owned — not vendor-supported. Budget for a weekend of replacement work if it's abandoned.

create_unregistered_mappings: true is a trap in production. It makes unknown source/target pairs "work" by silently best-guessing, which means a typo'd target class produces an empty-ish DTO instead of an exception. Enable it in local/testing for speed, disable it everywhere else, and rely on explicit registerMapping calls.

Errors arrive late and vaguely. A missing property or type mismatch throws on the request that triggers it, wrapped in library exceptions that don't name your controller. Mitigations: register mappings at boot (a broken configurator fails fast), and wrap map() in your own thin helper that re-throws with source/target class names in the message.

Reflection is not free. For single-object mapping the cost is noise. For mapMultiple() over thousands of rows in a job or export, benchmark against a hand-written loop — reflection-based hydration is materially slower. Keep the mapper for the long tail of shapes and hand-roll the one hot path.

Private properties and constructors. The default accessor reaches private properties via reflection; the Symfony bridge deliberately doesn't. Pick one deliberately. Likewise, skip_constructor: true is what you want for DTOs with required constructor args and no no-arg path, but combined with ignore_null_properties: false it will happily overwrite a field with null. Decide both options per project, not per configurator.

Nested collections need their own mappings. An unregistered nested type inside a collection doesn't error usefully — it may fall back to unregistered behaviour or a shallow copy. Register every type in the graph explicitly and cover it in the mapping test.

Eager-load before you map. Mapping inside a foreach over unloaded relations is an N+1 generator with extra steps, and the abstraction makes it invisible in the profiler. ->with('customer') on the query, then map.

Build the mapper once. Bind AutoMapperInterface as a singleton. Rebuilding the config per resolution throws away all the registration work and turns a cheap operation into a request-scoped cost.

Extension points worth knowing. $config->getOptions() inside configure() for per-project options; a custom property accessor for path support; priority ordering on configurators (the bundle supports a tag priority) if two classes touch the same mapping. Anything not covered by the options list in the bundle README simply isn't exposed — check the core library's Options object rather than assuming the YAML keys are exhaustive, since the README explicitly says "not all options are supported."

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.
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
adachsoft/php-code-style