arthem/object-reference-bundle
Installation:
composer require arthem/object-reference-bundle
Add the bundle to config/bundles.php:
return [
// ...
Arthem\ObjectReferenceBundle\ArthemObjectReferenceBundle::class => ['all' => true],
];
First Use Case:
Define an entity with a polymorphic reference using the #[ObjectReference] attribute. Example:
use Arthem\ObjectReferenceBundle\Mapping\Attribute\ObjectReference;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
class Post
{
#[ORM\Column(type: 'string', length: 36, nullable: true)]
#[ObjectReference(keyLength: 15)]
private \Closure|Comment $comment;
private $commentId; // Required field
private $commentType; // Required field
// Getter/setter logic (see README)
}
Database Schema:
Run migrations to ensure the *Id and *Type columns are created:
php bin/console doctrine:migrations:diff
php bin/console doctrine:migrations:migrate
Polymorphic References:
Use #[ObjectReference] for entities that can reference multiple types (e.g., User, Group, or Service).
#[ObjectReference(keyLength: 20)]
private \Closure|(User|Group) $owner;
Lazy Loading: Implement closures in getters to defer object hydration until needed:
public function getOwner(): ?(User|Group)
{
if ($this->owner instanceof \Closure) {
$this->owner = $this->owner->call($this);
}
return $this->owner;
}
Type Safety:
Leverage PHP 8+ union types (User|Group) in method signatures for IDE autocompletion and runtime checks.
Repository Integration:
Query polymorphic references using Doctrine’s SingleTableInheritance or ClassTableInheritance strategies:
$posts = $entityManager->createQueryBuilder()
->select('p')
->from(Post::class, 'p')
->where('p.commentType = :type')
->setParameter('type', Comment::class)
->getQuery()
->getResult();
Form Handling:
Use Symfony’s DataTransformer or ChoiceType with custom query builders to handle polymorphic selections:
$builder->add('owner', EntityType::class, [
'class' => [User::class, Group::class],
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('u')
->where('u.id = :id')
->orWhere('u.type = :type');
},
]);
Missing Fields:
Forgetting to declare $*Id and $*Type fields will cause Doctrine to fail during schema validation. Always include them, even if unused.
Closure Serialization:
Closures are not serializable by default. Ensure your entity’s __serialize()/__unserialize() methods handle them:
public function __serialize(): array
{
return [
'actor' => $this->actor instanceof \Closure ? null : $this->actor,
'actorId' => $this->actorId,
'actorType' => $this->actorType,
];
}
Key Length Mismatch:
The keyLength in #[ObjectReference] must match the actual length of the referenced entity’s ID column (e.g., uuid = 36, integer = 10).
Circular References: Avoid bidirectional references with closures to prevent infinite loops in getters/setters.
Query Logs: Enable Doctrine debug mode to inspect generated SQL for polymorphic queries:
$entityManager->getConnection()->getConfiguration()->setSQLLogger(new \Doctrine\DBAL\Logging\EchoSQLLogger());
Type Validation:
Use instanceof checks in getters to handle cases where the referenced object is not yet loaded:
if ($this->owner === null && $this->ownerId !== null) {
$this->owner = $entityManager->find($this->ownerType, $this->ownerId);
}
Event Listeners:
Attach lifecycle listeners to populate closures during postLoad:
$entityManager->getEventManager()->addEventListener(
\Doctrine\ORM\Events::postLoad,
function ($event) {
$entity = $event->getObject();
if (method_exists($entity, 'initializeClosures')) {
$entity->initializeClosures($event->getEntityManager());
}
}
);
Custom Reference Strategies:
Override the bundle’s ObjectReferenceType to support non-standard ID formats (e.g., custom UUIDs):
use Arthem\ObjectReferenceBundle\DBAL\Types\ObjectReferenceType;
class CustomObjectReferenceType extends ObjectReferenceType
{
public function convertToDatabaseValue($value, AbstractPlatform $platform)
{
// Custom logic here
}
}
Validation Constraints: Add Symfony Validator constraints to enforce reference integrity:
use Symfony\Component\Validator\Constraints as Assert;
#[ORM\Column(type: 'string', length: 36)]
#[ObjectReference(keyLength: 15)]
#[Assert\Type(type: 'object', message: 'Invalid reference type')]
private \Closure|Actor $actor;
API Serialization:
Use Symfony Serializer’s @Groups to control exposure of polymorphic references:
#[Groups(['api'])]
public function getActor(): ?Actor
{
// ...
}
How can I help you explore Laravel packages today?