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