This course is still being released! Check back later for more chapters.

Get Notified About this Course!

We will send you messages regarding this course only
and nothing else, we promise.
You can unsubscribe anytime by emailing us at:
privacy@symfonycasts.com
Login to bookmark this video
Buy Access to Course
08.

Orphan Removal

|

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

When we used make:entity to add a relation, it asked us about orphanRemoval. Time to find out what that is & when to use it.

In the fixtures, start with $starshipPart = StarshipPartFactory::createOne(). To make it stand out, I'll make this a crucial item for any space voyage: "Toilet Paper." Yes, a cheeky nod to pandemic times. Yuck!

Assign this part to the Starship above (add the missing $ship =) then dump $starshipPart:

49 lines | src/DataFixtures/AppFixtures.php
// ... lines 1 - 10
class AppFixtures extends Fixture
{
public function load(ObjectManager $manager): void
{
// ... lines 15 - 30
$ship = StarshipFactory::createOne([
// ... lines 32 - 36
]);
$starshipPart = StarshipPartFactory::createOne([
'name' => 'Toilet Paper',
'starship' => $ship,
]);
dump($starshipPart);
// ... lines 44 - 46
}
}

So far, so good: nothing fancy. Try reloading the fixtures:

symfony console doctrine:fixtures:load

No errors, and for the first time, we see that proxy object I've been mentioning.

Unveiling the Proxy Object

Remember: when you create an object via Foundry, it hands you back your shiny new object, but it's bundled up inside another object called a proxy. Most of the time: you don't notice or care: all method calls on the proxy are forwarded to the real object.

But because I want to make things crystal clear, extract the real object from both $ship and $starshipPart using _real():

49 lines | src/DataFixtures/AppFixtures.php
// ... lines 1 - 10
class AppFixtures extends Fixture
{
public function load(ObjectManager $manager): void
{
// ... lines 15 - 30
$ship = StarshipFactory::createOne([
// ... lines 32 - 36
])->_real();
$starshipPart = StarshipPartFactory::createOne([
'name' => 'Toilet Paper',
'starship' => $ship,
])->_real();
dump($starshipPart);
// ... lines 44 - 46
}
}

Run the fixtures again:

symfony console doctrine:fixtures:load

And... all smooth. Without the proxy, we can see that the StarshipPart is indeed tied to the correct Starship - the USS Espresso — which we created earlier. So far, it's all systems go!

Deleting a Starship Part: The Plot Thickens

But what if we need to delete a StarshipPart? Normally, we'd say $manager->remove($starshipPart), then $manager->flush(). But let's mix things up: let's simply remove the part from its ship: $ship->removePart($starshipPart):

51 lines | src/DataFixtures/AppFixtures.php
// ... lines 1 - 10
class AppFixtures extends Fixture
{
public function load(ObjectManager $manager): void
{
// ... lines 15 - 38
$starshipPart = StarshipPartFactory::createOne([
'name' => 'Toilet Paper',
'starship' => $ship,
])->_real();
$ship->removePart($starshipPart);
$manager->flush();
dump($starshipPart);
// ... lines 46 - 48
}
}

What do you think will happen? Will it delete the part? Or just remove it from the ship? In that case, the part will just be floating around in space, it'll become an orphan. Try it:

symfony console doctrine:fixtures:load

It blows up with our favorite:

starship_id cannot be null.

Fixing the Null Error

Why did this happen? When we call removePart(), it sets the Starship to null. But we made that not allowed with nullable: false: every part must belong to a ship. The solution? Well, it depends on what you want: do we want to allow parts to become orphaned? Cool! Change nullable to true in StarshipPart and make a migration.

Or maybe if a part is suddenly removed from its ship, we want to delete that part entirely from the database. Maybe the ship owner isn't a big fan of recycling. To do this, head to Starship and add orphanRemoval: true to the OneToMany:

182 lines | src/Entity/Starship.php
// ... lines 1 - 13
class Starship
{
// ... lines 16 - 41
/**
* @var Collection<int, StarshipPart>
*/
#[ORM\OneToMany(targetEntity: StarshipPart::class, mappedBy: 'starship', orphanRemoval: true)]
private Collection $parts;
// ... lines 47 - 180
}

Whirl back and reload the fixtures:

symfony console doctrine:fixtures:load

No errors in sight! The ID is null because it was entirely deleted from the database. So orphanRemoval means:

Hey, if any of these parts become orphaned toss them into the incinerator.

Next up: we'll explore a way to control the order of a relation, like making $ship->getParts() return alphabetically.