Inserting into a ManyToMany

Video not working?

It looks like your browser may not support the H264 codec. If you're using Linux, try a different browser or try installing the gstreamer0.10-ffmpeg gstreamer0.10-plugins-good packages.

Thanks! This saves us from needing to use Flash or encode videos in multiple formats. And that let's us get back to making more videos :). But as always, please feel free to message us.

The big question is: who is the best superhero of all time? Um, I mean, how can we insert things into this join table? How can we join a Genus and a User together?

Doctrine makes this easy... and yet... at the same time... kind of confusing! First, you need to completely forget that a join table exists. Stop thinking about the database! Stop it! Instead, your only job is to get a Genus object, put one or more User objects onto its genusScientists property and then save. Doctrine will handle the rest.

Setting Items on the Collection

Let's see this in action! Open up GenusController. Remember newAction()? This isn't a real page - it's just a route where we can play around and test out some code. And hey, it already creates and saves a Genus. Cool! Let's associate a user with it!

First, find a user with $user = $em->getRepository('AppBundle:User') then findOneBy() with email set to aquanaut1@example.org:

... lines 1 - 13
class GenusController extends Controller
{
... lines 16 - 18
public function newAction()
{
... lines 21 - 38
$user = $em->getRepository('AppBundle:User')
->findOneBy(['email' => 'aquanaut1@example.org']);
... lines 41 - 51
}
... lines 53 - 115
}

That'll work thanks to our handy-dandy fixtures file! We have scientists with emails aquanaut, 1-10@example.org:

... lines 1 - 22
AppBundle\Entity\User:
... lines 24 - 28
user.aquanaut_{1..10}:
email: aquanaut<current()>@example.org
... lines 31 - 37

We've got a User, we've got a Genus... so how can we smash them together? Well, in Genus, the genusScientists property is private. Add a new function so we can put stuff into it: public function: addGenusScientist() with a User argument:

... lines 1 - 14
class Genus
{
... lines 17 - 174
public function addGenusScientist(User $user)
{
... line 177
}
}

Very simply, add that User to the $genusScientists property. Technically, that property is an ArrayCollection object, but we can treat it like an array:

... lines 1 - 14
class Genus
{
... lines 17 - 174
public function addGenusScientist(User $user)
{
$this->genusScientists[] = $user;
}
}

Then back in the controller, call that: $genus->addGenusScientist() and pass it $user:

... lines 1 - 13
class GenusController extends Controller
{
... lines 16 - 18
public function newAction()
{
... lines 21 - 38
$user = $em->getRepository('AppBundle:User')
->findOneBy(['email' => 'aquanaut1@example.org']);
$genus->addGenusScientist($user);
... lines 42 - 51
}
... lines 53 - 115
}

We're done! We don't even need to persist anything new, because we're already persisting the $genus down here.

Try it out! Manually go to /genus/new. Ok, genus Octopus15 created. Next, head to your terminal to query the join table. I'll use:

./bin/console doctrine:query:sql "SELECT * FROM genus_scientist"

Oh yeah! The genus id 11 is now joined - by pure coincidence - to a user who is also id 11. This successfully joined the Octopus15 genus to the aquanaut1@example.org user.

If adding new items to a ManyToMany relationship is confusing... it's because Doctrine does all the work for you: add a User to your Genus, and just save. Don't over-think it!

Avoiding Duplicates

Let's do some experimenting! What if I duplicated the addGenusScientist() line?

... lines 1 - 13
class GenusController extends Controller
{
... lines 16 - 18
public function newAction()
{
... lines 21 - 40
$genus->addGenusScientist($user);
$genus->addGenusScientist($user); // duplicate is ignored!
... lines 43 - 52
}
... lines 54 - 116
}

Could this one new Genus be related to the same User two times? Let's find out!

Refresh the new page again. Alright! I love errors!

Duplicate entry '12-11' for key 'PRIMARY'

So this is saying:

Yo! You can't insert two rows into the genus_scientist table for the same genus and user.

And this is totally by design - it doesn't make sense to relate the same Genus and User multiple times. So that's great... but I would like to avoid this error in case this happens accidentally in the future.

To do that, we need to make our addGenusScientist() method a little bit smarter. Add if $this->genusScientists->contains()... remember, the $genusScientists property is actually an ArrayCollection object, so it has some trendy methods on it, like contains. Then pass $user. If genusScientists already has this User, just return:

... lines 1 - 14
class Genus
{
... lines 17 - 174
public function addGenusScientist(User $user)
{
if ($this->genusScientists->contains($user)) {
return;
}
$this->genusScientists[] = $user;
}
}

Now when we go back and refresh, no problems. The genus_scientist table now holds the original entry we created and this one new entry: no duplicates for us.

Next mission: if I have a Genus, how can I get and print of all of its related Users? AND, what if I have a User, how can I get its related Genuses? This will take us down the magical - but dangerous - road of inverse relationships.

Leave a comment!

This course is built on Symfony 3, but most of the concepts apply just fine to newer versions of Symfony.

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
        "stof/doctrine-extensions-bundle": "^1.2" // v1.2.2
    },
    "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
    }
}