Installation Add the package via Composer:
composer require api-platform/doctrine-common
Ensure api-platform/core and doctrine/doctrine-bundle are also installed (dependencies).
First Use Case
Use this package to leverage Doctrine Common utilities (e.g., ClassMetadata, ProxyFactory, PersistenceObjectWrapper) in API Platform projects.
Example: Extend a Resource class to use ClassMetadata for dynamic property checks:
use ApiPlatform\Core\DataTransformer\CollectionDataProviderInterface;
use Doctrine\Common\Persistence\ObjectManager;
use Doctrine\Common\Util\ClassUtils;
class CustomDataProvider implements CollectionDataProviderInterface
{
public function __construct(private ObjectManager $manager) {}
public function supports(string $resourceClass, string $operationName = null, array $context = []): bool
{
return ClassUtils::getClass($resourceClass) === MyEntity::class;
}
public function getCollection(string $resourceClass, string $operationName = null, array $context = []): array
{
$metadata = $this->manager->getClassMetadata($resourceClass);
// Use metadata to filter/transform data
return [...];
}
}
Where to Look First
api-platform/core for how it uses Doctrine Common internally (e.g., ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\AbstractFilter).Dynamic Metadata Access
Use ClassMetadata to inspect entity properties at runtime (e.g., for custom serialization/deserialization):
$metadata = $this->manager->getClassMetadata(MyEntity::class);
$propertyNames = $metadata->getFieldNames(); // Get all field names
$lifecycleCallbacks = $metadata->getLifecycleCallbacks(); // Get lifecycle hooks
Proxy Object Handling
Leverage Doctrine\Common\Proxy\ProxyFactory to generate proxies for lazy-loading or custom logic:
use Doctrine\Common\Proxy\ProxyFactory;
$proxyFactory = new ProxyFactory();
$proxy = $proxyFactory->getProxy(new \ReflectionClass(MyEntity::class), $entityId);
Persistence Object Wrapping
Use Doctrine\Common\Persistence\ObjectManager to wrap entities for custom persistence logic:
$wrapped = $this->manager->getRepository(MyEntity::class)->find($id);
$wrapper = new \Doctrine\Common\Persistence\ObjectWrapper($wrapped);
$wrapper->setProperty('name', 'New Name'); // Custom logic
Integration with API Platform Filters
Extend API Platform’s built-in filters (e.g., SearchFilter, OrderFilter) by using Doctrine Common’s ClassMetadata to validate or transform filter inputs:
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\AbstractFilter;
use Doctrine\Common\Util\ClassUtils;
class CustomFilter extends AbstractFilter
{
protected function filterProperty(string $property, $value, array $resourceClass, string $operationName = null, array $context = []): bool
{
$metadata = $this->manager->getClassMetadata($resourceClass[0]);
if (!$metadata->hasField($property)) {
return false; // Skip invalid properties
}
return true;
}
}
Event Subscribers
Use Doctrine Common’s EventManager to tap into lifecycle events (e.g., onFlush, postPersist):
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Event\LifecycleEventArgs;
class CustomSubscriber implements EventSubscriber
{
public function getSubscribedEvents(): array
{
return ['postPersist'];
}
public function postPersist(LifecycleEventArgs $args): void
{
$entity = $args->getObject();
if ($entity instanceof MyEntity) {
// Custom logic
}
}
}
Dependency Injection
The package is auto-wired via Symfony’s DI. Inject ObjectManager, ClassMetadataFactory, or ProxyFactory directly into services:
# config/services.yaml
services:
App\Service\CustomService:
arguments:
$manager: '@doctrine.orm.entity_manager'
API Platform Resource Classes
Extend ApiPlatform\Core\Annotation\ApiResource classes to use Doctrine Common for dynamic behavior:
use ApiPlatform\Core\Annotation\ApiResource;
use Doctrine\Common\Util\ClassUtils;
#[ApiResource]
class MyEntity
{
public function getDynamicProperty(): string
{
$metadata = $this->manager->getClassMetadata(self::class);
return $metadata->hasField('dynamic_field') ? 'Yes' : 'No';
}
}
Testing
Mock ClassMetadata or ObjectManager in tests using PHPUnit:
$metadata = $this->createMock(\Doctrine\ORM\Mapping\ClassMetadata::class);
$metadata->method('getFieldNames')->willReturn(['id', 'name']);
$this->manager->expects($this->any())->method('getClassMetadata')->willReturn($metadata);
Circular Dependencies
Avoid circular references when using ClassMetadata or proxies. Doctrine Common throws exceptions for invalid proxy configurations:
// ❌ Avoid this in proxies
$proxy->getSomething()->getSomethingElse(); // May cause infinite recursion
Metadata Cache Invalidation Changes to entity mappings (e.g., adding/removing fields) require clearing the metadata cache:
php bin/console doctrine:cache:clear-metadata
Or programmatically:
$this->manager->getMetadataFactory()->getCache()->deleteAll();
Proxy Generation Issues
Proxies may fail if the target class is not properly configured for proxying (e.g., missing __clone() or __sleep()). Ensure your entity uses Doctrine’s proxyable traits:
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
class MyEntity
{
// No need for explicit traits; Doctrine handles it by default
}
Performance Overhead
Frequent calls to getClassMetadata() can be expensive. Cache results if used repeatedly:
$metadataCache = [];
$metadata = $metadataCache[$resourceClass] ?? $this->manager->getClassMetadata($resourceClass);
$metadataCache[$resourceClass] = $metadata;
Inspect Metadata Dump metadata to debug entity mappings:
$metadata = $this->manager->getClassMetadata(MyEntity::class);
dump($metadata->getFieldNames(), $metadata->getAssociationNames());
Proxy Debugging Enable proxy logging to trace proxy generation:
# config/packages/dev/doctrine.yaml
doctrine:
orm:
proxy_dir: '%kernel.cache_dir%/doctrine/orm/Proxies'
proxy_namespace: Proxies
logging: true
Event Subscriber Debugging Use Symfony’s debug toolbar to inspect Doctrine events:
// Temporarily dump events in a subscriber
public function getSubscribedEvents(): array
{
dump('Subscriber loaded'); // Check if subscriber is registered
return ['postPersist'];
}
Custom Metadata Drivers
Extend Doctrine\Common\Persistence\Mapping\Driver\MappingDriverChain to add custom metadata sources (e.g., YAML/JSON overrides):
use Doctrine\Common\Persistence\Mapping\Driver\MappingDriverChain;
class CustomDriver extends MappingDriverChain
{
public function loadMetadataForClass($className, ClassMetadata $metadata)
{
parent::loadMetadataForClass($className, $metadata);
// Override or extend metadata
}
}
Dynamic Proxies Create custom proxy factories for non-Doctrine entities:
use Doctrine\Common\Proxy\Proxy;
class CustomProxyFactory extends \Doctrine\Common\Proxy\ProxyFactory
{
protected function initializeProxy(Proxy $proxy): void
{
// Custom initialization logic
}
}
API Platform State Processor Use Doctrine Common in `State
How can I help you explore Laravel packages today?