Ventus
Ventus

Reputation: 150

Doctrine ORM and factories with abstract classes strategy

So I've stumbled upon this hurdle where I have to create an abstract class and a factory to create objects of more specific classes that extend the abstract class and implement more specific object methods.

Simply said, I got a SocialMediaAbstract class. Extending classes are Facebook, Instagram, and they implement a SocialMediaInterface. Facebook, Instagram etc are all saved in the db, with an id, a name and several more properties that are all used among the extending classes, hence an abstract class.

Because I want to be able to query several things from the SocialMedia Objects, and every social media platform have their own APIs for it, I made the interface and created the different classes so they can all have their own implementations of those methods.

Now, the problem is of course with my abstract class and Doctrine. Doctrine says this on their website regarding inheritance:

A mapped superclass cannot be an entity, it is not query-able [...]

Now if I had a SocialMediaFactory and threw in an ID, I would like to get the respective Object of, for example, class Facebook or Instagram back. I don't want to know exactly which SocialMedia it is when I collect them. Now that is a problem with doctrine, at least that's what I think it is.

Am I overlooking something, is the factory pattern still possible? Or should I really just remove the abstract class, and create a factory that searches in every table of a SocialMediaInterface implementing class, which seems highly inefficient and unmaintable when an application gets bigger.

Any insight or pointers would be appreciated, since I'm sure this problem must've come up more often. I tried googling and searching on Stackoverflow itself, but I couldn't get any relevant questions or answers.

Thank you very much in advance.

EDIT: I came across this interesting possibility: Class Table Inheritance. This would mean adding:

 * @ORM\InheritanceType("JOINED")
 * @ORM\DiscriminatorColumn(name="discr", type="string")
 * @ORM\DiscriminatorMap({"facebook" = "Facebook", "instagram" = "Instagram"})

to my code. I had high hopes, but sadly enough the validator gave me this error:

[Doctrine\ORM\Mapping\MappingException] It is not supported to define inheritance information on a mapped superclas s 'Portal\SocialMedia\Entity\SocialMediaAbstract'.

A shame mapper superclasses are not supported.

EDIT 2/CONCLUSION: I've decided to go with Class Table Inheritance (just like the answer below suggested). Removing the abstract from the class made it possible to still use my factory.

I am using a concrete class as an abstract class now however, which feels wrong. I've documented in docblock that no objects should be instantiated from this class.

One little sidenote: Doctrine's Entity Manager more or less already provides the Factory:

$socialMedia = $entityManager->find('Portal\SocialMedia\Entity\SocialMedia', 2);

This returns an Instagram object. I still suggest you build your own factory above it for maintainability later as the SocialMedia entity might change later on.

Upvotes: 2

Views: 1632

Answers (1)

SaSchu
SaSchu

Reputation: 513

Some time has passed now since I worked with doctrine, but if I remember correctly, doctrine's mapped super classes are an implementation of the concrete table inheritance pattern by Martin Fowler.

In the example mentioned there, the Player is the mapped super class, whose attributes are distributed to all inheriting entities / models. The point here is that a player can't be instantiated and thus has no own id. Instead, every inheriting model got it's own id, which are all independent of each other.

I think the pattern you are looking for is either single table inheritance or class table inheritance (have a look at doctrine's inheritance types).


Single table inheritance is implemented in doctrine's inheritance type "SINGLE_TABLE", where you have one table for all entities. They are sharing the exact same attributes and same id pool, meaning you can "throw in" an id, get the object and check the type (Facebook, Instagram etc..).

The downside is that if you got in any of the entites an attribute that may be NULL, you could run into problems if the other entites don't have this attribute or don't need it. This would mean you have to set the given attribute to a dummy value in the other entities to save them into the database table.


Class table inheritance overcomes this issue by saving every entity in its own table, while still being able to share the id pool, because doctrine takes care that the common attributes are saved in the base class table, while all the attributes specific to an entity are saved in the entity's table. The tables are then joined by the id, hence the inheritance type "JOINED" in doctrine.


Conclusion:

Use single table inheritance if the classes are very similar and only differ in function definition or implementation, but have the same attributes.

Use class table inheritance if the classes have distinct attributes that would be problematic to store in a single table.

Use concrete table inheritance if the classes are not really related to each other, but only share a small amount of common attributes. But this could also be implemented through PHP's traits, which in my opinion is easier and more flexibly to use than doctrine's mapped super class. In a PHP trait you can also use doctrine's annotations, because the PHP interpreter will properly assign the annotations to the classes you use the traits in.

You should still be able to use your SocialMediaFactory with either single table or class table inheritance pattern.

Upvotes: 2

Related Questions