Doctrine2 __constructor not called when using $em->find(); ? How to load entity properly?
I'm learning doctrine2, and having a problem how to call constructor automatically. For example, in my entity I have
/**
* @Entity
*/
class User{
....
public function __construct() {
exit('in');
}
}
and when I get the object this way:
$userObj = $em->find('User', 1);
I do get that object from database, but constructor is never called. I want to put some common things in constructor, like validation rules, or even to put sample code from the doctrine documentation like
$this->comments = new ArrayCollection();
This ofcourse works when I create new object in code for creating a user like
开发者_高级运维$user = new User(); //now constructor works just fine
Now, what is the "proper" way of getting the entity? I doubt I have to call constructor manually each time I user $em->find() with $user0bj->__construct(); ? This would kinda sucks then... Or I should use something other then ->find() to get single entity properly? I know I can user @PrePersist, and I am using it to actually do validation checks etc. I guess that I'm probably missing something here, or I'm trying to use constructor in a poor way. Thanks for any explanations and guides!
I'm pretty certain that find
or similar isn't expected to call the constructor...
You need to hook into the @PostLoad event.
Why would you want to call the constuctor of already persisted entity? When you need to validate it you should have done the validation or initializations before you have persisted it. So When you call a already persisted entity there is no point to validate it. The right place to put validation and other initializations is the constructor method of entity. Eg.
/**
* @Entity
*/
class User{
protected $name;
public function __construct($name) {
if (isset($name)) {
//** validate the name here */
$this->name=$name;
} else {
throw new Exception("no user name set!");
}
}
}
According to the doctrine2 documentation Doctrine2 never calls __construct() method of entities. http://www.doctrine-project.org/docs/orm/2.0/en/reference/architecture.html?highlight=construct
doctrine uses reflection to instantiate your object without invoking your constructor.
Since PHP 5.4 , you can use reflection to instanciate a class without calling the constructor using ReflectionClass::newInstanceWithoutConstructor
the instantiator of doctrine use it like :
private function buildFactory(string $className) : callable
{
$reflectionClass = $this->getReflectionClass($className);
if ($this->isInstantiableViaReflection($reflectionClass)) {
return [$reflectionClass, 'newInstanceWithoutConstructor'];
}
$serializedString = sprintf(
'%s:%d:"%s":0:{}',
is_subclass_of($className, Serializable::class) ? self::SERIALIZATION_FORMAT_USE_UNSERIALIZER : self::SERIALIZATION_FORMAT_AVOID_UNSERIALIZER,
strlen($className),
$className
);
$this->checkIfUnSerializationIsSupported($reflectionClass, $serializedString);
return static function () use ($serializedString) {
return unserialize($serializedString);
};
}
Doctrine ORM will "rewrite" your class, it generate a new class that implement \Doctrine\ORM\Proxy\Proxy
And it rewrite the construct
method:
/**
* @param \Closure $initializer
* @param \Closure $cloner
*/
public function __construct($initializer = null, $cloner = null)
{
$this->__initializer__ = $initializer;
$this->__cloner__ = $cloner;
}
You can see it inside the cache
folder ${CACHE}/doctrine/orm/Proxies
.
You will need both @ORM\HasLifecycleCallbacks on the class + @ORM\PostLoad on a specific function of your choice.
Beware! If you put it on the constructor it will override loaded database data!
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Table(name="dossier")
* @ORM\Entity()
* @ORM\HasLifecycleCallbacks
*/
class Dossier
{
// ...
/**
* The normal constructor stays as usual
*/
public function __construct()
{
$this->takenActions = new ArrayCollection();
$this->classifications = new ArrayCollection();
$this->dossierProblems = new ArrayCollection();
$this->internalNotes = new ArrayCollection();
}
/**
* Triggers after the entity has been loaded in the EntityManager (e.g. Doctrine's ->find() etc...)
* The constructor does not get called. Some variables still need a default value
* Must be in combination with "ORM\HasLifecycleCallbacks" on the class
*
* @ORM\PostLoad
*/
public function postLoadCallback(): void
{
// Only put a default value when it has none yet
if (!$this->dossierProblems)
$this->dossierProblems = new ArrayCollection();
if (!$this->internalNotes)
$this->internalNotes = new ArrayCollection();
}
// ...
}
精彩评论