I have some entites with common relations and attributes. So, I want to simplify my schema using inheritance mapping.
I created a BaseData mappedsuperclass, and make my other entities expand it. This BaseData class has the common relations I need in each entity.
It works with many-to-one relation, like
/**
* @ORM\MappedSuperclass
*/
class BaseData
{
/**
* @ORM\ManyToOne(targetEntity="Service")
* @ORM\JoinColumn(name="service_id", referencedColumnName="id")
*/
protected $service;
But it become a little bit more tricky with self-referencing.
For instance, since I want to create a parent reference, I tried that :
/**
* @ORM\MappedSuperclass
*/
class BaseData
{
/**
* @ORM\ManyToOne(targetEntity="BaseData")
* @ORM\JoinColumn(name="parent_id", referencedColumnName="id", nullable=true)
*/
protected $parent;
Obviously, it lead to a TableNotFoundException when I try to query this entity : QLSTATE[42S02]: Base table or view not found: 1146 Table 'project.base_data' doesn't exist
.
So, I tried AssociationOverrides, but it seems that doesn't allow to change the Target Entity.
So, is there a way to build some self-reference on a MappedSuperclass ? And by the way, does it even make sense ?
Many thanks in advance !
Here is the anwser :
I defined the protected $parent
and protected $children
in my BaseData mappedSuperClass as planned. I annotated them with other information I need. eg :
/**
* @ORM\MappedSuperclass
*/
class BaseData
{
/**
* @Datagrid\Column(field="parent.id", title="datagrid.parent_id", visible=false, safe=false)
* @Serializer\Expose
* @Serializer\Groups({"foo"})
*/
protected $parent;
/**
* @Serializer\Expose
* @Serializer\Groups({"elastica"})
*/
protected $children;
Then, I add the ORM relation with the event loadClassMetadata.
/**
* @param LoadClassMetadataEventArgs $eventArgs
*/
public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs)
{
// the $metadata is all the mapping info for this class
$classMetadata = $eventArgs->getClassMetadata();
$reflObj = new \ReflectionClass($classMetadata->name);
if($reflObj) {
if ($reflObj->isSubclassOf('CoreBundle\Entity\BaseData')) {
$fieldMapping = array(
'targetEntity' => $classMetadata->name,
'fieldName' => 'parent',
'inversedBy' => 'children',
'JoinColumn' => array(
'name' => 'parent_id',
'referencedColumnName' => 'id',
'nullable' => true,
'onDelete' => 'SET NULL',
),
);
$classMetadata->mapManyToOne($fieldMapping);
$fieldMapping = array(
'fieldName' => 'children',
'targetEntity' => $classMetadata->name,
'mappedBy' => 'parent',
);
$classMetadata->mapOneToMany($fieldMapping);
}
}
}
Register the event, and that's it.
Now, every class which extends the BaseData superClass get the relation. For instance, php app/console doctrine:generate:entities MyBundle
will generates the following code inside the SubClass entity :
/**
* Set parent
*
* @param \MyBundle\Entity\Subclass $parent
*
* @return Subclass
*/
public function setParent(\MyBundle\Entity\Subclass $parent = null)
{
$this->parent = $parent;
return $this;
}
/**
* Get parent
*
* @return \MyBundle\Entity\Subclass
*/
public function getParent()
{
return $this->parent;
}
/**
* Add child
*
* @param \MyBundle\Entity\Subclass $child
*
* @return Subclass
*/
public function addChild(\MyBundle\Entity\Subclass $child)
{
$this->children[] = $child;
return $this;
}
/**
* Remove child
*
* @param \MyBundle\Entity\Subclass $child
*/
public function removeChild(\MyBundle\Entity\Subclass $child)
{
$this->children->removeElement($child);
}
/**
* Get children
*
* @return \Doctrine\Common\Collections\Collection
*/
public function getChildren()
{
return $this->children;
}
You can remove the mapping @ORM\ManyToOne(targetEntity="BaseData")
and create an event listener on the event loadClassMetadata. (I didn't test the following code, this is just a starting point) Something like this:
class TestEvent
{
public function loadClassMetadata(\Doctrine\ORM\Event\LoadClassMetadataEventArgs $eventArgs)
{
$classMetadata = $eventArgs->getClassMetadata();
$class = $classMetadata->getName();
$fieldMapping = array(
'fieldName' => 'parent',
'targetEntity' => $class,
);
$classMetadata->mapManyToOne($fieldMapping);
}
}
One important thing to notice is that a listener will be listening for all entities in your application.
See Doctrine docs about events And How to register event listener in symfony2