Doctrine ORM and abstract entities

Hey,

I 've got an issue with aggregating entities in Doctrine ORM 2.x. I 've seen the ResolveTargetEntityListener solution provided by Doctrine. This seems to work for exactly one abstraction as seen in the Doctrine documentation. But what if I have more than one inheritation from an abstract entity or more than one implementation of an interface?

A short example …

declare(strict_types=1);
namespace Application\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\MappedSuperclass
 */
abstract class Partner
{
    /**
     * @ORM\Column(name="name", type="string", length=255, nullable=false)
     */
    protected ?string $name;
}

As you can see, this is the abstract entity from which other entities will derive. The inherited entities are somewhat like the following two. For the example, I will mention only two. There are far more entities.

declare(strict_types=1);
namespace Application\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * Represents a natural person
 *
 * @ORM\Entity
 * @ORM\Table(name="natural_person")
 */
class NatualPerson extends Partner
{
    /**
     * @var int
     * @ORM\Id @ORM\Column(type="integer", name="id")
     * @ORM\GeneratedValue
     */
    protected int $id;
}

… and the other one …

declare(strict_types=1);
namespace Application\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * Represents a legal person (companies)
 *
 * @ORM\Entity
 * @ORM\Table(name="legal_person")
 */
class LegalPerson extends Partner
{
    /**
     * @var int
     * @ORM\Id @ORM\Column(type="integer", name="id")
     * @ORM\GeneratedValue
     */
    protected int $id;
}

A group can contain zero or more partners. A collection of natural and legal persons.

declare(strict_types=1);
namespace Application\Entity;

use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity
 * @ORM\Table(name="group")
 */
class Group
{
    /**
     *
     * @var int
     * @ORM\Id @ORM\Column(type="integer", name="id")
     * @ORM\GeneratedValue
     */
    protected $id;
    
    /**
     * @ORM\ManyToOne(targetEntity="Application\Entity\Partner")
     * @var Partner
     */
    protected Collection $members;

    public function __construct()
    {
        $this->members = new ArrayCollection();
    }
}

If I 'm using the example from the above mentioned doctrine cookbook example, this won 't work.

$evm  = new \Doctrine\Common\EventManager;
$rtel = new \Doctrine\ORM\Tools\ResolveTargetEntityListener;

// Adds a target-entity class
$rtel->addResolveTargetEntity(Partner::class, LegalPerson::class, []);
$rtel->addResolveTargetEntity(Partner::class, NaturalPerson::class, []);

// Add the ResolveTargetEntityListener
$evm->addEventListener(Doctrine\ORM\Events::loadClassMetadata, $rtel);

$em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config, $evm);

Question: Is there another smart way to reach something like aggregation based on an abstract entity or an interface, that aggregates to more than one target entity?

I don’t think you need ResolveTargetEntityListener at all, based on your example.

The purpose of ResolveTargetEntityListener is to resolve an interface to a class at runtime, which is useful if you’re trying to decouple modules. In the cookbook example, the idea is to avoid having the CustomerModule and InvoiceModule not depend on each other. In the example, an Invoice can have a subject which must implement an interface. RTEL allows the Application module to compose the two (independent) modules.

You don’t appear to be doing that in your example. It looks like you simply want to target an interface or abstract class in an association. You can just do that directly, without involving RTEL.

It looks like you don’t need anything that isnt’ described in the Inheritance Mapping chapter of the ORM documenation.

Thanks for your answer tim. But my problem is not inheritance mapping. My problem is a relation to an entity property, which is an interface or an abstract class. Perhaps my example was not clear enough.

What happens, when my Group class collects both NaturalPerson and LegalPerson entities in its members collection? Both are inheritations of Partner. How does doctrine get the join tables and columns?

It’s possible I’m missing something, but from memory, you can just target the mapped superclass and it will just work. What breaks if you just remove all the RTE related code?

After reviewing the docs (it’s been some time since I did this), I realize you can’t use mapped superclass like that.

What you probably want is class-table inheritance with an abstract Partner entity. You’ll end up with three tables in your schema.

Like this: GitHub - timdev/doctrine-inheritance-demo: https://discourse.laminas.dev/t/doctrine-orm-and-abstract-entities/2197/3

1 Like

Hi,

My dumb question would be, why are you making a legal person, a neutral person as an entity? Those are characteristic of a person. So, shouldn’t it be a characteristic entity rather than each entity for each characteristic? Sorry, just wanted to understand your approach. You must have some reason behind it. Maybe you’re dealing with some legacy code. Therefore, I’m giving a view of what I understood. Thanks!

Sorry for the late reply and a big thanks for the demo. That is exactly what I 'm looking for.
Very good approach. I 'll use this.

Again, thank you and happy easter everyone.

1 Like

Because legal persons and natural persons have different properties. The abstract partner class contains the properties that both natural and legal persons can have. The derived classes for legal and natural persons contain the specific properties that apply only to legal or natural persons.

Short example: A natural person has a $prename property. Legal persons do not have a $prename property. Legal persons have a $sales property, while natural persons have not. Both of them have a $name property.

Conclusion: There are to many properties that differ between legal and natural persons. At the same time they have properties in common. So there has to be an abstraction from which both of them inherit.