mark-gerarts/auto-mapper-plus
AutoMapper+ transfers data between PHP objects with minimal boilerplate, inspired by .NET AutoMapper. Define mappings with custom callbacks and operations, handle nested objects, construction, naming conventions, and map arrays/stdClass with configurable options.
Installation:
composer require mark-gerarts/auto-mapper-plus
For Laravel, consider using the Symfony bundle if applicable.
Basic Setup:
Initialize the mapper in a service provider (e.g., AppServiceProvider):
use AutoMapperPlus\AutoMapper;
use AutoMapperPlus\Configuration\AutoMapperConfig;
public function boot()
{
$config = new AutoMapperConfig();
$this->app->singleton(AutoMapper::class, function () use ($config) {
return AutoMapper::initialize(function (AutoMapperConfig $config) {
// Register mappings here (see below).
});
});
}
First Use Case: Map an entity to a DTO in a controller or service:
$mapper = app(AutoMapper::class);
$dto = $mapper->map($entity, EmployeeDto::class);
Mapping Entities to DTOs:
Register mappings in a dedicated config class (e.g., MappingConfig) or service provider:
$config->registerMapping(Employee::class, EmployeeDto::class)
->forMember('age', function (Employee $source) {
return date('Y') - $source->getBirthYear();
});
Collection Mapping:
Use mapMultiple for arrays/collections:
$dtos = $mapper->mapMultiple($employees, EmployeeDto::class);
Nested Mappings: Handle relationships recursively:
$config->registerMapping(Employee::class, EmployeeDto::class)
->forMember('address', Operation::mapTo(AddressDto::class));
Polymorphic Mappings: Map to multiple possible child classes:
$config->registerMapping(Parent::class, ParentDto::class)
->forMember('children', Operation::mapToAnyOf([
ChildADto::class,
ChildBDto::class
]));
AutoMapper as a singleton in Laravel’s service container.UserMapping, OrderMapping) and load them in the provider.
$this->app->afterResolving(AutoMapper::class, function ($mapper) {
$mapper->getConfiguration()->addMappings([new UserMapping(), new OrderMapping()]);
});
$config->registerMapping(Employee::class, EmployeeDto::class)
->forMember('name', function ($source, AutoMapper $mapper, $context) {
return $mapper->translate($source->getName(), $context['locale']);
});
Circular References:
Avoid bidirectional mappings between the same classes (e.g., A → B and B → A with nested properties).
Solution: Use ignore() or break cycles manually.
Private Property Access:
The mapper accesses private properties via reflection. Ensure your IDE/IDE helper (e.g., barryvdh/laravel-ide-helper) is configured to recognize them.
Performance with Large Collections:
mapMultiple processes items sequentially. For bulk operations, consider batching or parallel processing.
Overriding Default Mappings: Explicitly register mappings for classes to avoid unexpected behavior with similar property names.
Enable Verbose Logging:
$config->getOptions()->setDebug(true);
Logs mapping operations to storage/logs/automapper.log.
Validate Mappings:
Use hasMappingFor() to check if a mapping exists:
if (!$config->hasMappingFor(Source::class, Destination::class)) {
throw new \RuntimeException("Mapping not registered!");
}
Custom Operations:
Implement MappingOperationInterface for reusable logic (e.g., validation, formatting):
class UppercaseOperation implements MappingOperationInterface {
public function execute($source, $destinationProperty, $context) {
return strtoupper($source);
}
}
Dynamic Property Resolution: Use callbacks for runtime property name resolution:
$config->registerMapping(Source::class, Destination::class)
->forMember('dynamicProp', Operation::fromProperty(function ($source) {
return 'prop_' . $source->getType();
}));
Conditional Mapping: Skip properties based on conditions:
$config->registerMapping(Source::class, Destination::class)
->forMember('sensitiveData', function ($source) {
return app('auth')->check() ? $source->getData() : null;
});
Service Provider Binding: Bind the mapper with context (e.g., request, auth):
$this->app->singleton(AutoMapper::class, function ($app) {
return AutoMapper::initialize(function (AutoMapperConfig $config) use ($app) {
$config->getOptions()->setContext(['request' => $app['request']]);
});
});
Testing: Mock the mapper in tests:
$mapper = Mockery::mock(AutoMapper::class);
$mapper->shouldReceive('map')->andReturn(new EmployeeDto());
Caching Mappings:
Cache the AutoMapperConfig in Laravel’s cache:
$config = Cache::remember('automapper_config', 60, function () {
return new AutoMapperConfig();
});
How can I help you explore Laravel packages today?