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...
Related
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
I am trying to subclass a Doctrine 2 entity to add a bunch of "helper" functions I'd like to use.
For example, this is my entity:
namespace Project\Entity;
class Product
{
private $name;
private $idProductCategory;
}
Mapping is done via XML files.
Then, I try to extend it:
namespace Project\Entity;
class ProductJSON extends Product {
public function toJSON() {
/* ... */
}
}
When I try to use this object in Doctrine:
$a = $entityManager->getRepository('\Project\Entity\ProductJSON');
I get the "No mapping file found named Project.Entity.ProductJSON.dcm.xml" error.
Which is perfectly right, because I do not want any additional mapping.
I've extensively read Doctrine docs and about Mapped Super Classes (Doctrine: extending entity class), but as far as I understand that is for extending Entities in a DB-sense.
I don't care about the database/mapping, I just want to extend entities PHP-wise to use the objects seamlessy in my application.
How to achieve this goal?
You do not need a subclass to add additional methods. Just add the methods to your entity class - Doctrine will just ignore them because there is no mapping information attached to them.
E.g.
class Product
{
private $name;
private $idProductCategory;
/* ... */
public function toJSON() {
/* ... */
}
}
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.
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).
I am using Silex with Doctrine ORM, everything was working properly but I got a problem that I can't figure out.
I have an Entity News in the namespace Lpdq\Model\Entity which extends another class News in the namespace Lpdq\Model which contains some methods and pre/post event methods for doctrine.
My entity News
<?php
namespace Lpdq\Model\Entity;
/**
* News
*
* #Table(name="news")
* #Entity(repositoryClass="Lpdq\Model\Entity\Repository\News")
*/
class News extends Lpdq\Model\News{
/*some properties/methods*/
}
My super class News
<?php
namespace Lpdq\Model;
/**
* News
*
* #MappedSuperclass
* #HasLifecycleCallbacks
*/
class News{
/**
* #PrePersist
*/
public function prePersist()
{
$this->setCreated(new \DateTime());
$this->setUpdated(new \DateTime());
}
/**
* #PreUpdate
*/
public function preUpdate()
{
$this->setUpdated(new \DateTime());
}
/*...some methods...*/
}
In my controller, I just instance my entity and persist/flush it
<?php
namespace Lpdq\Controller;
use Silex\Application;
use Symfony\Component\HttpFoundation\Request;
use Lpdq\Model\Entity\News;
class NewsController {
public function addAction( Request $request, Application $app) {
$news = new News();
$news->setTitle('test');
/*...*/
$app['orm.em']->persist($news);
$app['orm.em']->flush();
/*...*/
}
}
My problem is that my prePersist/preUpdate methods are not called when I persist my entity.
(So I get an error because my properties created and updated are null)
If I set my entity News as HasLifecycleCallbacks and put the same prePersist/Update method, they are triggered.
While I am here, I am wondering if the way I extends my entities to put pre/post and other methods are a good or bad practice?
If you have multiple entities which need same set of methods then having a base class news makes sense, if only one entity is extending the class news then it's a overkill and you can put the code in your entity class itself.
The general pattern is if you have multiple entities and all for them have created and updated field then you should create a base class and all such entities should extend it.
You need to have the annotation HasLifecycleCallbacks to enable Lifecycle callbacks. If the lifecycle events are applicable for all entities you are extending from base class then you should put in the annotation in base class otherwise put it in individual classes.
You have Lpdq\Model\Entity\News extending Lpdq\Model\News which is at least confusing.
You're also only showing partial implementation- make sure that setTitle() actually updates tracked model properties for Doctrine to identify the instance as dirty. Otherwise the flush events won't be called.