OneToMany: Inverse Side of the Relation

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.

Start your All-Access Pass
Buy just this tutorial for $12.00

We have the Genus object. So how can we get the collection of related GenusNote Well, the simplest way is just to make a query - in fact, you could fetch the GenusNote repository and call findBy(['genus' => $genus]). It's really that simple.

Tip

You can also pass the Genus's ID in queries, instead of the entire Genus object.

But what if we could be even lazier? What if we were able to just say $genus->getNotes()? That'd be cool! Let's hook it up!

Setting up the OneToMany Side

Open up GenusNote. Remember, there are only two types of relationships: ManyToOne and ManyToMany. For this, we needed ManyToOne.

But actually, you can think about any relationship in two directions: each GenusNote has one Genus. Or, each Genus has many GenusNote. And in Doctrine, you can map just one side of a relationship, or both. Let me show you.

Open Genus and add a new $notes property:

... lines 1 - 11
class Genus
{
... lines 14 - 48
private $notes;
... lines 50 - 109
}

This is the inverse side of the relationship. Above this, add a OneToMany annotation with targetEntity set to GenusNote and a mappedBy set to genus - that's the property in GenusNote that forms the main, side of the relation:

... lines 1 - 11
class Genus
{
... lines 14 - 45
/**
* @ORM\OneToMany(targetEntity="GenusNote", mappedBy="genus")
*/
private $notes;
... lines 50 - 109
}

But don't get confused: there's still only one relation in the database: but now there are two ways to access the data on it: $genusNote->getGenus() and now $genus->getNotes().

Add an inversedBy set to notes on this side: to point to the other property:

... lines 1 - 10
class GenusNote
{
... lines 13 - 39
/**
* @ORM\ManyToOne(targetEntity="Genus", inversedBy="notes")
* @ORM\JoinColumn(nullable=false)
*/
private $genus;
... lines 45 - 99
}

I'm not sure why this is also needed - it feels redundant - but oh well.

Next, generate a migration! Not! This is super important to understand: this didn't cause any changes in the database: we just added some sugar to our Doctrine setup.

Add the ArrayCollection

Ok, one last detail: in Genus, add a __construct() method and initialize the notes property to a new ArrayCollection:

... lines 1 - 11
class Genus
{
... lines 14 - 50
public function __construct()
{
$this->notes = new ArrayCollection();
}
... lines 55 - 109
}

This object is like a PHP array on steroids. You can loop over it like an array, but it has other super powers we'll see soon. Doctrine always returns one of these for relationships instead of a normal PHP array.

Finally, go to the bottom of the class and add a getter for notes:

... lines 1 - 11
class Genus
{
... lines 14 - 105
public function getNotes()
{
return $this->notes;
}
}

Time to try it out! In getNotesAction() - just for now - loop over $genus->getNotes() as $note and dump($note):

... lines 1 - 12
class GenusController extends Controller
{
... lines 15 - 94
public function getNotesAction(Genus $genus)
{
foreach ($genus->getNotes() as $note) {
dump($note);
}
... lines 100 - 109
}
}

Head back and refresh! Let the AJAX call happen and then go to /_profiler to find the dump. Yes! A bunch of GenusNote objects.

Oh, and look at the Doctrine section: you can see the extra query that was made to fetch these. This query doesn't happen until you actually call $genus->getNotes(). Love it!

Owning and Inverse Sides

That was pretty easy: if you want this shortcut, just add a few lines to map the other side of the relationship.

But actually, you just learned the hardest thing in Doctrine. Whenever you have a relation: start by figuring out which entity should have the foreign key column and then add the ManyToOne relationship there first. This is the only side of the relationship that you must have - it's called the "owning" side.

Mapping the other side - the OneToMany inverse side - is always optional. I don't map it until I need to - either because I want a cute shortcut like $genus->getNotes() or because I want to join in a query from Genus to GenusNote - something we'll see in a few minutes.

Tip

ManyToMany relationships - the only other real type of relationship - also have an owning and inverse side, but you can choose which is which. We'll save that topic for later.

Now, there is one gotcha. Notice I did not add a setNotes() method to Genus. That's because you cannot set data on the inverse side: you can only set it on the owning side. In other words, $genusNote->setGenus() will work, but $genus->setNotes() would not work: Doctrine will ignore that when saving.

So when you setup the inverse side of a relation, do yourself a favor: do not generate the setter function.

Leave a comment!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=5.5.9",
        "symfony/symfony": "3.1.*", // v3.1.4
        "doctrine/orm": "^2.5", // v2.7.2
        "doctrine/doctrine-bundle": "^1.6", // 1.6.4
        "doctrine/doctrine-cache-bundle": "^1.2", // 1.3.0
        "symfony/swiftmailer-bundle": "^2.3", // v2.3.11
        "symfony/monolog-bundle": "^2.8", // 2.11.1
        "symfony/polyfill-apcu": "^1.0", // v1.2.0
        "sensio/distribution-bundle": "^5.0", // v5.0.22
        "sensio/framework-extra-bundle": "^3.0.2", // v3.0.16
        "incenteev/composer-parameter-handler": "^2.0", // v2.1.2
        "knplabs/knp-markdown-bundle": "^1.4", // 1.4.2
        "doctrine/doctrine-migrations-bundle": "^1.1" // 1.1.1
    },
    "require-dev": {
        "sensio/generator-bundle": "^3.0", // v3.0.7
        "symfony/phpunit-bridge": "^3.0", // v3.1.3
        "nelmio/alice": "^2.1", // 2.1.4
        "doctrine/doctrine-fixtures-bundle": "^2.3" // 2.3.0
    }
}