A Symfony bundle for extending Doctrine ORM entities with custom attribute-driven metadata. Define PHP attributes on your entities and let autoconfigured mapping drivers turn them into cacheable, serializable metadata — with full support for embedded classes and multiple entity managers.
MappingDriverInterface and drivers are auto-tagged via Symfony DIloadClassMetadata to load extension metadata alongside Doctrine's own metadataspl_object_id scopingcomposer require chamber-orchestra/metadata-bundle
Create a mapping driver by extending AbstractMappingDriver. Override getClassAttribute() or getPropertyAttribute() to declare which PHP attributes your extension requires:
use ChamberOrchestra\MetadataBundle\Mapping\Driver\AbstractMappingDriver;
use ChamberOrchestra\MetadataBundle\Mapping\ExtensionMetadataInterface;
class TimestampableDriver extends AbstractMappingDriver
{
protected function getClassAttribute(): string|null
{
return Timestampable::class;
}
public function loadMetadataForClass(ExtensionMetadataInterface $extensionMetadata): void
{
// Read attributes and populate your metadata configuration
}
}
Drivers implementing MappingDriverInterface are automatically tagged and registered by the bundle.
Extend AbstractExtensionMetadataFactory to define how your extension metadata is created and loaded:
use ChamberOrchestra\MetadataBundle\Mapping\AbstractExtensionMetadataFactory;
use ChamberOrchestra\MetadataBundle\Mapping\ExtensionMetadataInterface;
use Doctrine\Persistence\Mapping\ClassMetadata;
class TimestampableMetadataFactory extends AbstractExtensionMetadataFactory
{
protected function newClassMetadataInstance(ClassMetadata $metadata): ExtensionMetadataInterface
{
return new ExtensionMetadata($metadata);
}
protected function doLoadMetadata(ExtensionMetadataInterface $class): void
{
// Delegate to your mapping drivers
}
}
Use AbstractDoctrineListener to access extension metadata during Doctrine lifecycle events:
use ChamberOrchestra\MetadataBundle\EventSubscriber\AbstractDoctrineListener;
use Doctrine\Bundle\DoctrineBundle\Attribute\AsDoctrineListener;
use Doctrine\ORM\Event\PrePersistEventArgs;
use Doctrine\ORM\Events;
#[AsDoctrineListener(event: Events::prePersist)]
class TimestampableListener extends AbstractDoctrineListener
{
public function prePersist(PrePersistEventArgs $args): void
{
foreach ($this->getScheduledEntityInsertions($args->getEntityManager(), TimestampableConfiguration::class) as $metadataArgs) {
$metadataArgs->extensionMetadata->setFieldValue(
$metadataArgs->entity,
'createdAt',
new \DateTimeImmutable()
);
}
}
}
MetadataSubscriber (Doctrine loadClassMetadata event)
└── MetadataReader
└── AbstractExtensionMetadataFactory
├── MappingDriverInterface[] (attribute-based mapping drivers)
├── ExtensionMetadataInterface (per-entity extension metadata)
│ ├── MetadataConfigurationInterface[] (per-driver configurations)
│ └── Embedded metadata (recursive for embeddables)
└── PSR-6 Cache (serialized metadata storage)
| Class / Interface | Role |
|---|---|
MappingDriverInterface |
Extension point — implement to define attribute-driven metadata |
AbstractMappingDriver |
Base driver with AttributeReader and supports() logic |
MetadataConfigurationInterface |
Stores field mappings; serializable for Doctrine cache |
AbstractDoctrineListener |
Base for Doctrine listeners that need extension metadata |
MetadataArgs |
DTO bundling EntityManager, metadata, configuration, and entity |
composer install # Install dependencies
composer test # Run the test suite
MIT
How can I help you explore Laravel packages today?