benkle/doctrine-adoption-bundle
Installation:
composer require benkle/doctrine-adoption-bundle
Ensure your project uses Doctrine ORM (Laravel's Eloquent is not compatible).
Enable the Bundle:
Add to config/bundles.php (Symfony) or AppKernel.php (legacy):
Benkle\DoctrineAdoptionBundle\BenkleDoctrineAdoptionBundle::class => ['all' => true],
First Use Case:
Define a parent entity with @InheritanceType("JOINED") and @DiscriminatorMap. Example:
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity(repositoryClass="Benkle\DoctrineAdoptionBundle\Repository\AdoptionRepository")
* @ORM\InheritanceType("JOINED")
* @ORM\DiscriminatorColumn(name="type", type="string")
* @ORM\DiscriminatorMap({"parent" = "App\Entity\ParentEntity"})
*/
class ParentEntity { ... }
Key Services:
AdoptionRepository: Extends Doctrine’s EntityRepository with inheritance-aware methods.AdoptionManager: Handles dynamic adoption of child entities to parent types.Use AdoptionManager to programmatically reclassify entities:
$manager = $this->get('benkle.doctrine_adoption.manager');
$childEntity = $entityManager->find(ChildEntity::class, 1);
$manager->adopt($childEntity, ParentEntity::class); // Reclassifies to ParentEntity
Leverage AdoptionRepository for type-agnostic queries:
$repository = $this->get('doctrine')->getRepository(ParentEntity::class);
$results = $repository->findByAdoptedType(ChildEntity::class); // Finds all ChildEntity instances
Combine JOINED, SINGLE_TABLE, or MAPPED_SUPERCLASS with adoption logic:
// Example: Mixed inheritance in a single hierarchy
@ORM\InheritanceType("JOINED")
@ORM\DiscriminatorMap({
"parent" = "App\Entity\ParentEntity",
"single_table_child" = "App\Entity\SingleTableChild"
})
Hook into Doctrine lifecycle events for automatic adoption:
// In a Doctrine event subscriber
$event->getObject()->setType('new_type'); // Triggers adoption
Service Container Binding:
Bind the bundle’s services in AppServiceProvider:
$this->app->bind('benkle.doctrine_adoption.manager', function ($app) {
return new AdoptionManager($app->make('doctrine')->getManager());
});
Eloquent Compatibility Note: This bundle does not work with Eloquent. Use native Doctrine entities or a hybrid approach (e.g., Doctrine entities with Laravel’s query builder).
Custom Adoption Logic:
Extend AdoptionManager to add business rules:
class CustomAdoptionManager extends AdoptionManager {
public function adoptWithValidation($entity, string $targetType) {
if (!$this->isValidTransition($entity, $targetType)) {
throw new \RuntimeException("Invalid adoption");
}
$this->adopt($entity, $targetType);
}
}
discriminator_column is indexed for large hierarchies.EntityManager#flush() strategically to avoid N+1 queries during bulk operations.Discriminator Column Conflicts:
discriminator_column clashes with existing columns, rename it or use a unique prefix (e.g., dt_type).@DiscriminatorColumn.Circular References:
Transaction Boundaries:
$entityManager->beginTransaction();
try {
$manager->adopt($entity, $newType);
$entityManager->flush();
$entityManager->commit();
} catch (\Exception $e) {
$entityManager->rollback();
throw $e;
}
Legacy Data Issues:
NULL discriminator values may break adoption logic.Enable Doctrine Logging:
# config/packages/dev/doctrine.yaml
doctrine:
dbal:
logging: true
profiling: true
Check logs for SQL errors during adoption (e.g., foreign key violations).
Query Builder Pitfalls:
DQL queries that ignore the discriminator column. Use:
$qb = $repository->createQueryBuilder('e');
$qb->where('e.type = :type')->setParameter('type', 'child_type');
Caching Quirks:
php bin/console doctrine:cache:clear-metadata
Custom Adoption Strategies:
Benkle\DoctrineAdoptionBundle\Strategy\AdoptionStrategyInterface for custom validation/transformation.Event Listeners:
preAdopt/postAdopt events (if the bundle exposes them) to add pre/post-processing:
$eventDispatcher->addListener(
'benkle.doctrine_adoption.pre_adopt',
[$this, 'validateAdoption']
);
Repository Decorators:
AdoptionRepository to add hierarchy-specific methods:
class CustomAdoptionRepository extends AdoptionRepository {
public function findByHierarchyLevel(int $level) { ... }
}
services:
app.custom_adoption.repository:
class: App\Repository\CustomAdoptionRepository
decorates: benkle.doctrine_adoption.repository
arguments: ['@app.custom_adoption.repository.inner']
How can I help you explore Laravel packages today?