Cascade Persist
Keep on Learning!
If you liked what you've learned so far, dive in! Subscribe to get access to this tutorial plus video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeCheck out this error: it's a doozy!
An entity was found through the relationship
Starship.droidsthat was not configured to "cascade persist" operations for entityStarshipDroid.
Let me translate that for you:
Hey, you're saving this
Starshipand it's got aStarshipDroidattached to it. That's great, but you forgot to tell me to persist theStarshipDroid. 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:
| // ... 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:
| // ... 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:
| // ... 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:
| // ... 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!
 
             
            
Thank you Ryan! It's wonderful to be able to accompany you in this tutorial. It's always nice and I always learn something new or at least refresh some knowledge. You are a great motivation and inspiration for me - and have been for all these great years.