check24/apitk-dtomapper-bundle
Installation:
composer require check24/apitk-dtomapper-bundle
Register the bundle in config/bundles.php (if not auto-discovered).
Configure Services:
Add this to config/services.yaml to auto-load mappers:
services:
App\DtoMapper\:
resource: '../src/DtoMapper'
public: true
First Use Case:
src/Dto/UserV1.php).src/DtoMapper/UserV1Mapper.php) extending MapperInterface.@Rest\View() with @Dto\View() in your controller to trigger mapping.DTO Design:
UserV1, UserV2) to support backward compatibility.Mapper Implementation:
@param User $data) for IDE hints.UserV1Mapper calls AddressV1Mapper).Controller Integration:
@Rest\View() with @Dto\View(dtoMapper="App\DtoMapper\UserV1Mapper").Dto\CollectionView for array responses:
/**
* @Dto\CollectionView(dtoMapper="App\DtoMapper\UserV1Mapper")
*/
Autowiring:
public function __construct(private UserV1Mapper $userMapper) {}
Versioned Endpoints:
src/DtoMapper/V1/, src/DtoMapper/V2/).symfony/serializer for JSON/XML output.
# config/packages/serializer.yaml
framework:
serializer:
mapping:
paths: ['%kernel.project_dir%/config/serializer']
use Symfony\Component\Validator\Constraints as Assert;
class UserV1 {
/**
* @Assert\Email()
*/
private string $email;
}
public function map($data): UserV1 {
return Cache::remember("user_{$data->getId()}", 3600, fn() => new UserV1(...));
}
Mapper Naming Collisions:
UserV1Mapper vs. UserMapperV1).@Dto\View to avoid ambiguity.Circular Dependencies:
UserV1Mapper mapping to AddressV1, which maps back to UserV1).Performance:
$users = $entityManager->getRepository(User::class)->findBy([], ['address' => 'JOIN']);
/**
* @Dto\CollectionView(dtoMapper="App\DtoMapper\UserV1Mapper", stream=true)
*/
Deprecated Features:
api-platform/core (for modern APIs).nelmio/api-doc-bundle + custom mappers.Annotation Overrides:
@Dto\View must replace @Rest\View entirely. Mixing them may cause unexpected behavior.Mapper Not Found:
services.yaml.@Dto\View annotation value.Serialization Errors:
@Serializer\SerializedName to properties if field names differ:
use Symfony\Component\Serializer\Annotation\SerializedName;
class UserV1 {
#[SerializedName('user_id')]
private int $id;
}
Circular Reference Errors:
@Serializer\MaxDepth to limit recursion:
#[Serializer\MaxDepth(1)]
class UserV1 {}
Custom Mappers:
MapperInterface for reusable logic:
abstract class BaseMapper implements MapperInterface {
public function map($data): Dto {
$dto = new Dto();
$dto->setId($data->getId());
return $this->postMap($dto, $data);
}
abstract protected function postMap(Dto $dto, $data);
}
Dynamic Mappers:
$mapper = $container->get(sprintf('App\DtoMapper\%sMapper', $version));
Event Listeners:
$dispatcher->dispatch(new MapperEvent($data, $dto, MapperEvents::PRE_MAP));
Testing:
$mapper = $this->createMock(UserV1Mapper::class);
$mapper->method('map')->willReturn(new UserV1());
$this->container->set(UserV1Mapper::class, $mapper);
How can I help you explore Laravel packages today?