How do you create ManyToOne relation within single supermapped class - php

I am creating a simple CMS Bundle for my headless symfony backend and I'm trying to map Page to Page with parent and child relation(Many children to one parent) and I have this class mapped superclass to create reusable code, this is a minified sample on what I'm trying to archive:
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\MappedSuperclass()
*/
class Test
{
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
public function getId()
{
return $this->id;
}
/**
* #ORM\ManyToOne(targetEntity="Ziebura\CMSBundle\Entity\Test")
*/
protected $parent;
public function getParent()
{
return $this->parent;
}
public function setParent($parent)
{
$this->parent = $parent;
}
}
Then I'm extending this class as a normal entity to create DB table
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Ziebura\CMSBundle\Entity\Test as BaseTest;
/**
* #ORM\Table(name="test")
* #ORM\Entity(repositoryClass="App\Repository\TestRepository")
*/
class Test extends BaseTest
{
}
The issue is that I'm getting this doctrine exception
Column name `id` referenced for relation from App\Entity\Test towards Ziebura\CMSBundle\Entity\Test does not exist.
I don't quite understand why it produces this error or is the thing that I'm trying to archive impossible, I already did relations on mapped superclasses but it was 2 or more tables and not just a single on. I already tried creating $children field but it didnt worked and still produced above error. Did anyone try to create something simmilar? I couldn't find anything about this in doctrine docs, only found how to map 2 different superclasses. I suppose the easy way out would be to specify the relation in App namespace not in the Bundle but that pretty much destroys the purpose of reusable code if I'd have to declare that in every project I use the bundle. I believe in stack let's figure this out. Thanks!

Lets read Doctrine docs about this: https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/reference/inheritance-mapping.html#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 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 associations are not possible on a mapped superclass at all.
According to this:
MappedSuperclass cannot be Entity
Cannot have One-To-Many relationship - so if you are defining ManyToOne to same class then it creates also OneToMany on same class - which, as you read above, is forbidden.

For some reason only changing the full entity path in BaseTest resolved app throwing the exception and it works, if anyone would face same issue try changing
/**
* #ORM\ManyToOne(targetEntity="Ziebura\CMSBundle\Entity\Test")
*/
protected $parent;
public function getParent()
{
return $this->parent;
}
public function setParent($parent)
{
$this->parent = $parent;
}
To
/**
* #ORM\ManyToOne(targetEntity="Test")
*/
protected $parent;
public function getParent()
{
return $this->parent;
}
public function setParent($parent)
{
$this->parent = $parent;
}
If someone knows why it has to be like this I'd much appreciate a comment to my answer.

Related

Doctrine2: persisting parent entity given child class

I've got a User Entity defined (mapping in yml)
namespace My\CoreBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
class User
{
...
And I created a child class that inherits from that entity, so that I can add some custom validation methods and a few fields that I need but do not need to be persisted (e.g. ConfirmPassword, ConfirmEmail fields)
namespace My\SecondBundle\EditModels;
use My\CoreBundle\Entity\User;
class UserModel extends User
{
When the user submit a registration form, I map the request to a UserModel entity, and if it is valid I try to persist the user.
The following code throws an exception
$entityManager->persist($userModel);
//=>The class 'My\SecondBundle\EditModels\UserModel' was not found in the chain configured namespaces My\CoreBundle\Entity
Question: How can I persist $userModel (instance of UserModel) as a User entity class? Possible options:
Do not use an inherited class and add custom fields and validation method to the User entity itself
Copy the fields from the UserModel to the User entity and persist the user entity
I don't think I should use Doctrine inheritance mechanism as I do not want to save the extra fields.
Thank you
I think your problem here, is that you've just configured My\CoreBundle\Entity namespace in Doctrine2, but the entity you actually want to persist is located in My\SecondBundle\EditModels.
Usually when inheriting classes marked as #ORM\Entity() the class you are extending from must have the class annotation #ORM\MappedSuperclass(). But normally you use this for single table inhertiance e.g., not for your usecase.
In my opinion the approach to split database related attributes from the others, is not affordable. I would keep validation related stuff in the model itself - you need it in your create/update action.
I'm not familiar with XML configuration, but when using annotations you need to mark each property to be mapped with database (using #ORM\Column()). So Doctrine will ignore all the other attributes and methods entirely.
So here I share my recently developed AbstractModel for you, to see how I've implemented validation (with respect/validation):
<?php
namespace Vendor\Package\Model;
use Doctrine\ORM\Mapping as ORM;
/**
* Abstract Model
*
* #ORM\MappedSuperclass()
*/
abstract class AbstractModel
{
/**
* #var \Respect\Validation\Validator
*/
protected $validator;
/**
* AbstractModel constructor
*/
public function __construct()
{
$this->validator = static::validation();
}
/**
* Defines validation for this model
*
* #return \Respect\Validation\Validator
*/
public static function validation() : \Respect\Validation\Validator
{
return \Respect\Validation\Validator::create();
}
/**
* Executes validations, defined in validation method.
*
* #return bool
*/
public function isValid() : bool
{
if (is_null($this->validator)) {
$this->validator = new \Respect\Validation\Validator();
$this->validation();
}
return $this->validator->validate($this);
}
}
A model which extends from the AbstractModel needs to implement a static validate method, to define class validation:
<?php
namespace Vendor\Package\Model;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity()
* #ORM\Table(name="my_model")
*/
class MyModel extends AbstractModel
{
/**
* #var string
* #ORM\Column(type="string")
*/
private $name;
/**
* Defines validation for this model
*
* #return \Respect\Validation\Validator
*/
public static function validation() : \Respect\Validation\Validator
{
return \Respect\Validation\Validator::create()
->attribute('name', \Respect\Validation\Validator::notEmpty()->stringType()->length(null, 32))
;
}
// getter, setter, ...
}
Each entity, persisted to database, will have the $validator property and all these methods, but because I left annotations here (and pretty sure this also works with xml/yaml) Doctrine ignores it.
And this way you also keep validation related stuff out of the model class itself, which is good for readability. The validation itself should be defined in the model itself, imho. But this respect/validation framework is neat way to achive this. Hope this helps :)

Doctrine multiple level inheritance mapping

I have four types of products which I would like to map to a Doctrine ORM structure on a MySQL RDBMS. The products are PrepaidProduct, PostpaidProduct, MobilePrepaidProduct, MobilePostpaidProduct with the following structure:
abstract class Product {
/**
* #ORM\Column(type="integer")
*/
private $price;
...
}
class PrepaidProduct extends Product {
/**
* #ORM\Column(type="integer")
*/
private $credit;
/**
* #ORM\OneToMany(targetEntity="PrepaidDiscount")
*/
private $prepaidDiscounts;
}
class PostpaidProduct extends Product {
/**
* #ORM\OneToMany(targetEntity="BundleMapping")
*/
private $bundleMappings;
}
class MobilePrepaidProduct extends PrepaidProduct {
/**
* #ORM\ManyToOne(targetEntity="Device")
*/
private $device;
}
class MobilePostpaidProduct extends PostpaidProduct {
/**
* #ORM\ManyToOne(targetEntity="Device")
*/
private $device;
}
The main idea is that I would like to use a service (factory) that will use the basic class structure of the PostpaidProduct class to create a structure of the corresponding bundle mapping, so I think I would need this as a mapped super class.
In my opinion the way to go would be to have two separate tables, one for PostpaidProduct and one for PrepaidProduct, and have a Single Table Inheritance on those for MobilePostpaidProduct/PostpaidProduct and MobilePrepaidProduct/PrepaidProduct.
What do you guys think? Any thoughts on best way to model this?
If you are using a RDBMS layer [MySQL, Postgre] as I think, your proposal is the best choice in my opinion.
If subclasses have *little more attributes, possibly relationships most of all, you are actually promoting composition and your data representation won't be sparse [i.e. you won't have many empty fields in the main table].
On the other hand, if you want to stick with composition only [which is more advisable in most cases] - is $device the only additional relationship in Mobile classes? If so, you could write a findAllMobileProducts method in your repository class that would return every product where device is not null, and so on.

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.

Doctrine2, Mapping "inherited" tables

as pointed out here: Doctrine 2.1 - Map entity to multiple tables Doctrine2 does not allow mapping of one object to multiple tables.
I currently have a Mysql db setup similar to this:
base_entity: id, some_basic_data_columns
state: id, state, entity_id (FK to base_entity.id), start_time, end_time, ...
entity_one: id (FK to base_entity.id), some_specific_data
entity_two: id (FK to base_entity.id), some_specific_data
and so on...
In a way, entity_x is "extending" base_entity, and all these entities can have multiple states. To have proper foreign keys I would have to either have separate state tables (which I don't want to do because they will structurally be the same ), or do it like this.
The base entity by itself is useless, id could even be boiled down to just the id field to allow to join with each child entity to multiple states.
I do not need a BaseEntity class, but I do need for each child Entity to have a getStates() method. Of course I may actually have an abstract entity class, but concrete entities will extend it, not have it as a property like they would if I would map them as one would map other one-to-one relationships
Since Doctrine will not allow me to map EntityOne to both entity_one and base_entity table I have to ask:
Is this bad design? Am I overlooking some other way to solve this elegantly? I know other DMBSs have inheritance, but for instance PostgreSql would still not allow me to join the base_entity to state if no physical base_entity exists for a child.
I could do something like this on the code side:
class EntityOne {
// baseEntity as a property
private $_baseEntity;
// private getter for the base table
private getBaseEntity();
// and getters like this for properties in the base table
public getStates(){
return $this->getBaseEntity()->getStates();
}
}
This way the entity would behave like a single entity (not combined from base and child) to the outside world, but it would still require that I write a separate BaseEntity class and all the config info to connect it to other entity classes
Basically, what I'm asking is: is this a Db design issue, and I got it completely wrong from the start (and if I did, which is the "best" approach), or is this a code issue, and I should work around it with code (if so, is my approach in 2. ok, or are there better ways to deal with this), and are there ORMs which allow for multiple table mapping?
Many thanks in advance.
You could use Class Table Inheritance (see Doctrine documentation about that), defining a BaseEntity entity class, and and create EntityOne and EntityTwo extending that.
You could define the relationship between the BaseEntity class and the State entity class as one-to-many association - if I understood right what you wanted, providing the needed getState() method in the BaseEntity class.
Something like this:
/**
* #Entity
* #Table(name="base_entity")
* #InheritanceType("JOINED")
* #DiscriminatorColumn(name="entity_type", type="string")
* #DiscriminatorMap({"entity_one"="EntityOne", "entity_two"="EntityTwo"})
*/
class BaseEntity {
/**
* #Id
* #Column(type="integer")
*/
protected $id;
/**
* #OneToMany(targetEntity="State", mappedBy="entity)
**/
protected $states;
public function getStates() {
return $this->states;
}
...
}
/**
* #Entity
* #Table(name="entity_one")
*/
class EntityOne extends BaseEntity {
...
}
/**
* #Entity
* #Table(name="entity_two")
*/
class EntityTwo extends BaseEntity {
...
}
/**
* #Entity
* #Table(name="state")
*/
class State {
/**
* #ManyToOne(targetEntity="BaseEntity", inversedBy="states")
* #JoinColum(name="entity_id", referencedColumnName="id")
*/
protected $entity;
public function getEntity() {
return $this->entity;
}
...
}

Doctrine 2 LifecycleCallbacks with abstract base class are not called

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

Categories