Le blog

ObjectMapper : un nouveau composant Symfony 7.3 pour la transformation d'objets

Publié le 27 mai 2025

Symfony 7.3 intègre un nouveau composant aux côtés de JsonPath : ObjectMapper. Développé par Antoine Bluchet, ce composant, actuellement expérimental, a pour vocation de simplifier la transformation d'un objet en un autre.

Le composant ObjectMapper" class="wp-image-11304"/><figcaption class="wp-element-caption#

La nécessité d'un composant ObjectMapper

Dans le cycle de vie d'une application, la transformation de données d'une structure à une autre est une opération courante et souvent nécessaire. Que ce soit pour convertir des Data Transfer Objects (DTO) en Entités à persister (ou vice-versa), pour adapter les données provenant d'une API externe à votre modèle de domaine interne, ou encore pour isoler du "code legacy" derrière des façades modernes, le besoin d'un mécanisme de mapping d'objets est omniprésent.

#

Exemple d’utilisation

La configuration d'ObjectMapper s'appuie principalement sur les attributs PHP. Prenons l'exemple d'un ProductInputDto que nous souhaitons mapper vers une entité Doctrine Product.

// src/Dto/ProductInputDto.php
namespace App\Dto;

use App\Entity\Product;
use Symfony\Component\ObjectMapper\Attribute\Map;

// Define the default mapping target for this DTO class
#[Map(target: Product::class)]
class ProductInputDto
{
    // The 'productName' property will be mapped to the 'name' property of the Product entity
    #[Map(target: 'name')]
    public string $productName;

    // The 'description' property will be mapped directly if a 'description' property exists in Product
    public string $description;

    // This property will not be mapped because of if: false
    #[Map(if: false)]
    public string $internalSku = '';

    #[Map(transform: 'floatval')]
    public string $price;
}

// src/Entity/Product.php
namespace App\Entity;


use Doctrine\ORM\Mapping as ORM;


#[ORM\Entity]
class Product
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private ?int $id = null;


    #[ORM\Column]
    public string $name;


    #[ORM\Column]
    public string $description;


    #[ORM\Column]
    public float $price;


    #[ORM\Column]
    public \DateTimeImmutable $createdAt;


    public function __construct()
    {
        $this->createdAt = new \DateTimeImmutable();
    }


    public function getId(): ?int
    {
        return $this->id;
    }
}

Ici, l'attribut #[Map] sur la classe ProductInputDto indique que sa cible de transformation par défaut est l'entité Product. Sur la propriété $productName, l’attribut #[Map(target: 'name')] spécifie que sa valeur doit être affectée à la propriété $name de l'objet Product. La propriété price sera transformée en float grâce à la fonction native floatval pour l’exemple. Les transformations et les conditions peuvent aussi utiliser des services plutôt que des callables. Les autres propriétés seront mappées si elles ont une propriété du même nom dans l’entité cible.

Voyons comment l'utiliser dans un contrôleur Symfony :

// src/Controller/ProductController.php
namespace App\Controller;

use App\Dto\ProductInputDto;
use App\Entity\Product;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpKernel\Attribute\AsController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Attribute\MapRequestPayload;
use Symfony\Component\ObjectMapper\ObjectMapperInterface;
use Symfony\Component\Routing\Annotation\Route;

#[AsController]
class ProductController
{
    #[Route('/product', name: 'product_create', methods: ['POST'])]
    public function create(
        #[MapRequestPayload] ProductInputDto $productInput,
        ObjectMapperInterface $objectMapper,
        EntityManagerInterface $entityManager
    ): Response {
        // It is also possible to add a second argument
	 // which is either a target class name or a target object
        $product = $objectMapper->map($productInput);        
        $entityManager->persist($product);
        $entityManager->flush();

        return $this->json(
            ['message' => 'Product created successfully: ' . $product->name],
            Response::HTTP_CREATED
        );
    }
}

Dans ce contrôleur, après que MapRequestPayload ait créé l'instance de ProductInputDto à partir des données de la requête, $objectMapper->map($productInput) transfère les valeurs de $productInput vers une entité Product en suivant les règles définies par les attributs #[Map].

#

Statut expérimental et perspectives d'évolution

Il est important de souligner que le composant ObjectMapper est actuellement expérimental. Son API publique pourrait donc connaître des ajustements dans les futures versions de Symfony, avant sa stabilisation. Les retours et contributions de la communauté sont précieux durant cette phase.Les discussions concernant ses évolutions et les fonctionnalités envisagées sont ouvertes, notamment sur GitHub (voir la discussion #54476). Des pistes d'amélioration pourraient s'inspirer d'outils établis comme AutoMapper, en améliorant les performances avec du code généré, des services de transformation de données réutilisables (comme TargetClass ou MapCollection) ou encore une intégration plus fine avec les autres composants Symfony.

#

Pour aller plus loin

Le composant ObjectMapper propose une approche déclarative et flexible pour la transformation d'objets, avec l'objectif d'améliorer la clarté et la maintenabilité du code qui gère ces opérations.

Nous vous encourageons à explorer ce nouveau composant et à consulter la documentation officielle de Symfony pour une vue d'ensemble de ses capacités. Nous travaillons aussi sur une intégration dans API Platform. N'hésitez pas à nous contacter pour toute demande de renseignements, formations ou expertise de notre part !

Antoine Bluchet

Antoine Bluchet

Principal developer

Mots-clés54476, ObjectMapper, Symfony, Symfony 7.3

Le blog

Pour aller plus loin