I have a problem with a Lifecycle Callback of Doctrine ORM, that is not executed on fetch-joined entities, while it's regularly executed on entities that are lazy-loaded.
This is the code:
EntityA:
namespace AppBundle\Entity\EntityA;
use Doctrine\ORM\Mapping as ORM;
/**
* EntityA
* @ORM\Entity()
* @ORM\HasLifecycleCallbacks()
*/
class EntityA {
/**
* @var int
* @ORM\Id
*/
private $id;
/**
* @var string
*
* @ORM\Column(type="string", length=255, nullable=true)
*/
private $name;
/**
* @var \Doctrine\Common\Collections\ArrayCollection
* @ORM\OneToMany(targetEntity="EntityB", mappedBy="EntityA", indexBy="name", cascade="all", orphanRemoval=true)
*/
private $entitiesB;
/**
* @var \Doctrine\Common\Collections\ArrayCollection
*/
private $myNotMappedField;
public function __construct() {
/*initializes here fields mapped by Doctrine to db*/
$this->name='';
$this->initNotMappedFields();
}
/**
* Here I initialize properties not handled by Doctrine
* @ORM\PostLoad()
*/
public function initNotMappedFields() {
$this->myNotMappedField=new \Doctrine\Common\Collections\ArrayCollection();
}
}
EntityB:
/**
* EntityB
* @ORM\Entity()
* @ORM\HasLifecycleCallbacks()
*/
class EntityB {
/**
* @var int
* @ORM\Id
*/
private $id;
/**
* @var string
*
* @ORM\Column(type="string", length=255, nullable=true)
*/
private $name;
/**
* @var \Doctrine\Common\Collections\ArrayCollection
* @ORM\ManyToOne(targetEntity="EntityA", inversedBy="entitiesB")
*/
private $entityA
/**
* @var \Doctrine\Common\Collections\ArrayCollection
*/
private $myNotMappedField;
public function __construct() {
/*initializes here fields mapped by Doctrine to db*/
$this->name='';
$this->initNotMappedFields();
}
/**
* Here I initialize properties not handled by Doctrine
* @ORM\PostLoad()
*/
public function initNotMappedFields() {
$this->myNotMappedField=new \Doctrine\Common\Collections\ArrayCollection();
}
}
Controller:
// this works right:
// EntityA::initNotMappedFields() is properly called
$entityA = $this->getDoctrine()->getManager()->getRepository('AppBundle:EntityA')->findOneById(1);
// EntityB::initNotMappedFields() is properly called
$entityA->getEntitiesB();
// I want to fetch join EntityB into EntityA, to avoid
// multiple single SQL statements to be executed against the DB
// EntityA::initNotMappedFields() is called
$entityA = $this->getDoctrine()->getManager()->getRepository('AppBundle:EntityA')->createQueryBuilder('entA')
->addSelect(['entB'])
->andWhere('entA=:id')->setParameter('id', $id)
->leftJoin('entA.entitiesB', 'entB')
->getQuery()->getOneOrNullResult();
// EntityB::initNotMappedFields() is NOT called
$entityA->getEntitiesB();
What am I missing?
This occurs because the first example is going to generate a SQL query to get each individual EntityB and then load it into EntityA. If A has 10 Bs, you'll get 10 additional queries to get each B which call your postLoad() callback on B, because the EntityManager is the one constructing EntityB. See the documentation on the postLoad() event.
What I would do is simply use EntityA to call your initialization on EntityB:
/**
* Here I initialize properties not handled by Doctrine
* @ORM\PostLoad()
*/
public function initNotMappedFields()
{
$this->myNotMappedField = new \Doctrine\Common\Collections\ArrayCollection();
// the 'if' here is in case you load EntityA without joining EntityB
// so that you won't cause the extra queries if you don't want EntityB in there
if ($this->entitiesB) {
foreach ($this->entitiesB as $entityB) {
$entityB->initNotMappedFields();
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With