Add column mappings to an extended class in Doctrine - php

I have a PHP entity that inherits from a base class contained in a library which I cannot change. I would like to map properties that are inherited from the base class to database columns. Since the #Column annotation must be used on a field of method, I can not define the inherited columns that way. Using #AttributeOverrides also will not work as then I would need to have access to the base class to make it a MappedSuperclass.
Example of classes:
class LibraryClass
{
protected $someProperty;
}
/**
* #Entity
* #Table(name="child")
*/
class Child extends LibraryClass
{
/**
* #Column(name="some_property", type="string")
* Somehow target $this->someProperty
*/
}
Is there any other way to map parent properties without transferring the properties between classes and not using inheritance?

You could use mapping with XML or YML instead of annotations. It's documented here.
https://symfony.com/doc/current/doctrine.html#add-mapping-information

Related

Doctrine 2 how to properly map multi-level inheritance

I have multi-level inheritance doctrine entity like this:
/**
* #ORM\Entity
* #ORM\InheritanceType("JOINED")
* #ORM\DiscriminatorColumn(name="type", type="string")
* #ORM\DiscriminatorMap({"customer" = "CustomerUser",
* "admin" = "AdminUser", "stock" = "StockUser"})
*/
abstract class User { ... }
/** #ORM\Entity */
abstract class EmployeeUser extends User { ... }
/** #ORM\Entity */
class AdminUser extends EmployeeUser { ... }
/** #ORM\Entity */
class StockUser extends EmployeeUser { ... }
However it does not work this way, fields of EmployeeUser are neither read from database nor persisted.
I've found out that it works when I specify the discriminator map this way:
* #ORM\DiscriminatorMap({"customer" = "CustomerUser",
* "admin" = "AdminUser", "stock" = "StockUser", "EmployeeUser"})
it starts to work this way (there is no need to specify discriminator key for EmployeeUser - as it is abstract and will never be instantiated), but
I don't like when magic happen that I don't understand enough :) so my question is: is this a proper solution? To just let Doctrine know this way that this class is somehow included in inheritance hierarchy? Or should it be done anyhow else?
I haven't found any mention about how to do multiple level entity class inheritance in Doctrine docs.
I have same situation. I need more than one level of inheritance. That in your case is expected behavior, you need to list all of mapped classes in DiscriminatorMap.
My case don't have that map because I am using native transformation of ClassName to type key and it's working for classes on all inheritance level.
abstract ClassA
abstract ClassB extends ClassA
- protected someName
ClassC extends ClassB
When I save ClassC obj I have that property someName saved. You can try yourself without discriminatory map and see that all classes are mapped and save.
EDIT :
One more thing if you want to avoid multilevel inheritance you can always use trait for that. Just group the props to trait and add it to entity. There are good example of trait usage in DoctrineBehaviors bundle. There are using it to import additional capabilities to entities like blamable, loggable etc.

Map subclass as its extended parent

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?
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.
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...

Inheritance on doctrine's embeddables

Is it possible to use inheritance on value objects embedded in doctrine entities?
The situation I'm thinking about is:
I have an entity that has and embedded value object. That value object has the following hierarchy:
class myEntity {
/** #Embedded(class = "baseValueObject") */
private $value_object;
...
}
class baseValueObject {...}
class valueObject1 extends baseValueObject{...}
class valueObject2 extends baseValueObject2{...}
If I define my entity to have baseValueObject as an embeddable, nothing happens when I use the schema-tool to update my db schema, so I guess that's not the way to do it.
Another option that I'm thinking about is to use single-table inheritance on the entity to create a child entity that use one of the value objects, and another child entity for the other one. Like this:
class myEntity {
/** #Embedded(class = "baseValueObject") */
private $value_object;
...
}
class myEntityA extends myEntity {
/** #Embedded(class = "valueObject1") */
private $value_object;
...
}
class myEntityB extends myEntity {
/** #Embedded(class = "valueObject2") */
private $value_object;
...
}
class baseValueObject {...}
class valueObject1 extends baseValueObject{...}
class valueObject2 extends baseValueObject2{...}
What's the proper approach? Is it even possible to do it this way?
If you want to extend one embeddable from another you need to set the parents properties as protected not private.
https://github.com/doctrine/doctrine2/issues/4097
If you want to use your Value Object in field then you should define new type in doctrine http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/cookbook/custom-mapping-types.html
If you want inherit properties from base then you should use #MappedSuperclass annotation http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/inheritance-mapping.html
You should use #Embeddable when you want split entity by specific properties by creating specific entities. So you can't use Value Object as target. According to documentation (I'm not able to share third link).

Symfony2 / Doctrine mapped superclass in the middle of class table inheritance

I currently have a model structure as follows:
/**
* #ORM\Entity
* #ORM\InheritanceType("JOINED")
* #ORM\DiscriminatorColumn(name="related_type", type="string")
* #ORM\DiscriminatorMap({"type_one"="TypeOne", "type_two"="TypeTwo"})
*/
abstract class BaseEntity {
... (all the usual stuff, IDs, etc)
/**
* #ORM\OneToMany(targetEntity="Comment", mappedBy="baseEntity")
*/
private $comments;
}
/**
* #ORM\Entity
*/
class TypeOne extends BaseEntity {
/**
* #ORM\Column(type="string")
*/
private $name;
/**
* #ORM\Column(type="string")
*/
private $description;
}
/**
* #ORM\Entity
*/
class TypeTwo extends BaseEntity {
/**
* #ORM\Column(type="string")
*/
private $name;
/**
* #ORM\Column(type="string")
*/
private $description;
}
/**
* #ORM\Entity
*/
class Comment {
... (all the usual stuff, IDs, etc)
/**
* #ORM\ManyToOne(targetEntity="BaseEntity", inversedBy="comments")
*/
private $baseEntity;
}
The idea here is to be able to tie a comment to any of the other tables. This all seems to be working ok so far (granted, I'm still exploring design options so there could be a better way to do this...), but the one thing I've noticed is that the subclasses have some common fields that I'd like to move into a common parent class. I don't want to move them up into the BaseEntity as there will be other objects that are children of BaseEntity, but that won't have those fields.
I've considered creating a MappedSuperclass parent class in the middle, like so:
/**
* #ORM\MappedSuperclass
*/
abstract class Common extends BaseEntity {
/**
* #ORM\Column(type="string")
*/
private $name;
/**
* #ORM\Column(type="string")
*/
private $description;
}
/**
* #ORM\Entity
*/
class TypeOne extends Common {}
/**
* #ORM\Entity
*/
class TypeTwo extends Common {}
I figured this would work, but the doctrine database schema generator is complaining that I can't have a OneToMany mapping on a MappedSuperclass. I didn't expect this to be a problem as the OneToMany mapping is still between the root BaseEntity and the Comment table. Is there a different structure I should be using, or other way to make these fields common without adding them on the BaseEntity?
From the Docs:
A mapped superclass is an abstract or concrete class that provides
persistent entity state and mapping information for its subclasses,
but which is not itself an entity. Typically, the purpose of such a
mapped superclass is to define state and mapping information that is
common to multiple entity classes.
That said, how can you associate one entity with one that is not?
More from the docs:
A mapped superclass cannot be an entity, it is not query-able and
persistent relationships defined by a mapped superclass must be
unidirectional (with an owning side only). This means that One-To-Many
assocations are not possible on a mapped superclass at all.
Furthermore Many-To-Many associations are only possible if the mapped
superclass is only used in exactly one entity at the moment. For
further support of inheritance, the single or joined table inheritance
features have to be used.
Source: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/inheritance-mapping.html
Update
Because your MappedSuperClass extends BaseEntity it also inherits the BaseEntity's associations, as if it were its own. So you effectively DO have a OneToMany on a MappedSuperClass.
To get around it, well, you'd need to modify/extend doctrine to work the way you want.
As far as native functionality goes you have two options:
Class Table Inheritance
You Common class and the resulting DB representation would have the common fields and child classes will now only have the fields specific to themselves. Unfortunately this may be a misrepresentation of your data if you are simply trying to group common fields for the sake of grouping them.
Make Common an Entity
It appears that all a Mapped Super Class is is an Entity that isn't represented in the DB. So, make common a Entity instead. The downside is that you'll end up with a DB table, but you could just delete that.
I recommend that you take a second look at your data and ensure that you are only grouping fields if they are common in both name and purpose. For example, a ComputerBox, a ShoeBox, a Man, and a Woman may all have the "height" property but in that case I wouldn't suggest have a Common class with a "height" property that they all inherit from. Instead, I would have a Box with fields common to ComputerBox and ShoeBox and I'd have a Person with fields common to Man and Woman. In that situation Class Table Inheritance or single table if you prefer would work perfectly.
If your data follows that example go with Single Table or Class Table Inheritance. If not, I might advise not grouping the fields.

Doctrine 2.0: How to target an entity's superclass in a OneToOne relationship

I am not very good at asking questions but the code below should be self-explanatory.
I need to create a OneToOne association from a class to an entity's superclass which is NOT an entity.
/* Not an entity */
class Superclass {
/**
*#Id #Column(name="entity_id", type="integer") #GeneratedValue
**/
protected $id;
}
/**
* #Entity #Table(name="subclasses1")
**/
class Subclass1 extends Superclass {
}
/**
* #Entity #Table(name="subclasses2")
**/
class Subclass2 extends Superclass {
}
/**
* #Entity #Table(name="assoc")
**/
class Associationclass
{
/**
*OneToOne(targetEntity="Superclass")
**/
protected $association;
/**
*#Column(type="string")
**/
protected $info;
}
The question is: How can I reference both subclass1 and subclass2 using the OneToOne relationship without making Superclass an Entity (creating a new table and using discriminators)?
You can't. If you want that sort of inheritance (the kind you can use in associations), you need to model the inheritance in doctrine.
The association needs a "targetEntity" -- which, like the name indicates, must be an entity.
Unless there's a very good reason not to, go ahead and make your superclass an entity, and set up the inheritance in a way doctrine can understand.
The reason your superclass needs to be an entity is because the superclass and it's subclasses will then share an identifier. So with the identifier (and the discriminator), doctrine can then figure out that SuperClass#1234 is actually a SubClass2.

Categories