alexandrebulete/ddd-doctrine-bridge
Installation
composer require alexandrebulete/ddd-doctrine-bridge
Ensure doctrine/orm and spatie/laravel-data (or similar DDD support packages) are installed.
Basic Configuration
config/database.php and config/doctrine.php).config/app.php:
'providers' => [
// ...
Alexandrebulete\DddDoctrineBridge\DddDoctrineBridgeServiceProvider::class,
],
First Use Case: Mapping a DDD Entity to a Doctrine Entity
use Alexandrebulete\DddDoctrineBridge\Attributes\DoctrineEntity;
use Alexandrebulete\DddDoctrineBridge\DoctrineEntityMapper;
#[DoctrineEntity]
class UserEntity {
// Your DDD entity properties/methods
}
// Map to Doctrine entity
$mapper = app(DoctrineEntityMapper::class);
$doctrineEntity = $mapper->mapToDoctrineEntity(new UserEntity());
#[DoctrineEntity], #[DoctrineColumn]) to define how DDD entities map to Doctrine entities. Example:
#[DoctrineEntity(repository: UserRepository::class)]
class User {
#[DoctrineColumn(name: 'email', type: 'string')]
public string $email;
}
DoctrineEntityMapper:
$mapper->mapToDoctrineEntity($dddEntity, CustomDoctrineEntity::class);
use Alexandrebulete\DddDoctrineBridge\DoctrineRepository;
class UserRepository extends DoctrineRepository {
protected $entityClass = User::class;
}
$repository = app(UserRepository::class);
$users = $repository->findAll();
use Alexandrebulete\DddDoctrineBridge\Event\DoctrineEntityPersisted;
DoctrineEntityPersisted::dispatch($doctrineEntity);
prePersist, postUpdate) and trigger DDD logic:
DoctrineEntityManager::getEventManager()->addEventListener(
['prePersist', 'preUpdate'],
new DddDomainEventListener()
);
#[DoctrineEmbeddable]
class Address {
public string $street;
}
#[DoctrineEntity]
class User {
#[DoctrineEmbedded]
public Address $address;
}
UnitOfWork to manage aggregate roots:
$entityManager = DoctrineEntityManager::get();
$uow = $entityManager->getUnitOfWork();
$uow->registerManaged($doctrineEntity, $dddAggregateRoot);
Attribute Overrides:
#[DoctrineColumn] take precedence over Doctrine’s default naming conventions. Ensure consistency to avoid runtime errors.name in #[DoctrineColumn] may lead to unexpected column names.Circular Dependencies:
Order ↔ Customer) can cause infinite loops in mapping. Use #[DoctrineIgnore] to exclude problematic properties:
#[DoctrineIgnore]
public Order $order;
Transaction Boundaries:
$entityManager->beginTransaction();
try {
$entityManager->persist($doctrineEntity);
$entityManager->flush();
$entityManager->commit();
} catch (\Exception $e) {
$entityManager->rollback();
throw $e;
}
Lazy Loading Conflicts:
#[DoctrineFetch("EAGER")] to force eager loading:
#[DoctrineFetch("EAGER")]
public Collection $orders;
Enable Doctrine Logging:
Add to config/doctrine.php:
'logging' => true,
'logging_level' => \Doctrine\ORM\Logging\LogLevel::DEBUG,
Logs appear in Laravel’s log channel.
Validate Mappings:
Use the DoctrineEntityMapper::validateMapping() method to check for inconsistencies before runtime:
$mapper->validateMapping(User::class);
Hybrid Repositories: If mixing Eloquent and Doctrine, ensure repositories are type-hinted correctly to avoid ambiguity:
// Avoid:
public function find(int $id) { ... }
// Prefer:
public function find(User $user) { ... }
Custom Mappers:
Extend DoctrineEntityMapper to handle bespoke DDD-to-Doctrine logic:
class CustomMapper extends DoctrineEntityMapper {
protected function mapProperty($dddProperty, $doctrineProperty) {
// Custom logic here
}
}
Event Subscribers: Create custom Doctrine event subscribers for DDD-specific behaviors:
use Doctrine\ORM\Event\LifecycleEventArgs;
class DddLifecycleSubscriber implements \Doctrine\Common\EventSubscriber {
public function postPersist(LifecycleEventArgs $args) {
$entity = $args->getObject();
// Trigger DDD domain events
}
}
Query Builders: Override Doctrine’s query builder to support DDD query objects:
$queryBuilder = $entityManager->createQueryBuilder();
$queryBuilder->andWhere('u.status = :status')
->setParameter('status', UserStatus::ACTIVE);
How can I help you explore Laravel packages today?