Installation Add the package via Composer:
composer require austral/list-bundle
Ensure austral/entity-bundle (v3.1+) and Symfony Routing (v5.4/v6.4) are installed.
Register the Bundle
In config/bundles.php, add:
Austral\EntityBundle\EntityBundle::class => ['all' => true],
Austral\ListBundle\ListBundle::class => ['all' => true],
First Use Case: Basic List Mapping
Define a mapper class (e.g., src/Mapper/UserListMapper.php):
use Austral\ListBundle\Mapper\AbstractMapper;
use Austral\EntityBundle\Entity\User;
class UserListMapper extends AbstractMapper
{
public function map(array $data): array
{
return array_map(function ($item) {
return new User($item['id'], $item['name'], $item['email']);
}, $data);
}
}
Dependency Injection
Register the mapper in services.yaml:
services:
Austral\ListBundle\Mapper\UserListMapper:
tags: ['austral.list_mapper']
Usage in Controller
use Austral\ListBundle\Mapper\ListMapperInterface;
class UserController
{
public function __construct(private ListMapperInterface $listMapper) {}
public function index(): array
{
$rawData = [['id' => 1, 'name' => 'John'], ['id' => 2, 'name' => 'Jane']];
return $this->listMapper->map($rawData, UserListMapper::class);
}
}
Mapper Inheritance
Extend AbstractMapper for reusable logic:
class BaseEntityMapper extends AbstractMapper
{
protected function hydrate(array $data, string $entityClass): object
{
return new $entityClass(...);
}
}
Dynamic Mapping
Use mapWith() for conditional logic:
public function mapWith(array $data): array
{
return $this->map($data, function ($item) {
return $item['active'] ? new ActiveUser($item) : new InactiveUser($item);
});
}
Integration with Austral EntityBundle
Leverage EntityManager for hydration:
use Doctrine\ORM\EntityManagerInterface;
class DoctrineUserMapper extends AbstractMapper
{
public function __construct(private EntityManagerInterface $em) {}
public function map(array $data): array
{
return array_map(function ($item) {
return $this->em->find(User::class, $item['id']);
}, $data);
}
}
Batch Processing For large datasets, implement chunked mapping:
public function mapInBatches(array $data, int $batchSize = 100): array
{
$results = [];
foreach (array_chunk($data, $batchSize) as $batch) {
$results = array_merge($results, $this->map($batch));
}
return $results;
}
API Response Transformation Use mappers to convert entities to API responses:
class UserResponseMapper extends AbstractMapper
{
public function map(array $users): array
{
return array_map(function ($user) {
return [
'id' => $user->getId(),
'name' => $user->getName(),
'metadata' => $user->getMetadata()->toArray(),
];
}, $users);
}
}
Circular Dependencies
Avoid circular references in mappers (e.g., MapperA depending on MapperB which depends on MapperA).
Fix: Use dependency injection for complex mappers or refactor into smaller, single-purpose mappers.
Performance with Large Datasets Lazy-load mappers or use generators to avoid memory issues:
public function map(array $data): Generator
{
foreach ($data as $item) {
yield $this->hydrate($item);
}
}
Type Safety The package lacks built-in type hints for mapped entities. Use PHP 8.0+ return types and runtime checks:
/** @return array<User> */
public function map(array $data): array { ... }
Configuration Overrides
If using Austral’s config system, ensure list_mapper services are properly tagged:
# config/packages/austral_list.yaml
austral_list:
mappers:
- Austral\ListBundle\Mapper\UserListMapper
Mapper Not Found
Verify the service is tagged in services.yaml:
tags: ['austral.list_mapper'] # Required for autowiring
Data Mismatch
Use dd() or var_dump() in map() to inspect input/output:
public function map(array $data): array
{
dump($data); // Debug input
$result = array_map(...);
dump($result); // Debug output
return $result;
}
Doctrine Hydration Issues
If using EntityManager, ensure entities are properly configured in Doctrine:
$user = $this->em->find(User::class, $id);
if (!$user) {
throw new \RuntimeException("User not found for ID: $id");
}
Custom Mapper Events Dispatch events before/after mapping (requires Symfony EventDispatcher):
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
class EventfulMapper extends AbstractMapper
{
public function __construct(private EventDispatcherInterface $dispatcher) {}
public function map(array $data): array
{
$this->dispatcher->dispatch(new PreMapEvent($data));
$result = parent::map($data);
$this->dispatcher->dispatch(new PostMapEvent($result));
return $result;
}
}
Validation Integration Combine with Laravel Validators or Symfony Validator:
use Symfony\Component\Validator\Validator\ValidatorInterface;
class ValidatedMapper extends AbstractMapper
{
public function __construct(private ValidatorInterface $validator) {}
public function map(array $data): array
{
$errors = $this->validator->validate($data);
if (count($errors) > 0) {
throw new \InvalidArgumentException((string) $errors);
}
return parent::map($data);
}
}
Caching Mapped Results Cache results for static or rarely changing data:
use Symfony\Contracts\Cache\CacheInterface;
class CachedMapper extends AbstractMapper
{
public function __construct(private CacheInterface $cache) {}
public function map(array $data): array
{
$cacheKey = md5(serialize($data));
return $this->cache->get($cacheKey, function () use ($data) {
return parent::map($data);
});
}
}
Testing Mappers Mock dependencies and test edge cases:
public function testMapperWithInvalidData()
{
$mapper = new UserListMapper();
$this->expectException(\InvalidArgumentException::class);
$mapper->map([['invalid' => 'data']]);
}
How can I help you explore Laravel packages today?