Custom Property Mapper
If you need a custom mapping logic for a specific property, you can create a
service and add the attribute AsPropertyMapper
to a custom method.
Basic Usage
Example:
use Rekalogika\Mapper\Attribute\AsPropertyMapper;
class UserMapper
{
#[AsPropertyMapper(
targetClass: UserDto::class,
property: 'name',
)]
public function mapName(User $user): string
{
return strtoupper($user->getFirstName() . ' ' . $user->getLastName());
}
}
The above example concatenates first name and last name from the source User
object, transforms it to uppercase, and returns the result. Mapper will then
assign the result to the name
property of the target UserDto
object, as
specified in the arguments of the AsPropertyMapper
attribute.
Shorthand Using AsPropertyMapper
Attached to the Class
If you have many properties to manually map, you can put the AsPropertyMapper
attribute on the class, and it will apply to all methods in the class. Example:
use Rekalogika\Mapper\Attribute\AsPropertyMapper;
#[AsPropertyMapper(targetClass: UserDto::class)]
class UserMapper
{
#[AsPropertyMapper('name')]
public function mapName(User $user): string
{
return strtoupper($user->getFirstName() . ' ' . $user->getLastName());
}
#[AsPropertyMapper('birthDate')]
public function mapBirthDate(User $user): string
{
return $user->getBirthDate()->format('Y-m-d');
}
#[AsPropertyMapper('email')]
public function mapEmail(User $user): string
{
return $user->getEmailAddress();
}
}
Property Name Magic
For even more shorthand, you can omit the property name altogether, and the mapper will use the method name, stripping the leading 'map' and lowercasing the first letter.
use Rekalogika\Mapper\Attribute\AsPropertyMapper;
#[AsPropertyMapper(targetClass: UserDto::class)]
class UserMapper
{
// maps to 'name'
#[AsPropertyMapper]
public function mapName(User $user): string
{
return strtoupper($user->getFirstName() . ' ' . $user->getLastName());
}
// maps to 'birthDate'
#[AsPropertyMapper]
public function mapBirthDate(User $user): string
{
return $user->getBirthDate()->format('Y-m-d');
}
// maps to 'email
#[AsPropertyMapper]
public function mapEmail(User $user): string
{
return $user->getEmailAddress();
}
}
Getting the Existing Target Value
If you need to get the existing value of the target property, you can add the optional second argument to the method. The mapper will pass the existing value to the method.
use Rekalogika\Mapper\Attribute\AsPropertyMapper;
class UserMapper
{
#[AsPropertyMapper(
targetClass: User::class,
property: 'birthDate',
)]
public function mapBirthDate(
UserDto $userDto,
?\DateTimeInterface $birthDate // this will contain the current value
): \DateTimeInterface {
return new \DateTimeImmutable($userDto->birthDate);
}
}
If the target property contains an object, you may return the original instance or a new instance. If you return a new instance, Mapper will replace the original instance with the new one.
Extra Arguments
You also have the option to inject the main transformer, sub-mapper, and the context to the property mapper. This can be in any order, but the first argument must be the source object, followed by an optional second argument for the existing target value.
use Rekalogika\Mapper\Context\Context;
use Rekalogika\Mapper\MainTransformerInterface;
use Rekalogika\Mapper\Attribute\AsPropertyMapper;
use Rekalogika\Mapper\SubMapper\SubMapperInterface;
#[AsPropertyMapper(targetClass: UserDto::class)]
class UserMapper
{
#[AsPropertyMapper]
public function mapName(
User $user,
MainTransformerInterface $mainTransformer,
SubMapperInterface $subMapper,
Context $context
): string {
return strtoupper($user->getFirstName() . ' ' . $user->getLastName());
}
}
Source Union Types
Union types on the source side are supported.
use Rekalogika\Mapper\Attribute\AsPropertyMapper;
class AnimalMapper
{
#[AsPropertyMapper(
targetClass: AnimalDto::class,
property: 'name',
)]
public function mapName(Cat|Dog $animal): string
{
return $animal->getName();
}
}
Manual Wiring
If you don't use autowiring, autoconfiguration, or don't want to use attributes, you can add the service manually like this:
services:
App\Mapper\UserMapper:
tags:
- name: 'rekalogika.mapper.property_mapper'
method: 'mapName'
sourceClass: 'App\Entity\User'
targetClass: 'App\Dto\UserDto'
property: 'name'
- name: 'rekalogika.mapper.property_mapper'
method: 'mapBirthDate'
sourceClass: 'App\Entity\User'
targetClass: 'App\Dto\UserDto'
property: 'birthDate'
- name: 'rekalogika.mapper.property_mapper'
method: 'mapEmail'
sourceClass: 'App\Entity\User'
targetClass: 'App\Dto\UserDto'
property: 'email'
Dumping Property Mapper Table
To dump the list of all property mappers, run the following command:
$ bin/console debug:container --tag=rekalogika.mapper.property_mapper