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

Auto Mapper Bundle Laravel Package

retailcrm/auto-mapper-bundle

Symfony bundle integrating RetailCRM AutoMapper to automatically map arrays and objects to typed DTOs. It simplifies request/response hydration with service auto-configuration and support for nested and collection mappings.

View on GitHub
Deep Wiki
Context7

Getting Started

retailcrm/auto-mapper-bundle is a Symfony bundle wrapping RetailCRM's object-to-object mapper. It has no Laravel service provider, no package discovery, no publishable config — Composer will install it, and then nothing happens. Treat it as "a library plus a DI wiring layer you must redo yourself."

Minimal path:

  1. Read composer.json first. Check the require block for symfony/framework-bundle, symfony/config, symfony/dependency-injection. If those are hard requirements you're pulling a chunk of Symfony into a Laravel app — acceptable in a large app, a smell in a small one. Also confirm the underlying mapper package it wraps (likely a retailcrm/auto-mapper-style library) — that library is what you actually want, and you can usually use it standalone without the bundle.
  2. Skim the bundle's DependencyInjection/*Extension.php. This is the whole bundle in one file: it reads the config tree and constructs the mapper service. Whatever constructor arguments it passes are what you must bind in Laravel.
  3. Check Resources/config/services.* for the service id and any tagged services (custom mappers/normalizers/transformers). Those tags become your manual bindings/tagged bindings.
  4. If you only need the mapping engine, skip the bundle entirely: composer require retailcrm/auto-mapper (or whatever the underlying package is named), instantiate the mapper directly, and avoid the Symfony DI baggage.

First use case that justifies the dependency: you're integrating with RetailCRM (retailcrm/api-client-php) and need to map API payloads (orders, customers, catalogs) into typed DTOs and back. That's the vendor's own problem domain, which is where the default conventions will fit best. If you're mapping unrelated internal objects, Laravel-native options (spatie/laravel-data, eventsauce/object-hydrator, or plain symfony/serializer with ObjectNormalizer) will likely be less friction.

Implementation Patterns

Hand-wiring the mapper in a service provider

Translate the Symfony service definition into a Laravel singleton, and keep the bundle's service id out of your application code behind an interface:

// app/Providers/AutoMapperServiceProvider.php
public function register(): void
{
    // Constructor args copied from the bundle's Extension::load()
    $this->app->singleton(MapperInterface::class, function ($app) {
        return new Mapper(
            cacheDir: storage_path('framework/mapper'),
            // transformers/normalizers from config
        );
    });
}

Then alias: $this->app->alias(MapperInterface::class, 'auto-mapper');.

Config translation

The bundle expects Symfony's config tree (auto_mapper: { ... } in config/packages/*.yaml). In Laravel, create config/auto-mapper.php, return the same keys, and adapt them at binding time. Two things break in translation:

  • %env(VAR)% parameters don't exist. Replace with env()/config() lookups in the provider.
  • Symfony's snake_case config keys are usually passed straight to a constructor as an options array — flatten nested mappings: / transformers: lists into plain PHP arrays.

Mapping API payloads to DTOs

The high-value pattern: keep Eloquent models out of the mapping layer. Map into readonly DTOs, then fill()/create() explicitly.

$dto = $mapper->map($payload, OrderDto::class);   // array -> typed object
$model = Order::create($dto->toArray());           // explicit persistence boundary

Mapping straight onto Eloquent models is tempting but fragile — mass-assignment guards, mutators, casts, and HasAttributes hydration don't line up with reflection-based assignment, and you lose the ability to distinguish "not provided" from "null". DTO-then-fill keeps that distinction.

Collections

For list endpoints, map element-wise rather than expecting a magic collection type:

$orders = collect($response['orders'])
    ->pipe(fn ($c) => $c->map(fn ($o) => $mapper->map($o, OrderDto::class)));

If the underlying mapper has a mapCollection()/iterable overload, use it — it typically reuses reflection metadata across elements and is measurably faster on large catalog syncs.

Custom transformers

Vendor-specific normalization (RetailCRM date strings, money as string floats, phone normalization) belongs in dedicated transformer classes, registered once, so mapping rules live in one place rather than in foreach loops scattered across jobs.

class RetailCrmDateTransformer
{
    public function __invoke($value): \DateTimeImmutable
    {
        return new \DateTimeImmutable($value, new \DateTimeZone('UTC'));
    }
}

Where it fits in a sync workflow

The realistic day-to-day flow in a Laravel app:

  1. Webhook controller receives RetailCRM payload → validate minimal shape → dispatch job.
  2. Job maps payload to DTO via the mapper.
  3. DTO drives an upsert (upsert() on external id) and emits domain events.
  4. Outbound: map model → DTO → array for the API client.

Put the mapper only in steps 2 and 4. Controllers and Eloquent models should never see it; that isolates you from a 1-star dependency.

Testing

Snapshot-test mapping rather than asserting field-by-field:

it('maps a retailcrm order payload', function () {
    $dto = app(MapperInterface::class)->map($this->payload(), OrderDto::class);
    expect($dto)->toMatchSnapshot();
});

Snapshots catch silent drift when DTO properties change — important when the mapper is reflection-driven and won't error on a renamed property.

Gotchas and Tips

  • No auto-discovery. Laravel's package manifest won't find anything. You must register the provider yourself (or just not install the bundle).
  • Zero dependents and one star. No ecosystem to lean on; no Stack Overflow answers; the README is the documentation. Budget time for reading source. Wrap it in your own MapperInterface so you can swap in spatie/laravel-data later without touching call sites.
  • Symfony cache directory. Cached mapping metadata typically lives in a Symfony-style var dir. Point it at storage_path('framework/cache/auto-mapper') and add a php artisan auto-mapper:clear-equivalent (or clear it in your deploy script), otherwise stale maps after DTO refactors will produce confusing errors in production only.
  • Reflection cost. Mapping hundreds of thousands of catalog items is reflection-heavy on first call per type. Warm the cache in deploy, and reuse one mapper instance (singleton) — don't new it per item.
  • Type coercion is silent. Reflection-based assignment will happily turn "1" into 1 or throw depending on the setter's declared type. RetailCRM payloads are heavily string-typed, so declare DTO property types deliberately and add explicit transformers for money/quantities.
  • Nullability traps. If the mapper uses constructor promotion or setters, a missing key may become null on a non-nullable property, or a TypeError deep in a job. Add an explicit "required fields" assertion at the boundary rather than relying on mapping failures.
  • Nested objects and arrays-of-objects usually need type hints the mapper can read (docblocks, attributes, or explicit mapping definitions). If nested mapping silently yields arrays, you've probably lost the generic docblock.
  • Circular references (Order → Customer → Orders) will recurse; use depth limits or map only the direction you need per use case.
  • Debugging: when a map fails, dump the target class's reflection metadata / the registered mapping for that pair before stepping through the payload. Most failures are "no mapping defined for this pair" rather than a value problem.
  • Extension points worth knowing: tagged custom transformers, custom map definitions (if the underlying library supports config-file mappings), and the compiler pass — the last one is meaningful only in Symfony, so in Laravel this becomes a service provider that registers tagged services manually.
  • Version pinning. With this dependency profile, pin exactly ("1.x.y", not "^1.0") and let Renovate/Dependabot surface updates for review. A surprise minor bump in a mapper can silently change coercion behaviour.
  • Before committing, verify the package actually targets your PHP version and is maintained (last release date vs. commit activity). The scoring here (opportunity 62, stars 1) suggests "interesting niche, no community" — which is fine for a vendor-specific integration and a red flag for general-purpose mapping.
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.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle