Silex + Doctrine ORM doesn't fire events when set on #MappedSuperclass - php

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.

Related

How do you create ManyToOne relation within single supermapped class

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.

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 :)

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

How do I configure a Doctrine2 entity which extends PersistentObject within Symfony2 project?

I would like to be able to use the PersistentObject described here http://www.doctrine-project.org/blog/a-doctrine-orm-odm-base-class.html during development of a Symfony2 project, to avoid creating and deleting getters and setters endlessly whilst the database and entity design are in flux.
Where in a Symfony2 project does one 'configure' the ObjectManager, as suggested in the brief documentation (code quote below)? Should it be in the controller, and if so, what would it look like?
$entityManager = EntityManager::create(...);
PersistentObject::setObjectManager($entityManager);
I cannot find any working examples (although I did find this parallels example for the Zend2 framework on stackoverflow: Using PersistentObject from Doctrine in Zend Framework
Thanks for your time!
The PersistentObject is an object which you don't manually have to persist. It thereby provides magic getters and setters using php's __call() method.
You simply extend the Object in your entity class and use it inside your controller. without the need to generate getters and setters.
example entity
<?php
namespace Vendor\YourBundle\YourEntity;
use Doctrine\Common\Persistence\PersistentObject;
use Doctrine\Common\Persistence\ObjectManager;
use Doctrine\ORM\Mapping as ORM;
class YourEntity extends PersistentObject
{
// i added the constructor in order to save the setObjectManager()
// call in the the controller
public function __construct(ObjectManager $om)
{
$this->setObjectManager($om);
}
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="string", length=100)
*/
protected $name;
// ... more properties
}
example controller action
class YourController extends Controller
{
public function yourAction($name)
{
$em = $this->get('doctrine')->getManager('default');
$entity = new YourEntity($em); // __construct calls setObjectManager($em)
$entity->setName($name); // magic setter is used here
// ... no need for $em->persist($entity);
$em->flush(); // $entity is being persisted.
}
// ...
You get the doctrine entity manager inside a symfony controller using one of ...
$em = $this->get('doctrine')->getManager(); // gets default manager
$em = $this->get('doctrine')->getManager('default'); // same as above
$em = $this->getDoctrine()->getManager(); // using alias

Categories