Buy

Let's talk about the famous, ManyToMany relationship. We already have a Genus entity and also a User entity. Before this tutorial, I updated the fixtures file. It still loads genuses, but it now loads two groups of users:

37 lines src/AppBundle/DataFixtures/ORM/fixtures.yml
... lines 1 - 22
AppBundle\Entity\User:
user_{1..10}:
email: weaverryan+<current()>@gmail.com
plainPassword: iliketurtles
roles: ['ROLE_ADMIN']
avatarUri: <imageUrl(100, 100, 'abstract')>
user.aquanaut_{1..10}:
email: aquanaut<current()>@example.org
plainPassword: aquanote
isScientist: true
firstName: <firstName()>
lastName: <lastName()>
universityName: <company()> University
avatarUri: <imageUrl(100, 100, 'abstract')>

The first group consists of normal users, but the second group has an isScientist boolean field set to true. In other words, our site will have many users, and some of those users happen to be scientists.

That's not really important for the relationship we're about to setup, the point is just that many users are scientists. And on the site, we want to keep track of which genuses are being studied by which scientists, or really, users. So, each User may study many genuses. And each Genus, may be studied by many Users.

This is a ManyToMany relationship. In a database, to link the genus table and user table, we'll need to add a new, middle, or join table, with genus_id and user_id foreign keys. That isn't a Doctrine thing, that's just how it's done.

Mapping a ManyToMany in Doctrine

So how do we setup this relationship in Doctrine? It's really nice! First, choose either entity: Genus or User, I don't care. I'll tell you soon why you might choose one over the other, but for now, it doesn't matter. Let's open Genus. Then, add a new private property: let's call it $genusScientists:

This could also be called users or anything else. The important thing is that it will hold the array of User objects that are linked to this Genus:

174 lines src/AppBundle/Entity/Genus.php
... lines 1 - 14
class Genus
{
... lines 17 - 74
private $genusScientists;
... lines 76 - 172
}

Above, add the annotation: @ORM\ManyToMany with targetEntity="User".

174 lines src/AppBundle/Entity/Genus.php
... lines 1 - 14
class Genus
{
... lines 17 - 71
/**
* @ORM\ManyToMany(targetEntity="User")
*/
private $genusScientists;
... lines 76 - 172
}

Doctrine ArrayCollection

Finally, whenever you have a Doctrine relationship where your property is an array of items, so, ManyToMany and OneToMany, you need to initialize that property in the __construct() method. Set $this->genusScientists to a new ArrayCollection():

174 lines src/AppBundle/Entity/Genus.php
... lines 1 - 14
class Genus
{
... lines 17 - 76
public function __construct()
{
... line 79
$this->genusScientists = new ArrayCollection();
}
... lines 82 - 172
}

Creating the Join Table

Next... do nothing! Or maybe, high-five a stranger in celebration... because that is all you need. This is enough for Doctrine to create that middle, join table and start inserting and removing records for you.

It can be a bit confusing, because until now, every table in the database has needed a corresponding entity class. But the ManyToMany relationship is special. Doctrine says:

You know what? I'm not going to require you to create an entity for that join table. Just map a ManyToMany relationship and I will create and manage that table for you.

That's freaking awesome! To prove it, go to your terminal, and run:

./bin/console doctrine:schema:update --dump-sql

Boom! Thanks to that one little ManyToMany annotation, Doctrine now wants to create a genus_user table with genus_id and user_id foreign keys. Pretty dang cool.

JoinTable to control the... join table

But before we generate the migration for this, you can also control the name of that join table. Instead of genus_user, let's call ours genus_scientists - it's a bit more descriptive. To do that, add another annotation: @ORM\JoinTable. This optional annotation has just one job: to let you control how things are named in the database for this relationship. The most important is name="genus_scientist":

175 lines src/AppBundle/Entity/Genus.php
... lines 1 - 14
class Genus
{
... lines 17 - 71
/**
* @ORM\ManyToMany(targetEntity="User")
* @ORM\JoinTable(name="genus_scientist")
*/
private $genusScientists;
... lines 77 - 173
}

With that, find your terminal again and run:

./bin/console doctrine:migrations:diff

Ok, go find and open that file!

37 lines app/DoctrineMigrations/Version20160921164430.php
... lines 1 - 10
class Version20160921164430 extends AbstractMigration
{
... lines 13 - 15
public function up(Schema $schema)
{
// this up() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on \'mysql\'.');
$this->addSql('CREATE TABLE genus_scientist (genus_id INT NOT NULL, user_id INT NOT NULL, INDEX IDX_66CF3FA885C4074C (genus_id), INDEX IDX_66CF3FA8A76ED395 (user_id), PRIMARY KEY(genus_id, user_id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB');
$this->addSql('ALTER TABLE genus_scientist ADD CONSTRAINT FK_66CF3FA885C4074C FOREIGN KEY (genus_id) REFERENCES genus (id) ON DELETE CASCADE');
$this->addSql('ALTER TABLE genus_scientist ADD CONSTRAINT FK_66CF3FA8A76ED395 FOREIGN KEY (user_id) REFERENCES user (id) ON DELETE CASCADE');
}
... lines 25 - 28
public function down(Schema $schema)
{
// this down() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on \'mysql\'.');
$this->addSql('DROP TABLE genus_scientist');
}
}

Woohoo!

Now it creates a genus_scientist table with those foreign keys. Execute the migration:

./bin/console doctrine:migrations:migrate

Guys: with about 5 lines of code, we just setup a ManyToMany relationship. Next question: how do we add stuff to it? Or, read from it?

Leave a comment!

  • 2017-07-03 Victor Bocharsky

    Hey Moni,

    That's a good question! The questions is which side is inverse one. There're a few subtle difference between inverse and owning side. We explain the difference here: https://knpuniversity.com/s... . You can also take a look at explanation in Doctrine docs: http://docs.doctrine-projec... . So it's just depends on you as a developer to choose which side is inverse and which one is owning, i.e. depends on your further application logic.

    Also, here's a screencast about how to synchronize owning and inverse sides which can be useful for you:
    https://knpuniversity.com/s... .

    Cheers!

  • 2017-07-02 Мони Мони

    How to define in witch entity User or Genus we must use @ManyToMany annotation. Why do you use It in User instead of Genus class?

  • 2017-02-24 Rob

    Yo weaverryan !

    It does help! Now I would have a max of like 15 color fields in Setup (i only said 3), but that would be it. I should have stated 15 fields first... For my learning I'm going to set it up and run some tests to see how it works out. I don't see another way at the moment and this advice will keep me going! I'm only messing around with it anyway and thats a good thing! Thanks!

  • 2017-02-23 weaverryan

    Yo Rob!

    I don't see a problem with this :). Often, when we have multiple "things" (like colors), there is a temptation to turn it into a collection field - e.g. a "colors" property on Setup, perhaps a ManyToMany relationship or something different. But, I hate this - if you know that you will always have 3 fields, then yea, I like color1, color2, and color3. And I don't see a problem with have 3 relation fields. Your code might have some small duplication because of this, but it will be really easy to understand :).

    I hope that helps! Cheers!

  • 2017-02-23 Rob

    I have a question regarding setting relationship to individual fields in a entity. For example, I have a entity called Setup which has fields such as Color1, Color2, Color3. Then I have a second table called Plate (printing plate info for each color in the Setup table). Now in my app I would like to relate a Plate to each Color1, Color2, Color3 field in the Setup table. I'm not sure if it is suggested to have relationships on individual fields within a entity, like this: Color1Plate, Color2Plate, etc.

  • 2016-11-21 Victor Bocharsky

    Great!

    Look at the "Sortable" extension from StofDoctrineExtensionsBundle - it could help with your issue about ordering entities.

    Cheers!

  • 2016-11-21 Greg

    Hey Victor,

    I did like this in my project but I would like to know if there is another way to do.
    I'm glad to know this is a good way to do ;)

    Thanks again for your answer. And like usual you're the best ;)

    Cheers.

  • 2016-11-21 Victor Bocharsky

    Hey Greg,

    That's really good question! Well, probably you can iterate over the collection and order it by some custom logic... but I think it'll better to use OneToMany + ManyToOne relationships instead of ManyToMany. i.e. you need an auxiliary entity (let's call it GenusScientist in our example) which will hold your position property (or any other additional properties you need for). Let me summarize that all: you will need OneToMany relationship between Genus and GenusScientist, and then ManyToOne relationship between GenusScientist and User. As a result, you will have the same ManyToMany relationship between Genus and User, but you can also add additional fields to the GenusScientist entity. It's a common practice in such situation.

    I hope you understand what I mean here and it makes sense for you.

    Cheers!

  • 2016-11-20 Greg

    Hi,

    Once again your videos are really awesome.
    I have a question about join table how can I do if I want an extra column like position.
    Imagine with your example like scientist, I want to order them by a position. Is the ManyToMany still work ? Or should I made my relation manually?
    Thanks again.
    G.