I have some problem using Doctrine and inheritance in Symfony 5.
First, I have a base entity with only 2 dates (createdAt and updatedAt).
/**
* #ORM\MappedSuperclass()
*/
class BaseEntity
{
/**
* #ORM\Column(type="datetime")
*/
protected $createdAt;
/**
* #ORM\Column(type="datetime")
*/
protected $updatedAt;
}
Next, I have another class extending the BaseEntity (for example "MyClass1").
/**
* #ORM\Entity(repositoryClass=MyClass1Repository::class)
* #ORM\MappedSuperclass()
*/
class MyClass1 extends BaseEntity
{
/* Some properties */
}
Finally, I have my class extending MyClass1 (for example "MyClass2").
/**
* #ORM\Entity(repositoryClass=MyClass2Repository::class)
*/
class MyClass2 extends BaseEntity
{
/* Some properties */
}
When I migrate my database (make:migration), Doctrine creates 2 tables :
myclass1
myclass2
Can you tell me where I am wrong ? I'd have only 1 table : myclass2.
The mistake is in the MyClass1 definition. From the Doctrine documentation about inheritance mapping.
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.
A Mapped superclass itself is not an entity, by adding the #Entity next to the #MappedSuperclass you're effectively making it an entity. The mapped superclass should not be an entity and is not query-able.
Related
Currently I use the storage agnostic pattern in my project. This pattern is pretty simple: I've my Model\Model and my specific storage layer that extends of Model\Model (like Entity\Model and Document\Model).
The problem I'm facing is with polymorphic objects. If I don't extend an entity Doctrine does not recognize the inheritance mapping. In that way I need to replicate my code for every storage layer:
class Model\Option {}
class Model\OptionNumber extends Option {}
class Entity\Model extends Model\Option {}
I would like to have:
class Entity\OptionNumber extends Model\OptionNumber{}
But it's not possible. I've to code in Entity\OptionNumber once PHP have no support for multiple inheritance.
Is there any pattern that solve this problem?
Just for reference, I'm using Doctrine 2.2 and Symfony 2.3.
Sure!
Model:
abstract class Option implements OptionInterface
{
/**
* #var int
*/
protected $id;
/**
* #var string
*/
protected $name;
/**
* #var string
*/
protected $presentation;
/**
* #var Collection of OptionChoiceInterface
*/
protected $choices;
}
Entity:
class Option extends BaseOption
{
}
Document:
class Option extends BaseOption
{
}
I have abstract entity (App - base core):
namespace App\Bundles\AppBundle\Entity;
abstract class App extends ContainerAware implements AppInterface
{
// .....
}
and self entity:
namespace AppRus\Bundles\AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM,
App\Bundles\AppBundle\Entity\App as BaseApp;
/**
* App
*
* #ORM\Table(name="apps")
* #ORM\Entity(repositoryClass="AppRus\Bundles\AppBundle\Entity\AppRepository")
*/
class App extends BaseApp
{
// ....
}
and entity for control revisions:
namespace App\Bundles\AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* AppHistory
*
* #ORM\Table(name="apps_history")
* #ORM\Entity(repositoryClass="App\Bundles\AppBundle\Entity\AppHistoryRepository")
*/
class AppHistory
{
/**
* #var integer
*
* #ORM\ManyToOne(targetEntity="App\Bundles\AppBundle\Entity\App")
* #ORM\JoinColumn(name="app_apple_id", referencedColumnName="apple_id")
*/
private $app;
}
I can't create relation AppHistory#app to App#apple_id
When I set entity to abstract class App, then doctrine create a new table "App"
When I set MappedSuperClass to abstract class App, I have error: http://docs.doctrine-project.org/en/latest/reference/inheritance-mapping.html#mapped-superclasses
How can I create relation from AppHistory to abstract core App?
My understanding is that that's not possible. I think you're doing things the wrong way, at least for Doctrine2.
First of all, from the Doctrine2 docs you mention:
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.
You can't create a relation to something that's not an entity!
To solve your immediate problem (~version control~ in doctrine), check out EntityAudit on Github instead.
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.
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.
I have this situation:
Abstract Class:
abstract class AbstractBase
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
* #var integer
*/
protected $id;
/**
* #ORM\Column(type="datetime", name="updated_at")
* #var \DateTime $updatedAt
*/
protected $updatedAt;
/**
* #ORM\PreUpdate
*/
public function setUpdatedAt()
{
die('THIS POINT IS NEVER REACHED');
$this->updatedAt = new \DateTime();
}
}
Concrete Class:
/**
* #ORM\Entity(repositoryClass="Entity\Repository\UserRepository")
* #ORM\Table(name="users")
* #ORM\HasLifecycleCallbacks
*/
class User extends AbstractBase
{
// some fields, relations and setters/getters defined here, these all work as expected.
}
Then i call it in my controller like this:
$user = $this->em->find('Entity\User', 1);
// i call some setters here like $user->setName('asd');
$this->em->flush();
die('end');
Everything works as expected, so the id field from the abstract class gets created for the User entity, i can access it etc.
The problem is, that the line "die('THIS POINT IS NEVER REACHED')" is never reached. (Note the #ORM\PreUpdate) This means that lifecycleCallbacks are not called on
inherited objects. Is this a bug, or is there a reason for this?
Your abstract base class has to be anotated as Mapped Superclasses and include the HasLifecycleCallbacks-Annotation.
Further Information: Inheritance Mapping in the Doctrine Documentation.
/**
* #ORM\MappedSuperclass
* #ORM\HasLifecycleCallbacks
*/
abstract class AbstractBase
{
[...]
/**
* #ORM\PreUpdate
*/
public function setUpdatedAt()
{
$this->updatedAt = new \DateTime();
}
}
/**
* #ORM\Entity(repositoryClass="Entity\Repository\UserRepository")
* #ORM\Table(name="users")
*/
class User extends AbstractBase
{
// some fields, relations and setters/getters defined here, these all work as expected.
}
You have to annotate the base class with #ORM\HasLifecycleCallbacks, and the function with #ORM\preUpdate
You have a typo (PreUpdate should be preUpdate), also preUpdate isn't called on creation (only on update). So if you want it also be triggered on creation, you should add #ORM\prePersist.
While the accepted reply is correct for the general case, in this particular case (timestamp) you actually want to use the doctrine extension Timestampable as explained for example here Lifecycle Callback Issue When Extending FOSUserBundle User Entity
It is important that the MappedSuperclass with HasLifecycleCallbacks is in the same namespace or directory as their child Entities.
I had problems with life cycle callbacks when the MappedSuperclass was in one directory (Model) while the Entities were in another (Entity). Putting the MappedSuperclass in the same directory as the Entities (Entity) solved the issue.
Maybe i'm wrong but I don't think preUpdate isn't triggered when you persist an entity. You should have a #prePersist.
http://www.doctrine-project.org/docs/orm/2.0/en/reference/events.html
But still then i'm not sure this is going to work but you could try that. Else a workaround would be to overwrite the setUpdatedAt function and just call his parent one but that's a bit ugly.
Hope the #prePersist helps for you.
Perhaps you could this issue report as a reference how to setup your annotations? The testcase seems to be valid and matches your use case.
I think you have to annotate the base class with #ORM\HasLifecycleCallbacks
docs