Doctrine+Symfony: adding indexes to fields defined in traits

Alex Kunin
2 min readJul 11, 2019

--

Unrelated but beautiful photo by Joanna Malinowska

The task: make a trait with some columns that you can reuse in entity classes. Basically it just works:

<?php
namespace App\Entity\Utils;

use Doctrine\ORM\Mapping as ORM;
trait MyTrait
{
/**
* @var string
* @ORM\Column("some_field", type="string")
*/
protected $someField;

public function setSomeField(string $someField): self
{
$this->someField = $someField;
return $this;
}

public function getSomeField(): string
{
return $this->someField;
}
}

And later in the entity class:

<?php
namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
*/
class MyEntity
{
use App\Entity\Utils\MyTrait;
}

But what if you need to have an index on your reused column? You can’t add it to trait as Doctrine wants to see @ORM\Table only on real tables. And you don’t want to add it on every entity where you use that trait — that’s the opposite of code reuse!

What you need to do is an event listener that will add index to all relevant entities automatically:

<?php
namespace App\Utils;

use App\Entity\Utils\MyTrait;
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Event\LoadClassMetadataEventArgs;

class MyEntityListener implements EventSubscriber
{
public function getSubscribedEvents()
{
return [
'loadClassMetadata',
];
}

public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs)
{
$cm = $eventArgs->getClassMetadata();
$class = $cm->getName();
$uses = class_uses($class);

if (in_array(MyTrait::class, $uses)) {
$cm->table['indexes'][] = [
'columns' => [
'some_field',
],
];
}
}
}

And don’t forget to register the listener in services.yaml:

services:
App\Utils\MyEntityListener:
tags:
- { name: doctrine.event_listener, event: loadClassMetadata }

Note missing key above in $cm->table['indexes'][]: you technically can specify index name there, but this means all entities with that trait will have same index name. This is not a problem for MySQL (I guess index name is local to owning table), but in SQLite you’ll get a duplicate index error. So, by omitting index name you tell Doctrine to generate unique name every time it is used.

Similar technique works for inherited tables with @ORM\MappedSuperclass, just replace class_uses/in_array with instance of.

--

--

No responses yet