benkle/doctrine-adoption
Adds a small Collector + Doctrine metadata listener to enhance inheritance mapping by “adopting” child entities into a parent with a named association. Register the loadClassMetadata listener manually (including for schema creation/CLI tools).
Installation:
composer require benkle/doctrine-adoption
Ensure your project uses Doctrine ORM (v2.x+).
First Use Case:
Define inheritance relationships between entities. For example, if ChildEntity inherits from ParentEntity but Doctrine doesn’t recognize it due to naming conventions:
$collector = new \Benkle\DoctrineAdoption\Collector();
$collector->addAdoptee(ParentEntity::class, ChildEntity::class, 'child');
Integrate with Doctrine:
Add the MetadataListener to your EntityManager:
$eventManager = new \Doctrine\Common\EventManager();
$eventManager->addEventListener(
\Doctrine\ORM\Events::loadClassMetadata,
new \Benkle\DoctrineAdoption\MetadataListener($collector)
);
$entityManager = EntityManager::create($connection, $config, $eventManager);
Verify: Run schema validation or migrations to confirm Doctrine now recognizes the inheritance:
php vendor/bin/doctrine orm:schema-tool:validate
Define Adoptions:
Use Collector to map parent-child relationships, specifying the discriminator column value (e.g., 'child'):
$collector->addAdoptee(ParentEntity::class, ChildEntity::class, 'child');
Dynamic Adoptions:
For dynamic relationships (e.g., based on config), populate the Collector in a service or bootstrap file:
$collector = new Collector();
foreach ($config['inheritance_map'] as $parent => $children) {
foreach ($children as $child => $discriminator) {
$collector->addAdoptee($parent, $child, $discriminator);
}
}
Integration with Symfony:
If using the bundle (benkle/doctrine-adoption-bundle), configure in config/packages/doctrine.yaml:
doctrine_adoption:
adoptees:
App\Entity\ParentEntity:
App\Entity\ChildEntity: child
Custom Discriminator Columns:
Override the default discriminator column name (type) by extending MetadataListener:
class CustomMetadataListener extends \Benkle\DoctrineAdoption\MetadataListener {
public function __construct(Collector $collector, string $discriminatorColumn = 'inheritance_type') {
parent::__construct($collector, $discriminatorColumn);
}
}
Listener Not Auto-Registered:
MetadataListener must be manually added to the EventManager. Forgetting this causes Doctrine to ignore inheritance mappings.EntityManager creation in a service or bootstrap file to ensure consistency.Discriminator Column Conflicts:
type column (or your custom discriminator column), the package will overwrite its mapping.entity_type) or extend MetadataListener to skip existing mappings.Schema Tool Limitations:
doctrine orm:schema-tool:create command may fail if the custom EntityManager isn’t used.EntityManager:
php -r "require 'vendor/autoload.php'; $em = \App\Services\DoctrineService::getAdoptionEntityManager(); $em->getConnection()->getSchemaManager()->createSchema();"
Circular Dependencies:
A → B and B → A causes infinite loops.Collector mappings or use a dependency graph library to detect cycles.Enable Logging:
Add a logger to MetadataListener to trace adoption events:
$listener = new MetadataListener($collector, new \Monolog\Logger('doctrine_adoption'));
Check Metadata: Inspect generated metadata after loading:
$metadata = $entityManager->getClassMetadata(ChildEntity::class);
dump($metadata->inheritanceType, $metadata->discriminatorMap);
Test with validateSchema:
Use orm:schema-tool:validate to catch mapping issues early:
php vendor/bin/doctrine orm:schema-tool:validate --em=adoption_em
Custom Adoption Logic:
Extend Collector to support runtime conditions:
class ConditionalCollector extends Collector {
public function addAdopteeIf(string $condition, string $parent, string $child, string $discriminator) {
if ($condition) {
$this->addAdoptee($parent, $child, $discriminator);
}
}
}
Post-Adoption Hooks: Trigger events after adoption (e.g., for caching or logging):
$collector->addAdoptee(ParentEntity::class, ChildEntity::class, 'child');
$collector->onAdoptionAdded(function ($parent, $child, $discriminator) {
// Log or cache the adoption
});
Database-Specific Quirks:
For PostgreSQL, ensure the discriminator column is VARCHAR (not TEXT) to avoid schema issues:
# config/packages/doctrine.yaml
doctrine:
dbal:
types:
inheritance_type: string
How can I help you explore Laravel packages today?