Cascade persist order
Hi, I just try to upgrade from v1.38 to v2.8, but we are facing an issue.
We have 2 entities: Parent and Child, the Child is cascade persisted from the Parent entity.
In the app, a user create a Parent entity with at least a Child, when we persist the Parent doctrine cascade persist the children.
In v1.38, the ParentFactory allow us access the Child entities in ->afterInstantiate() and ->afterPerist() factory event, but actually we can´t.
The problem is: the Parent entity has a non nullable reference property that is defined from ->getChildren(), but the factory do it:
-
- ParentFactory: ->afterInstantiate
-
- ParentFactory: ->afterPersist
-
- Child: ->afterInstantiate
-
- Child: ->afterPerist
So trying to persist the Parent with a null reference because we cannot process the reference without Children that is persisted after.
class Parent
{
#[ORM\Id, ORM\Column, ORM\GeneratedValue]
private ?int $id = null;
#[ORM\Column]
private ?string $reference = null;
#[Assert\Count(min: 1)]
#[Assert\Valid]
#[ORM\OneToMany(targetEntity: Child::class, mappedBy: 'parent', cascade: ['persist', 'remove'], orphanRemoval: true)]
#[ORM\OrderBy(['id' => 'ASC'])]
private Collection $children;
public function __construct()
{
$this->children = new ArrayCollection();
}
public function getReference(): ?string
{
return $this->reference;
}
public function setReference(string $reference): static
{
$this->reference = $reference;
return $this;
}
/**
* @return ReadableCollection<int, Child>
*/
public function getChildren(): ReadableCollection
{
return $this->children;
}
public function addChild(Child $child): static
{
if (!$this->children->contains($child)) {
$this->children[] = $child;
$child->setParent($this);
}
return $this;
}
public function removeChild(Child $child): static
{
if ($this->children->removeElement($child)) {
// set the owning side to null (unless already changed)
if ($child->getParent() === $this) {
$child->setParent(null);
}
}
return $this;
}
}
class Child
{
#[ORM\Id, ORM\Column, ORM\GeneratedValue]
private ?int $id = null;
#[ORM\ManyToOne(targetEntity: Parent::class, inversedBy: 'children')]
#[ORM\JoinColumn(nullable: false)]
private ?Parent $parent = null;
// Many other scalar properties
public function getId(): ?int
{
return $this->id;
}
public function getParent(): ?Parent
{
return $this->parent;
}
public function setParent(?Parent $parent): static
{
$this->parent = $parent;
return $this;
}
// Many getters/setters
}
Previously we can do it in the factory:
final class ParentFactory extends PersistentProxyObjectFactory
{
protected function defaults(): array
{
// In v2.8, ->many() is replaced by ->range()
// Important: withoutPersisting to avoid the Factory trying persist children, Doctrine cascade it
return [
'children' => ChildFactory::new()->withoutPersisting()->many(1, 2),
];
}
protected function initialize(): static
{
// see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#initialization
return $this
->afterInstantiate(function (Parent $parent, array $attributes): void {
// Here the $parent should have children inside, but no more available
// We call a Service to calculate the reference like in Controllers
})
;
}
public static function class(): string
{
return Parent::class;
}
}
Hello
Could you create a reproducer repository that illustrate the problem, please?
Hi, I've done a tiny reproducer: https://github.com/mpiot/zenstruck-foundry-issue-1038
I call the Factory in the Homepage and dump & die on it.
Hi @mpiot
thanks for the reproducer (and sorry for the late reply 😅)
I get the problem now, you're right, the afterInstantiate() callback should see the objects. I'll work on a fix soon