The Two Sides of a Relation: Owning vs Inverse
Lucky you! You found an early release chapter - it will be fully polished and published shortly!
This Chapter isn't quite ready...
Rest assured, the gnomes are hard at work
completing this video!
Fun fact for your next party: Every
relationship can be viewed from two different sides. Take
Starship
as an example: it has multiple parts, making it a
one-to-many relationship from the Starship
perspective. But, flip the
telescope around and look from the StarshipPart
end, and you'll find a
many-to-one relationship. One of these perspectives is always known as the
owning side, and the other, the inverse side.
Now, you might be thinking, "Why do I care about this?" Well, stick with me for a three-minute dive: it might just save you from a big headache down the road. Plus, you'll have a new bit of trivia to impress your family with at your next family holiday. You can thank me later.
The Owning Side Unveiled
First off, which side is the owning side? For a many-to-one: it's
always the side that has the ManyToOne
attribute, which is on the
entity that will have the foreign key column. In our case, that's
StarshipPart
.
The Importance of Ownership
But why does this matter? Two reasons. First, the
JoinColumn
can only live on the owning side. That makes sense:
it controls the foreign key column.
Second, you can only set the owning side of the relationship.
Let me show you:
Pop open src/DataFixtures/AppFixtures.php
and let's play a bit:
$starship = StarshipFactory::createOne();
. My AI overlord
was almost right. Below this, I'll sprinkle in
code that creates two StarshipPart
objects, persist & flush
them. I haven't set any relations yet, but let's recklessly load the fixtures anyway:
symfony console doctrine:fixtures:load
And voilĂ , our favorite error pops up. starship_id
cannot be null.
Totally expected.
The Owning vs Inverse Side in Action
To demonstrate the owning vs inverse issue, add
_real()
to the end of $starship
. When you create an entity via foundry,
it actually wraps that in a little gift called a proxy object. This is
usually inconsequential, but occasionally, it
can cause some confusion. By calling _real()
, we can unwrap the
proxy and get the real Starship
object.
Ok: time to connect these parts to this starship. Normally, we'd say
$part1->setStarship($starship);
, which sets the owning
side. This time try setting the inverse side. That would be
$starship->addPart($part1);
and $starship->addPart($part2);
.
Now, based on what I just explained, this should not work because we are only setting the inverse side. But let's roll the dice and load the fixtures anyway:
symfony console doctrine:fixtures:load
But surprise, surprise! No errors. In fact, if you check the database:
symfony console doctrine:query:sql "SELECT * FROM starship_part"
Sure enough, there are two new parts each related to a starship.
So, what gives? We just set the inverse side of the relationship, and it still saved to the database. That is the opposite of what I just told you!
The Plot Twist: Inverse Side Setting the Owning Side
Open the Starship
entity and find the addPart()
method. Aha! This
method calls $part->setStarship($this);
. It sets the owning
side. When we set the inverse side, our own code generated by the
make:entity
command also sets the owning side. Clever girl, eh?
## Owning vs Inverse vs I don't Care
So here's the takeaway: every relation has an owning side and an inverse side.
The inverse side is optional. make:entity
asked if we wanted to
generate the inverse side, and we said yes. That gave us the super convenient
$ship->getParts()
method.
So yes, technically, you can only set the relationship from the owning side (i.e.,
$starshipPart->setShip();
), but in practice, you can set it from either
side thanks to our own code that synchronizes both sides. So go astound your friends
with your newfound knowledge, then promptly forget about it: it's not critical
in practice.
Clean up our temporary code here and freshen things up by reloading the fixtures:
symfony console doctrine:fixtures:load
Alright, next up: orphanRemoval
. It's not nearly as mean as it sounds.