Login to bookmark this video
Buy Access to Course
24.

Cascade Persist

|

Share this awesome video!

|

Keep on Learning!

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Check out this error: it's a doozy!

An entity was found through the relationship Starship.droids that was not configured to "cascade persist" operations for entity StarshipDroid.

Let me translate that for you:

Hey, you're saving this Starship and it's got a StarshipDroid attached to it. That's great, but you forgot to tell me to persist the StarshipDroid. What do you want me to do?

But again, from inside Starship, we can't get the entity manager to say $manager->persist($starshipDroid).

Harnessing the Power of cascade=['persist']

The solution is to use something called cascade persist.

Scroll up to the $starshipDroids property, and find the OneToMany. Add a new option: cascade. I'll type it in manually. Set it to an array with persist inside:

262 lines | src/Entity/Starship.php
// ... lines 1 - 15
class Starship
{
// ... lines 18 - 53
#[ORM\OneToMany(targetEntity: StarshipDroid::class, mappedBy: 'starship', cascade: ['persist'])]
private Collection $starshipDroids;
// ... lines 56 - 260
}

We're setting up a sort of domino effect. If anyone persists this starship, we're going to cascade that persist down to any attached relationships.

A word of caution though: use this power wisely. It makes your code more automatic, which is great, but it can also make it harder to spot bugs.

But in this case, it's exactly the fix we need.

Give those fixtures another whirl:

symfony console doctrine:fixtures:load

Back to Adding Droids

We're back in business. We can use ship->addDroid() once more. But I still want to create a fleet of starships with droids attached to them.

Remove all the manual code and bring back the droids property on the StarshipFactory:

60 lines | src/DataFixtures/AppFixtures.php
// ... lines 1 - 13
class AppFixtures extends Fixture
{
public function load(ObjectManager $manager): void
{
// ... lines 18 - 51
DroidFactory::createMany(100);
StarshipFactory::createMany(100, fn() => [
'droids' => DroidFactory::randomRange(1, 5),
]);
StarshipPartFactory::createMany(100);
}
}

Fire up the fixtures again:

symfony console doctrine:fixtures:load

Guess what? They work!

Behind the scenes, Foundry is calling addDroid() on each Starship for each droid. And we just proved that addDroid() is back in action.

The creation of the StarshipDroid join entity is now hidden from our entire codebase!

Finer Control with assignedAt

But, what if you want to add a droid to a starship and control the assignedAt property? Add an argument for addDroid() in Starship: a DateTimeImmutable. Make it optional to keep things flexible. Then, after creating the StarshipDroid, set the $assignedAt if we passed it in:

265 lines | src/Entity/Starship.php
// ... lines 1 - 15
class Starship
{
// ... lines 18 - 207
public function addDroid(Droid $droid, \DateTimeImmutable $assignedAt = null): static
{
if (!$this->getDroids()->contains($droid)) {
// ... lines 211 - 213
if ($assignedAt) {
$starshipDroid->setAssignedAt($assignedAt);
}
// ... line 217
}
// ... lines 219 - 220
}
// ... lines 222 - 263
}

Cool... but there's a slight issue. Foundry won't let us control the assignedAt field. So, if you want to assign some droids at a specific time, you'll need to take the wheel manually.

Displaying assignedAt

Finally, let's make that assignedAt visible on our site. We'll need the StarshipDroid join entity object to do that. It's a bit more work, but totally doable.

Change the loop to for starshipDroid in ship.starshipDroids. Then starshipDroid.droid.name and starshipDroid.assignedAt with the ago filter for flair:

90 lines | templates/starship/show.html.twig
// ... lines 1 - 4
{% block body %}
// ... lines 6 - 19
<div class="md:flex justify-center space-x-3 mt-5 px-4 lg:px-8">
// ... lines 21 - 25
<div class="space-y-5">
<div class="mt-8 max-w-xl mx-auto">
<div class="px-8 pt-8">
// ... lines 29 - 61
<p class="text-[22px] font-semibold">
{% for starshipDroid in ship.starshipDroids %}
{{ starshipDroid.droid.name }} (assigned {{ starshipDroid.assignedAt|ago }} {% if not loop.last %}, {% endif %}
// ... lines 65 - 66
{% endfor %}
</p>
// ... lines 69 - 84
</div>
</div>
</div>
</div>
{% endblock %}

Refresh and... we can now see when each droid was assigned.

That's it, friends! We've explored the deepest corners of Doctrine relationships, even the elusive many-to-many with extra fields. As always, if you have questions, drop them in the comments below. We're all in this together!