AutoMapper for PHP

repository·main·Indexed 42 days ago

https://github.com/jolicode/automapper

A high-performance data mapper for PHP designed to eliminate boilerplate code when mapping objects or data structures. It features a Symfony bundle with support for API Platform integration, cache warmup for production, automatic mapper registration via the #[Mapper] attribute, and integration with Symfony Expression Language for conditional mapping and service calls.

Tokens
25.3K
Snippets
100
Records
120
Agent score
52%

What's inside AutoMapper

  1. Understand the core concepts of source and target

    main

    AutoMapper operates on two primary concepts:

    • Source: The existing object or data structure you want to extract information from.
    • Target: The destination object or data structure where the information will be mapped.

    Understanding these roles is fundamental to all mapping operations, including object-to-object mapping, normalization, and denormalization.

  2. Use PropertyTransformerInterface instead of specialized transformer interfaces

    main

    When upgrading to version 10.0, the specialized interfaces CustomPropertyTransformerInterface and CustomModelTransformerInterface have been removed. They are replaced by a single, unified PropertyTransformerInterface that handles both property and model transformations.

    If you have custom transformers, you must refactor them to implement PropertyTransformerInterface.

  3. Use #[MapTo] and #[MapFrom] attributes for property mapping

    main

    You can define explicit mappings between source and target objects using #[MapTo] and #[MapFrom] attributes.

    • #[MapTo] is applied to a property of the source object to specify where its value should go.
    • #[MapFrom] is applied to a property of the target object to specify where its value should come from.

    These attributes can be applied to:

    • Public or private properties (including promoted properties).
    • Public or private methods (used as setters/getters).
    • Classes (to define virtual properties).
    class Entity
    {
        #[MapTo(property: 'name')]
        public string $title;
    }
    
    class EntityDto
    {
        private string $name;
    
        #[MapFrom(property: 'title')]
        public function setName($name): void
        {
            $this->name = $name;
        }
    }
  4. How #[MapTo] and #[MapFrom] groups interact with Symfony Serializer #[Groups]

    main

    When a property uses both the Symfony Serializer #[Groups] attribute and the groups argument from #[MapTo] or #[MapFrom], the groups defined in #[MapTo] or #[MapFrom] take precedence and will override the #[Groups] attribute. The property will only be mapped if the context contains a group specified in the #[MapTo] or #[MapFrom] argument.

    use Symfony\Component\Serializer\Attribute\Groups;
    
    class Source
    {
        #[Groups(['group1', 'group2'])]
        #[MapTo(target: 'array', groups: ['group3'])]
        public $groupedProperty;
    }
    // In this case, the property is only mapped if the context contains 'group3'.
  5. Use Symfony Serializer `#[Groups]` for property mapping

    main

    When the Symfony Serializer component is available, AutoMapper respects the #[Groups] attribute to control which properties are included in the mapping process.

    Warning: If both the target and source objects have groups defined, a property is only mapped if the mapping context contains at least one group from the target object AND at least one group from the source object.

    use Symfony\Component\Serializer\Attribute\Groups;
    
    class Source
    {
        #[Groups(['group1', 'group2'])]
        public $groupedProperty;
    }
  6. Filter properties using Allowed attributes

    main

    Allowed attributes let you define a specific subset of properties to be mapped. Unlike groups, these are checked against the property names of the source object. You can also use nested arrays to restrict mapping for nested objects.

    WARNING

    The mapper only checks allowed attributes from the property name of the source object, not the target object.

    // Map only the 'id' property
    $target = $autoMapper->map($source, UserDTO::class, [
        'allowed_attributes' => ['id']
    ]);
    
    // Map only the 'city' property of a nested 'address' object
    $target = $autoMapper->map($source, UserDTO::class, [
        'allowed_attributes' => ['address' => ['city']]
    ]);
  7. Understand the mapping debug information

    main

    Both the debug:mapper command and the Symfony profiler panel provide the same diagnostic details for each mapper:

    • Property types: Displays the source and target property names along with their types (e.g., title (string) -> name (string)). This helps verify how AutoMapper interprets types and which transformer is selected.
    • Transformer: Shows the specific transformer used for each property. For supported transformers, a detailed description is provided (e.g., ObjectTransformer<SourceType, TargetType> or nested type info for arrays).
    • Ignored / not used properties: Lists properties that were skipped and the reason why (e.g., no matching source property, ignored by attribute, or condition not met).

    In the Symfony profiler, you can also click Show Code for an individual property to view the exact generated PHP snippet used for that specific mapping operation.

  8. Map data to existing objects using Identifiers

    main

    When mapping data to an existing object that contains collections (e.g., a User with multiple Address objects), AutoMapper can reuse existing objects within those collections instead of always instantiating new ones. This prevents duplicate entries in databases or ORM exceptions when dealing with existing entities.

    To enable this, AutoMapper uses the concept of an Identifier. When mapping a collection, AutoMapper checks the destination collection for objects with matching identifiers. If a match is found, the existing object is updated; otherwise, a new object is created and added to the collection.

    // Fetch existing object from database
    $user = $this->entityManager->find(User::class, $id);
    
    // Map data to the existing object
    // AutoMapper will use identifiers to manage nested collections
    $mapper->map($requestValue, $user);
  9. Handle Circular References

    main

    AutoMapper automatically tracks mapped objects to prevent infinite loops and preserve circular references in the target. However, you can control this behavior using the following options:

    • circular_reference_limit: Set to 0 to break the reference and throw a CircularReferenceException.
    • circular_reference_handler: Provide a callback function function ($source, $context) to return a custom value (e.g., null) when a circular reference is detected.
    // Break circular reference by throwing exception
    $target = $autoMapper->map($source, UserDTO::class, ['circular_reference_limit' => 0]);
    
    // Handle circular reference with a callback
    $target = $autoMapper->map($source, UserDTO::class, [
        'circular_reference_handler' => function ($source, $context) {
            return null;
        }
    ]);
  10. Skip null or uninitialized values

    main

    To prevent overwriting existing values in a target object when the source value is empty, use these options:

    • skip_null_values: If true, properties with null in the source will not be mapped, preserving the target's current value.
    • skip_uninitialized_values: If true, properties that have not been initialized in the source will not be mapped.
    // Skip nulls
    $target = $autoMapper->map($source, $target, ['skip_null_values' => true]);
    
    // Skip uninitialized properties
    $target = $autoMapper->map($source, $target, ['skip_uninitialized_values' => true]);
  11. Use Transformers to transform property values

    main

    Transformers define how a value from a source property is converted to a target property. By default, AutoMapper attempts to find the best transformer based on the source and target types. You can override this behavior by providing a transformer argument to the #[MapTo] or #[MapFrom] attributes.

    Supported transformer types include:

    • Symfony Expression Language: A string expression where source refers to the source object and context refers to the context array.
    • Callable: A PHP callable (e.g., a string representing a built-in function like 'strtoupper').
    • Static Callback: An array in the format [ClassName::class, 'methodName'].
    class Source
    {
        // Using Expression Language
        #[MapTo(transformer: "source.property === 'foo' ? 'bar' : 'baz'")]
        public string $property;
    
        // Using a callable
        #[MapTo(transformer: 'strtoupper')]
        public string $property;
    
        // Using a static callback
        #[MapTo(transformer: [self::class, 'transform'])]
        public string $property;
    
        public static function transform(string $value, Source $source, array $context): string
        {
            return strtoupper($value);
        }
    }