alexandrebulete/ddd-doctrine-bundle
composer require alexandrebulete/ddd-doctrine-bundle
config/bundles.php:
AlexandreBulete\DddDoctrineBundle\DddDoctrineBundle::class => ['all' => true],
DoctrineRepository (autowired via ddd-doctrine-bridge):
// src/Post/Infrastructure/Persistence/DoctrinePostRepository.php
namespace App\Post\Infrastructure\Persistence;
use AlexandreBulete\DddDoctrineBridge\Repository\DoctrineRepository;
use App\Post\Domain\Post;
use App\Post\Domain\PostRepositoryInterface;
class DoctrinePostRepository extends DoctrineRepository implements PostRepositoryInterface
{
public function __construct(
protected \Doctrine\Persistence\ManagerRegistry $registry,
protected string $entityClass = Post::class
) {}
}
DoctrineRepository: Base class for DDD repositories (handles entity persistence, queries, and lifecycle).DoctrinePaginator: For paginated queries (autowired as DoctrinePaginator).config/packages/doctrine.yaml under dbal.types.Workflow:
PostRepositoryInterface).DoctrineRepository:
class DoctrinePostRepository extends DoctrineRepository implements PostRepositoryInterface
{
public function findByTitle(string $title): ?Post
{
return $this->findOneBy(['title' => $title]);
}
}
use App\Post\Domain\PostRepositoryInterface;
class PostService {
public function __construct(private PostRepositoryInterface $repository) {}
}
Pattern:
PostId, PostTitle):
// src/Post/Infrastructure/Doctrine/Type/PostIdType.php
namespace App\Post\Infrastructure\Doctrine\Type;
use AlexandreBulete\DddDoctrineBridge\Type\AbstractValueObjectType;
use App\Post\Domain\PostId;
class PostIdType extends AbstractValueObjectType
{
public function getName(): string
{
return 'post_id';
}
public function convertToDatabaseValue($value, Platform $platform): ?string
{
return $value?->toString();
}
public function convertToPHPValue($value, Platform $platform): ?PostId
{
return PostId::fromString($value);
}
}
doctrine.yaml:
doctrine:
dbal:
types:
post_id: App\Post\Infrastructure\Doctrine\Type\PostIdType
Usage:
DoctrinePaginator into a service:
use AlexandreBulete\DddDoctrineBridge\Paginator\DoctrinePaginator;
class PostListService {
public function __construct(
private DoctrinePaginator $paginator,
private EntityManagerInterface $em
) {}
public function getPaginatedPosts(int $page, int $limit): array
{
return $this->paginator->paginate(
$this->em->getRepository(Post::class)->findAll(),
$page,
$limit
);
}
}
Integration:
DoctrineRepository's lifecycle callbacks (e.g., prePersist, postRemove) to trigger domain events:
class DoctrinePostRepository extends DoctrineRepository
{
public function prePersist(Post $post): void
{
$post->recordThatItWasCreated();
}
}
Circular Dependencies
DoctrineRepository directly in domain entities. Inject repositories only in domain services or infrastructure layers.Type Registration Conflicts
AbstractValueObjectType (from ddd-doctrine-bridge) for proper DDD integration.Entity Manager Leaks
DoctrineRepository holds a reference to the EntityManager. Avoid long-lived repository instances (e.g., as service container singletons) to prevent memory leaks.PHP 8.2+ Strictness
composer.json enforces this:
"config": {
"platform": {
"php": "8.2"
}
}
Repository Not Autowired?
bundles.php.DoctrineRepository classes must extend the bridge’s base class).Custom Types Not Recognized?
php bin/console doctrine:cache:clear-metadata
php bin/console doctrine:cache:clear-query
php bin/console doctrine:cache:clear-result
Paginator Issues
DoctrinePaginator expects a traversable collection (e.g., ArrayCollection, QueryBuilder result). Pass raw arrays or non-traversable objects will fail.findAll() or createQueryBuilder() instead of direct array inputs.Custom Repository Methods
Extend DoctrineRepository to add domain-specific queries:
class DoctrinePostRepository extends DoctrineRepository
{
public function findPublished(): array
{
return $this->createQueryBuilder('p')
->where('p.publishedAt <= :now')
->setParameter('now', new \DateTimeImmutable())
->getQuery()
->getResult();
}
}
Event Subscribers Attach subscribers to repository lifecycle events:
use AlexandreBulete\DddDoctrineBridge\Event\RepositoryEvents;
$repository->getEventDispatcher()->addListener(
RepositoryEvents::PRE_PERSIST,
fn (PrePersistEvent $event) => $event->getEntity()->validate()
);
Custom Paginator
Override DoctrinePaginator behavior by extending its class and binding it in services.yaml:
services:
App\Infrastructure\Paginator\CustomDoctrinePaginator:
decorates: 'ddd_doctrine.paginator'
arguments: ['@.inner']
How can I help you explore Laravel packages today?