Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Mapper Bundle Laravel Package

boshurik/mapper-bundle

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation:

    composer require boshurik/mapper-bundle
    

    Register the bundle in config/bundles.php (Symfony 5+ auto-discovers it).

  2. First Use Case: Define a simple mapping between two entities (e.g., User and UserDto). Create a mapping class implementing MappingInterface:

    namespace App\Mapper;
    
    use BoShurik\MapperBundle\Mapper\Mapping\MappingInterface;
    use App\Entity\User;
    use App\Dto\UserDto;
    
    class UserToUserDtoMapper implements MappingInterface
    {
        public function map(User $source): UserDto
        {
            return new UserDto($source->getId(), $source->getName());
        }
    }
    
  3. Register the Mapper: Bind the mapper in a service (e.g., services.yaml):

    services:
        App\Mapper\UserToUserDtoMapper:
            tags: ['mapper.mapping']
    
  4. Autowire and Use: Inject the mapper into a service/controller:

    use App\Mapper\UserToUserDtoMapper;
    
    class UserController
    {
        public function __construct(private UserToUserDtoMapper $mapper) {}
    
        public function show(User $user): UserDto
        {
            return $this->mapper->map($user);
        }
    }
    

Implementation Patterns

Core Workflows

  1. One-Way Mappings: Use MappingInterface for unidirectional transformations (e.g., Entity → DTO). Example:

    $dto = $mapper->map($entity);
    
  2. Two-Way Mappings: Implement ReverseMappingInterface for bidirectional transformations (e.g., DTO ↔ Entity). Example:

    class UserDtoToUserMapper implements ReverseMappingInterface
    {
        public function map(UserDto $source): User
        {
            $user = new User();
            $user->setName($source->getName());
            return $user;
        }
    
        public function reverseMap(User $source): UserDto
        {
            return new UserDto($source->getId(), $source->getName());
        }
    }
    
  3. Dependency Injection: Tag mappers with mapper.mapping or mapper.reverse_mapping to auto-register them. Access via:

    $this->container->get('mapper.mapping.user_to_dto');
    
  4. Batch Processing: Loop through collections:

    $dtos = array_map(
        fn ($entity) => $mapper->map($entity),
        $entities
    );
    
  5. Conditional Logic: Use if checks or strategy patterns inside map() for dynamic transformations.


Integration Tips

  1. Symfony Forms: Combine with Symfony\Component\Form for DTO-to-Entity binding:

    $form = $this->createForm(UserDtoType::class, $dto);
    if ($form->isSubmitted() && $form->isValid()) {
        $entity = $reverseMapper->map($dto);
        $entityManager->persist($entity);
    }
    
  2. API Resources: Use mappers in ApiPlatform serializers or Symfony Serializer for consistent responses:

    use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface;
    
    class UserDtoNormalizer implements ContextAwareNormalizerInterface
    {
        public function normalize($object, $format = null, array $context = [])
        {
            return $this->mapper->map($object);
        }
    }
    
  3. Event Listeners: Trigger mappings on entity events (e.g., prePersist):

    class UserListener
    {
        public function __construct(private UserToUserDtoMapper $mapper) {}
    
        public function prePersist(User $user)
        {
            $dto = $this->mapper->map($user);
            // Log or validate DTO
        }
    }
    
  4. Custom Generators: Extend the CLI generator to scaffold mappers with default logic:

    bin/console make:mapper User UserDto
    

Gotchas and Tips

Pitfalls

  1. Circular Dependencies: Avoid bidirectional mappings between the same classes (e.g., A ↔ B where A and B reference each other). Solution: Use DTOs as intermediaries or lazy-load properties.

  2. Performance with Large Collections: Mapping collections eagerly (e.g., array_map) can be memory-intensive. Solution: Use generators or chunk processing:

    $generator = function () use ($entities, $mapper) {
        foreach ($entities as $entity) {
            yield $mapper->map($entity);
        }
    };
    
  3. Type Safety: The package lacks runtime type validation. Ensure return types match expectations. Solution: Use PHP 8 attributes or runtime assertions:

    assert($dto instanceof UserDto);
    
  4. Generator Dependencies: Forgetting to install nikic/php-parser will break CLI generation:

    composer require nikic/php-parser
    
  5. Service Overrides: Custom mapper services must be properly tagged or manually bound to avoid conflicts.


Debugging

  1. Mapper Not Found: Verify the service is tagged (mapper.mapping) and the bundle is loaded. Debug: Run bin/console debug:container | grep mapper to check registration.

  2. Generation Errors: Ensure generated files are writable and class names are unique. Debug: Check var/log/dev.log for PHP-Parser exceptions.

  3. Reverse Mapping Issues: Test reverseMap() separately to isolate bidirectional logic:

    $entity = $reverseMapper->map($dto);
    $dtoRoundTrip = $reverseMapper->reverseMap($entity);
    assert($dto->equals($dtoRoundTrip));
    

Extension Points

  1. Custom Mappers: Extend the base interfaces for domain-specific logic:

    interface CustomMappingInterface extends MappingInterface
    {
        public function mapWithContext($source, array $context);
    }
    
  2. Generator Templates: Override the Twig templates for code generation in templates/mapper/. Example: Add default imports or annotations.

  3. Event Dispatching: Trigger events during mapping (e.g., MapperEvents::PRE_MAP):

    use BoShurik\MapperBundle\Event\MapperEvents;
    
    $dispatcher->dispatch(new PreMapEvent($source, $mapper));
    
  4. Caching: Cache generated mappers or results for performance:

    $cache = new FilesystemCache();
    $cachedMapper = $cache->get('mapper.user_to_dto', fn() => $mapper);
    
  5. Testing: Mock mappers in tests to isolate logic:

    $this->mockBuilder(UserToUserDtoMapper::class)
         ->shouldAllowMocking()
         ->getMock()
         ->method('map')
         ->willReturn(new UserDto(1, 'Test'));
    
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
craftcms/url-validator
directorytree/privacy-filter-classifier
directorytree/privacy-filter
datacore/hub-sdk
develia/commons
cuci/prototurk-sdk
cuci/prototurk-sdk-symfony
develia/geo-bundle
dreamzy/livewire-charts
touchestate-sdk/php-sdk
22h/doctrine-garbage-collection-bundle
agtp/agtp-php
agtp/mod-php
splash/sonata-admin
splash/metadata
splash/openapi
splash/scopes
splash/toolkit
testo/output-teamcity
testo/bridge-symfony