Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Many-To-One, Self-referencing with Inheritance Mapping

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 !

Update

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;
}
like image 490
TiPi Avatar asked Oct 30 '22 14:10

TiPi


1 Answers

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

like image 134
Mohamed Ramrami Avatar answered Nov 02 '22 09:11

Mohamed Ramrami