Loading...

Doctrine: constructor is not called using persistence layer methods

If you try to get an entity using the object repository find() method, be sure that the construct of the object in question is not called.

example:

$this->_em->getRepository(Foo::class)->find(['id'=>13]);
class Foo {

    /**
     * @var integer
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;
    /**
     * @var string
     * @ORM\Column(name="bar", type="string", length=25)
     */
    private $bar;
    /**
     * @var string
     */
    private $baz;

    public function __construct()
    {
    die('im will never be called here');
    }
}

Doctrine is not expected to call your constructor in this case , but how ?

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);
    };
}

The principe behind creating an instance without calling the constructor seems a little weird. but this option is very useful for mocking objects as PHPUnit did.

Use case

let’s suppose you are trying to init $baz for every doctrine find method call. the option of initing the property in the constructor is not working. to resolve it you can use doctrine lifecycles callback as postload.

/**
 * @ORM\PostLoad()
 */
public function initBaz(){
    $this->baz = 'baz';
}