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 component maps data from one object to another using PHP attributes. Simplifies DTO/entity transformations, supports configurable mapping rules, and integrates with the Symfony ecosystem. Documentation and contributions are handled in the main Symfony repository.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation:

    composer require symfony/object-mapper
    

    For Laravel, register the service in config/services.php:

    'object_mapper' => Symfony\Component\ObjectMapper\ObjectMapper::class,
    

    Bind it in a service provider:

    $this->app->singleton(Symfony\Component\ObjectMapper\ObjectMapper::class, function ($app) {
        return new Symfony\Component\ObjectMapper\ObjectMapper();
    });
    
  2. First Use Case: Map a source object to a target DTO using attributes:

    use Symfony\Component\ObjectMapper\Annotations as ORM;
    
    #[ORM\MapEntity]
    class UserResponseDTO {
        #[ORM\MapProperty("name")]
        public string $fullName;
    
        #[ORM\MapProperty("email")]
        public string $emailAddress;
    }
    
    // In a Laravel controller/service:
    $mapper = app(Symfony\Component\ObjectMapper\ObjectMapper::class);
    $dto = $mapper->map(new User(), UserResponseDTO::class);
    
  3. Quick Start Guide:

    • Use @MapEntity on the target class.
    • Annotate properties with @MapProperty to specify source fields.
    • For nested objects, use @MapCollection or @MapEmbedded.

Implementation Patterns

Core Workflows

1. Basic Object Mapping

// Source entity
class User {
    public string $name;
    public string $email;
}

// Target DTO
#[MapEntity]
class UserDTO {
    #[MapProperty("name")]
    public string $fullName;

    #[MapProperty("email")]
    public string $email;
}

// Usage
$mapper->map($user, UserDTO::class);

2. Nested Object Mapping

#[MapEntity]
class ProfileDTO {
    #[MapEmbedded]
    public AddressDTO $address;
}

#[MapEntity]
class AddressDTO {
    #[MapProperty("street")]
    public string $streetName;
}

// Maps `User` → `ProfileDTO` with nested `AddressDTO`.

3. Collection Mapping

#[MapEntity]
class UserCollectionDTO {
    #[MapCollection]
    public array $users;
}

// Maps `Collection<User>` → `array<UserDTO>`.

4. Conditional Mapping

#[MapEntity]
class AdminUserDTO {
    #[MapProperty("name")]
    public string $adminName;

    #[MapProperty("email")]
    public string $email;
}

// Only map to `AdminUserDTO` if `$user->isAdmin()`.
$mapper->map($user, AdminUserDTO::class, [
    'condition' => fn ($source) => $source->isAdmin(),
]);

5. Custom Transformations

#[MapEntity]
class UserWithAgeDTO {
    #[MapProperty("name")]
    public string $name;

    #[MapProperty("birthDate")]
    #[MapTransform(fn ($date) => Carbon::parse($date)->age)]
    public int $age;
}

Laravel Integration Tips

1. Service Provider Binding

public function register(): void {
    $this->app->singleton(Symfony\Component\ObjectMapper\ObjectMapper::class, function ($app) {
        $mapper = new Symfony\Component\ObjectMapper\ObjectMapper();
        // Optional: Register custom transforms or conditions.
        return $mapper;
    });
}

2. Controller Usage

public function show(User $user) {
    $dto = app(Symfony\Component\ObjectMapper\ObjectMapper::class)
        ->map($user, UserResponseDTO::class);

    return response()->json($dto);
}

3. Form Request Mapping

public function update(StoreUserRequest $request) {
    $data = $request->validated();
    $user = $mapper->map($data, User::class);
    $user->save();
}

4. Event Listeners

public function handle(OrderCreatedEvent $event) {
    $command = $mapper->map($event->order, OrderCommand::class);
    // Dispatch $command...
}

5. Testing

public function testMapping() {
    $user = new User(['name' => 'John', 'email' => 'john@example.com']);
    $dto = $mapper->map($user, UserDTO::class);

    $this->assertEquals('John', $dto->fullName);
}

Gotchas and Tips

Pitfalls

  1. Attribute Reflection Overhead:

    • The mapper uses PHP attributes, which require PHP 8+. If using an older version, consider alternatives like spatie/laravel-data.
    • Tip: Cache attributes in production for performance:
      $mapper = new ObjectMapper([
          'cache_attributes' => true,
      ]);
      
  2. Circular References:

    • Mapping objects with circular references (e.g., UserProfile) may cause infinite loops.
    • Fix: Use @MapEmbedded with lazy: true or implement a custom condition to break cycles.
  3. Missing Properties:

    • If a source property doesn’t exist, the mapper throws an exception by default.
    • Fix: Use @MapProperty(ignore_missing: true) or handle it in a custom transform.
  4. Constructor Arguments:

    • If the target class has a constructor, the mapper may fail to map properties.
    • Fix: Use @MapConstructor or ensure properties are public/accessible.
  5. Nested Collections:

    • Deeply nested collections (e.g., UserProfileAddress[]) can be slow.
    • Tip: Use @MapCollection with index_by for performance:
      #[MapCollection(index_by: 'id')]
      public array $addresses;
      
  6. Type Mismatches:

    • The mapper doesn’t automatically cast types (e.g., stringint).
    • Fix: Use @MapTransform or a custom TypeConverterInterface.

Debugging Tips

  1. Enable Debug Mode:

    $mapper = new ObjectMapper([
        'debug' => true,
    ]);
    
    • Logs mapping steps to stderr.
  2. Inspect Mapping Rules:

    $metadata = $mapper->getMetadata(UserDTO::class);
    dump($metadata->getProperties());
    
  3. Handle Exceptions:

    • Catch MappingException for invalid mappings:
      try {
          $mapper->map($source, Target::class);
      } catch (MappingException $e) {
          Log::error('Mapping failed: ' . $e->getMessage());
      }
      
  4. Custom Conditions:

    • Skip mapping if a condition fails:
      #[MapEntity]
      class PremiumUserDTO {
          #[MapProperty("name")]
          public string $name;
      
          #[MapProperty("subscription")]
          public SubscriptionDTO $subscription;
      
          #[MapCondition(fn ($source) => $source->isPremium())]
          public function isPremium(): bool {}
      }
      

Extension Points

  1. Custom Transformers:

    $mapper->addTransformer(new class implements TypeConverterInterface {
        public function convert($value, string $targetType, array $context = []): mixed {
            return Carbon::parse($value);
        }
    });
    
  2. Class-Level Conditions:

    #[MapEntity]
    #[MapCondition(fn ($source) => $source->isActive())]
    class ActiveUserDTO {}
    
  3. Lazy Loading:

    • Defer mapping until a property is accessed:
      #[MapEmbedded(lazy: true)]
      public ProfileDTO $profile;
      
  4. Custom Metadata:

    • Override default mapping behavior:
      $mapper->setMetadata(UserDTO::class, new Metadata(
          new PropertyMetadata('name', 'fullName'),
          // ...
      ));
      
  5. Integration with Laravel:

    • Use ObjectMapperAwareInterface for DI:
      class MyService implements ObjectMapperAwareInterface {
          public function setObjectMapper(ObjectMapper $mapper): void {
              $this->mapper = $mapper;
          }
      }
      
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.
nexmo/api-specification
capell-app/block-library
axium/identity
cetria/laravel-dummy-models
cetria/reflection-helper
agropredict/sso-auth-bundle
evolvestudio/spam-protection
datacore/hub-sdk
develia/commons
cuci/prototurk-sdk
cuci/prototurk-sdk-symfony
develia/geo-bundle
dreamzy/livewire-charts
touchestate-sdk/php-sdk
ecotone/kafka
22h/doctrine-garbage-collection-bundle
agtp/agtp-php
agtp/mod-php
splash/sonata-admin
splash/metadata