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

Ddd Doctrine Bundle Laravel Package

alexandrebulete/ddd-doctrine-bundle

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Install the Bundle
    composer require alexandrebulete/ddd-doctrine-bundle
    
  2. Enable the Bundle Add to config/bundles.php:
    AlexandreBulete\DddDoctrineBundle\DddDoctrineBundle::class => ['all' => true],
    
  3. First Use Case: Domain Repository Create a domain repository extending 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
        ) {}
    }
    

Key Entry Points

  • DoctrineRepository: Base class for DDD repositories (handles entity persistence, queries, and lifecycle).
  • DoctrinePaginator: For paginated queries (autowired as DoctrinePaginator).
  • Custom Doctrine Types: Register in config/packages/doctrine.yaml under dbal.types.

Implementation Patterns

1. Domain-Driven Repository Pattern

Workflow:

  1. Define a domain repository interface (e.g., PostRepositoryInterface).
  2. Implement it with DoctrineRepository:
    class DoctrinePostRepository extends DoctrineRepository implements PostRepositoryInterface
    {
        public function findByTitle(string $title): ?Post
        {
            return $this->findOneBy(['title' => $title]);
        }
    }
    
  3. Autowire the repository in domain services:
    use App\Post\Domain\PostRepositoryInterface;
    
    class PostService {
        public function __construct(private PostRepositoryInterface $repository) {}
    }
    

2. Custom Doctrine Types for Value Objects

Pattern:

  • Create a type class for value objects (e.g., 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);
        }
    }
    
  • Register the type in doctrine.yaml:
    doctrine:
        dbal:
            types:
                post_id: App\Post\Infrastructure\Doctrine\Type\PostIdType
    

3. Paginated Queries

Usage:

  • Inject 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
            );
        }
    }
    

4. Event Handling

Integration:

  • Use DoctrineRepository's lifecycle callbacks (e.g., prePersist, postRemove) to trigger domain events:
    class DoctrinePostRepository extends DoctrineRepository
    {
        public function prePersist(Post $post): void
        {
            $post->recordThatItWasCreated();
        }
    }
    

Gotchas and Tips

Pitfalls

  1. Circular Dependencies

    • Avoid autowiring DoctrineRepository directly in domain entities. Inject repositories only in domain services or infrastructure layers.
    • Fix: Use constructor injection in services, not entities.
  2. Type Registration Conflicts

    • Custom Doctrine types must extend AbstractValueObjectType (from ddd-doctrine-bridge) for proper DDD integration.
    • Fix: Ensure all custom types implement the bridge’s base classes.
  3. 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.
    • Fix: Scope repositories to request or method level where possible.
  4. PHP 8.2+ Strictness

    • The bundle requires PHP 8.2+. Ensure your composer.json enforces this:
      "config": {
          "platform": {
              "php": "8.2"
          }
      }
      

Debugging Tips

  1. Repository Not Autowired?

    • Verify the bundle is enabled in bundles.php.
    • Check for naming conflicts (e.g., custom DoctrineRepository classes must extend the bridge’s base class).
  2. Custom Types Not Recognized?

    • Clear the Doctrine cache:
      php bin/console doctrine:cache:clear-metadata
      php bin/console doctrine:cache:clear-query
      php bin/console doctrine:cache:clear-result
      
    • Ensure types are registered after the bundle loads (Symfony 6.3+ may require explicit ordering).
  3. Paginator Issues

    • DoctrinePaginator expects a traversable collection (e.g., ArrayCollection, QueryBuilder result). Pass raw arrays or non-traversable objects will fail.
    • Fix: Use findAll() or createQueryBuilder() instead of direct array inputs.

Extension Points

  1. 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();
        }
    }
    
  2. 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()
    );
    
  3. 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']
    
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.
nasirkhan/laravel-sharekit
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