Antoine Bluchet
Contributed by Antoine Bluchet in #51741 and #58654

In many Symfony applications it's common to use DTO objects to carry data between layers. For example, you might create a DTO from a Doctrine entity, modify it using a Symfony Form, and then map the updated data back to the entity.

The code to map the DTO back into an entity often looks like this:

1
2
3
4
5
$user = new User();
$user->name = $dto->name;
$user->email = $dto->email;
$user->roles = ['ROLE_USER'];
// ...

This is tedious and error-prone. That's why Symfony 7.3 introduces a new ObjectMapper component to transfer data between objects. With it, the previous example becomes much simpler:

1
2
3
4
5
// when creating a new object from another one
$user = $mapper->map($dto, User::class);

// when updating an existing object with another one
$mapper->map($dto, $user);

In this example, $mapper is the main object mapper service, automatically autowired in controllers and services when type-hinting for Symfony\Component\ObjectMapper\ObjectMapperInterface:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// src/Controller/UserController.php
namespace App\Controller;

use App\Dto\UserInput;
use App\Entity\User;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\ObjectMapper\ObjectMapperInterface;

class UserController extends AbstractController
{
    public function updateUser(UserInput $userInput, ObjectMapperInterface $objectMapper): Response
    {
        $user = new User();
        // map properties from UserInput to User
        $objectMapper->map($userInput, $user);

        // ... persist $user and return response
        return new Response('User updated!');
    }
}

ObjectMapper uses the PropertyAccess component to set or update the values of properties in the target object. You can also use the #[Map] attribute to configure mapping behavior explicitly:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// src/Dto/UserDto.php
namespace App\Dto;

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

#[Map(target: User::class)]
class UserDto
{
    // 'target' refers to the name of this property in the target object
    #[Map(target: 'emailAddress')]
    public string $email = '';

    // 'if' allows to define expressions evaluated at runtime;
    // if the result is TRUE, the property is mapped; otherwise it's ignored
    #[Map(if: false)]
    public string $debugOnly = '';

    // 'transform' applies a PHP function or callback before mapping the value
    #[Map(transform: 'strtolower')]
    public string $username = '';
}

This new component is marked as an experimental feature in Symfony 7.3, so it may evolve, but it's already powerful, flexible, and ready for many use cases.

For example, you can use the #[TargetClass] condition to map a property only when targeting a specific class. In this example, the User class can be mapped to two different DTOs, but the $lastLoginIp property should only be copied to one of them:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
use App\Dto\AdminUserProfile;
use App\Dto\PublicUserProfile;
use Symfony\Component\ObjectMapper\Attribute\Map;
use Symfony\Component\ObjectMapper\Condition\TargetClass;

#[Map(target: PublicUserProfile::class)]
#[Map(target: AdminUserProfile::class)]
class User
{
    #[Map(target: 'ipAddress', if: new TargetClass(AdminUserProfile::class))]
    public ?string $lastLoginIp = '192.168.1.100';

   // ...
}

The ObjectMapper component also includes other advanced features:

  • Use services to define complex mapping conditions or value transformations;
  • Map based on the target class, not the source;
  • Automatically handle recursion and cyclic object graphs;
  • Write your own mapping logic (similar to Java's MapStruct);
  • Conditionally map to different targets based on methods or context.

This component makes it easier to work with DTOs and helps decouple API input/output from your domain model. It's also great for legacy projects where new DTOs must integrate with existing complex objects. It reduces manual mapping code, prevents bugs, and keeps your architecture clean.

Read the full docs of ObjectMapper

Published in #Living on the edge