7

I have created the following abstract class, which use single table inheritance and maps subclasses on the DiscriminatorColumn model.

/**
 * @Entity
 * @Table(name="entity")
 * @InheritanceType("SINGLE_TABLE")
 * @DiscriminatorColumn(name="model", type="string")
 * @DiscriminatorMap({
 *      "green" = "model\GreenEntity",
 *      "blue"  = "model\BlueEntity"
 * })
 */
abstract class AbstractEntity
{
    /** @Id @Column(type="string") */
    protected $entity_id;
}

Let's say I extend the abstract class AbstractEntity by some classes:

class GreenEntity extends AbstractEntity {}
class BlueEntity extends AbstractEntity {}

And extend these by some more subclasses

class GreenEntityChildOne extends GreenEntity {}
class GreenEntityChildTwo extends GreenEntity {}
class BlueEntityChildOne extends BlueEntity {}
class BlueEntityChildTwo extends BlueEntity {}

Now, for example, when I instantiate GreenEntityChildOne and persist it to the database, it will throw an exception that I don't have a mapping for it.

What I'm trying to do is get GreenEntityChildOne to be mapped as GreenEntity (or rather, every class which extends a class below AbstractEntity to be mapped as the class which extends the upper abstract class).

Is this at all possible?

Bas Peeters
  • 3,269
  • 4
  • 33
  • 49

2 Answers2

1

It's not possible with pure annotations

Yes, the mapping you are trying to achieve is possible. However, not with pure annotations. The important thing is that Doctrine needs to know all sub classes at runtime. If you do not want to state them explicitly in the annotations of the mapped superclass, you will need to dynamically provide them.

Doctrine event system to the rescue

There is a great blog post on dynamic mapping with Doctrine, which explains how you can use Doctrine event listeners to programmatically change the loaded ClassMetadata. To dynamically add subclasses to the discriminator map you can implement a Doctrine event listener like the following:

class DynamicDiscriminatorMapSubscriber implements EventSubscriber
{
    public function getSubscribedEvents()
    {
        return array(Events::loadClassMetadata);
    }

    public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs)
    {
        $metadata = $eventArgs->getClassMetadata();
        $metadata->addDiscriminatorMapClass("GreenEntityChildOne", GreenEntityChildOne::class);
    }
}

Register your subscriber

Now you only need to register the event subscriber with Doctrine. Ideally, you inject the classes you want to add based on your configuration to the event subscriber.

// create dynamic subscriber based on your config which contains the classes to be mapped
$subscriber = new DynamicDiscriminatorMapSubscriber($config);
$entityManager->getEventManager()->addEventSubscriber($subscriber);

Further reading

Also, have a look at the PHP mapping section in the Doctrine manual and the more informative API docs for the ClassMetadataBuilder.

Fabian Kleiser
  • 2,988
  • 3
  • 27
  • 44
0

Answer is possibly on the Doctrine Docs:

"All entity classes that is part of the mapped entity hierarchy (including the topmost class) should be specified in the @DiscriminatorMap"

http://doctrine-orm.readthedocs.org/en/latest/reference/inheritance-mapping.html

You've only specified GreenEntity and BlueEntity.

I don't know what I'm talking about. This is the first thing I've ever read about Doctrine...

markdwhite
  • 2,360
  • 19
  • 24
  • I've only specified GreenEntity and BlueEntity because I only want AbstractEntity to map (to know about) those. The classes that extend them are in a library, so some instances of the core app use them, some not. I want AbstractEntity to be completely agnostic towards the classes which extend its subclasses while still being able to extend them by mapping them as their parent. – Bas Peeters Sep 16 '15 at 14:43
  • 1
    @ManeatingKoala - I really don't have a clue and thought I would just try to help out in the absence of other responses. So I'll leave it at that. – markdwhite Sep 17 '15 at 00:28