Weave Code
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 data between objects (e.g., DTOs to entities) using PHP attributes to define field mappings. It reduces boilerplate transformation code, supports configurable mapping logic, and integrates cleanly with Symfony applications.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup in Laravel (Symfony Bridge)

Since Laravel doesn’t natively support Symfony’s ObjectMapper, you’ll need to:

  1. Install via Composer (Symfony 8+ required):
    composer require symfony/object-mapper
    
  2. Register the service in config/app.php (if using Symfony components):
    'providers' => [
        // ...
        Symfony\Component\ObjectMapper\ObjectMapper::class,
    ],
    
  3. Basic Usage Example:
    use Symfony\Component\ObjectMapper\ObjectMapper;
    use Symfony\Component\ObjectMapper\Attribute\Map;
    
    // Define source and target classes with [Map] attributes
    #[Map]
    class UserDto {
        public function __construct(
            #[Map(source: 'name')]
            public string $fullName,
            #[Map(source: 'email')]
            public string $emailAddress,
        ) {}
    }
    
    // Map from an array or another object
    $mapper = new ObjectMapper();
    $dto = $mapper->map([
        'name' => 'John Doe',
        'email' => 'john@example.com',
    ], UserDto::class);
    

First Use Case: API Request → DTO

use Symfony\Component\ObjectMapper\Attribute\Map;

#[Map]
class CreateUserRequest {
    public function __construct(
        #[Map(source: 'first_name')]
        public string $name,
        #[Map(source: 'email_address')]
        public string $email,
    ) {}
}

// In a Laravel controller:
$mapper = app(ObjectMapper::class);
$requestData = $this->request->all();
$userRequest = $mapper->map($requestData, CreateUserRequest::class);

Implementation Patterns

1. Attribute-Driven Mapping

Use Symfony’s [Map] attributes to define transformations declaratively:

#[Map]
class OrderDto {
    public function __construct(
        #[Map(source: 'order_id')]
        public int $id,
        #[Map(source: 'customer.name')] // Nested property
        public string $customerName,
        #[Map(source: 'items', type: OrderItemDto::class)] // Collection mapping
        public array $items,
    ) {}
}

2. Conditional Mapping

Skip fields based on runtime conditions:

#[Map(condition: fn ($source) => $source['is_active'] ?? false)]
class UserDto {
    #[Map(source: 'role', condition: fn ($source) => $source['is_admin'] ?? false)]
    public ?string $role;
}

3. Nested Object Mapping

Map complex hierarchies (e.g., UserUserDto with embedded AddressDto):

#[Map]
class UserDto {
    public function __construct(
        #[Map(source: 'address', type: AddressDto::class)]
        public AddressDto $address,
    ) {}
}

#[Map]
class AddressDto {
    public function __construct(
        #[Map(source: 'street')]
        public string $street,
    ) {}
}

4. Collection Transformations

Map arrays to collections of objects:

#[Map]
class OrderDto {
    #[Map(source: 'items', type: OrderItemDto::class)]
    public array $items;
}

5. Custom Transformers

Extend functionality via TransformerInterface:

use Symfony\Component\ObjectMapper\Transformer\TransformerInterface;

class CustomDateTransformer implements TransformerInterface {
    public function transform($value, string $targetType, ?string $targetProperty): mixed {
        return \DateTime::createFromFormat('Y-m-d', $value);
    }
}

// Register in ObjectMapper:
$mapper->addTransformer(new CustomDateTransformer());

6. Laravel Integration Tips

  • Service Binding: Bind the mapper in AppServiceProvider:
    public function register(): void {
        $this->app->singleton(ObjectMapper::class, fn () => new ObjectMapper());
    }
    
  • Form Requests: Use for validating and mapping request data:
    use Symfony\Component\ObjectMapper\Attribute\Map;
    
    class StoreUserRequest extends FormRequest {
        #[Map]
        public function rules(): array {
            return [
                'name' => 'required|string',
                'email' => 'required|email',
            ];
        }
    
        public function map(): UserDto {
            return $this->objectMapper->map($this->validated(), UserDto::class);
        }
    }
    

Gotchas and Tips

Pitfalls

  1. Symfony Dependency Lock-In:

    • The package requires Symfony 8+ and PHP 8.4+. If your Laravel project uses older versions, this is a blocker.
    • Workaround: Use Spatie Data Transfer Objects or Fractal instead.
  2. Attribute Overhead:

    • Mapping relies on PHP 8 attributes, which may feel verbose for simple cases.
    • Tip: Use IDE autocompletion (e.g., PHPStorm) to reduce boilerplate.
  3. Lazy Loading Issues:

    • Nested objects may not load eagerly by default.
    • Fix: Use #[Map(lazy: false)] or manually initialize properties.
  4. Constructor Parameter Conflicts:

    • If a target class has non-promoted constructor parameters, mapping may fail.
    • Solution: Use #[Map(ignore_missing: true)] or adjust the target class.
  5. Circular References:

    • Mapping bidirectional relationships (e.g., UserOrder) can cause infinite loops.
    • Workaround: Use #[Map(circular: true)] or break cycles manually.
  6. Performance with Large Collections:

    • Mapping arrays with thousands of items can be slow.
    • Optimization: Use #[Map(batch: 100)] or process in chunks.

Debugging Tips

  • Enable Debug Mode:
    $mapper = new ObjectMapper();
    $mapper->setDebug(true); // Logs mapping steps
    
  • Check for Missing Attributes:
    • If mapping fails silently, verify all source properties have [Map] attributes.
  • Validate Target Class:
    • Ensure the target class’s constructor matches the mapped data structure.

Extension Points

  1. Custom Metadata: Override default behavior with #[MapMetadata]:
    #[MapMetadata('user', UserDto::class)]
    class UserMapper {
        public function map($source): UserDto {
            return new UserDto($source['name'], $source['email']);
        }
    }
    
  2. Event Listeners: Attach listeners for pre/post-mapping logic:
    $mapper->addListener(new class implements MapperListenerInterface {
        public function onMap(object $source, string $targetClass, array $context): void {
            // Pre-mapping logic
        }
    });
    
  3. Caching: Cache attribute metadata for performance:
    $mapper->setCache(new \Symfony\Component\Cache\Adapter\FilesystemAdapter());
    

Laravel-Specific Quirks

  • Service Container Conflicts: Avoid naming collisions with Laravel’s built-in services (e.g., ObjectMapper vs. Illuminate\Database\Eloquent\Model). Fix: Use a custom alias in config/app.php:
    'aliases' => [
        'symfony.object_mapper' => Symfony\Component\ObjectMapper\ObjectMapper::class,
    ],
    
  • Blade Template Integration: Avoid using ObjectMapper in views (tight coupling). Instead, map data in controllers and pass DTOs to views.

Performance Considerations

  • Avoid Over-Mapping: Only map what you need—excessive attributes slow down the mapper.
  • Use #[Map(ignore_missing: true)]: Skip unmapped properties instead of failing.
  • Batch Processing: For large datasets, process in batches:
    $mapper->map($chunk, UserDto::class, ['batch' => 50]);
    
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.
davejamesmiller/laravel-breadcrumbs
artisanry/parsedown
christhompsontldr/phpsdk
enqueue/dsn
bunny/bunny
enqueue/test
enqueue/null
enqueue/amqp-tools
bower-asset/punycode
bower-asset/inputmask
bower-asset/jquery
bower-asset/yii2-pjax
laravel/nova
spatie/laravel-mailcoach
spatie/laravel-superseeder
laravel/liferaft
nst/json-test-suite
danielmiessler/sec-lists
jackalope/jackalope-transport
twbs/bootstrap4