Symfony – Doctrine – Saving Many-To-Many (M:M) relationships
Pretty much one of the hardest things I had to get my head around, saving many-to-many (M:M) relationships in Symfony (using Doctrine), but after some mindset adjustment and a lot of googling, I found it to be very easy to implement. It took several weeks and perusing several different google searches to piece together enough information to finally get my head around this process, hopefully this post will save some babies from being punched due to frustration.
Schema first. Say you have a M:M relationship between books and authors, meaning that an author can write more than one book (many) and a book can have more than one author (many). The schema to support this requires that we have a 3rd table, to which I refer as a linking table. The linking table’s main purpose is to link data from the author’s table to the data from the book’s table, which forces us into 2NF (second normal form), by ensuring we are not saving duplicate data in either the author table or the book table.
Your schema should look something like the following (remember that your table engines must be innodb to establish a FK relationship):

Author_Book Many-To-Many
Now we need to rebuild the schema so we can rebuild the model to take advantage of these relationships established by the schema.
php symfony doctrine:build-schema
Now that the schema has been rebuilt, we need to do some manual edits to ensure the model files are created correctly. So open /config/doctrine/schema.yml and you should see something like:
Author:
tableName: author
columns:
...
relations:
LinkingAuthorBook:
local: id
foreign: book_id
type: many
Book:
tableName: book
columns:
...
relations:
LinkingAuthorBook:
local: id
foreign: author_id
type: many
After
Author:
tableName: author
columns:
...
relations:
Book:
class: Book
refClass: LinkingAuthorBook #This will allow you to reference Book rows from an Author object
local: author_id #Local value refers to the current object, in this case Author
foreign: book_id #Foreign value refers to the object you wish to link to from Author, in this case Book
Book:
tableName: book
columns:
...
relations:
Author:
class: Author
refClass: LinkingAuthorBook #This will allow you to reference Author rows from a Book object
local: book_id #Local value refers to the current object, in this case Author
foreign: author_id #Foreign value refers to the object you wish to link to from Book, in this case Author
Note the changes made to the after schema.yml file. We added ‘class’ and most importantly, ‘refClass’. You can read more information about refClass @ doctrine’s website.
Now rebuild the model:
php symfony doctrine:build-model
The auto-generated classes created after rebuilding the model will be aware of the M:M relationship you have established in your schema which results in saving these with minimal effort.
As part of the rebuild “stack”, you must also rebuild your filters and forms, and clear your cache. So be sure to do the following as well:
php symfony doctrine:build-filters php symfony doctrine:build-forms php symfony cc
Now lets see saving a M:M relationship in action, lets add a select double list to your Author form (assuming you have sfFormExtraPlugin installed), just add the following to /lib/form/doctrine/AuthorForm.class.php:
$this->widgetSchema['book_list']->setOption('renderer_class', 'sfWidgetFormSelectDoubleList');
When you render your Author form, you should now have a double list with all the books you have in your book table. Select a few, then click save, you should see that they were saved against the Author you are currently editing. You can remove, add, etc as you see fit.
That’s it! You set up your app to handle the saving of M:M relationships with (hopefully) minimal effort.
—Edit 5.25.2010
Some people are having a problem with this example with respect to the linking table in the given example. As far as I know, the linking table MUST be present in your schema and available via the resulting model class, otherwise doctrine will not know how to store the link relationship between book and author.
This entry was posted by Mike Purcell on December 9, 2009 at 4:49 pm, and is filed under Doctrine, Symfony. Follow any responses to this post through RSS 2.0.You can leave a response or trackback from your own site.
-
#5 written by Mike Purcell 2 years ago
can i now execute doctrine:build-schema when i added new table in databese or what???
Manually adding the refClass/class statements comes with a bit of overhead in that you have to ensure that they stay in place after rebuilding the schema. So build your schema using the doctrine:build-schema, then manually add the refClass/class statements in their correct places. Carrying custom changes to your schema.yml file isn’t too bad, because your schema shouldn’t change that often.
-
#7 written by Juan 2 years ago
Thanks a lot. But there is a problem.
What happends with the “book” form ?
Suppose that you don’t need to show the “authors” of a book, when you are at the book’s form. Then you unset the “author_list” field in the form’s “configure” method or in the generator.yml … Well, then the book’s form does not show the author, but if you save the book’s form, the associations between authors and the book are erased. ¿ Why ?
Do you know how to solve this problem ?
-
#8 written by Juan 2 years ago
Opsss … Human Error. Sorry.
The problem is solved if you “unset” the field, in the “configure()” form’s method.
Example:
unset($this->widgetSchema['author_list']);If you don’t include the field in the “generator.yml”,
book.yml
form:
display: [ ... all field without author_list ... ]the field still exists in the form. Therefore it’s needed unset the field in the “configure” method.
-
#10 written by Mike Purcell 2 years ago
Great question, I had to do the same thing for one of my linking tables.
For those of you who may not know, Le Tim Ly is referring to what is known as ‘meta data’. Basically it’s data about data. For example his idea of adding a sort_id to a linking row is data about the linking row, hence meta data.
I was actually able to implement this, but it took some fairly drastic measures. My goal is to write up an article about how to do it within the next few weeks, so check back by end of February and I should have something up (if not too busy at work).
-
#11 written by Mike Purcell 2 years ago
@Le Tim Ly: I finally finished an article on how to maintain sort order… you can find it here; http://melikedev.com/2010/04/06/symfony-saving-metadata-during-form-save-sort-ids/
-
Hi, excellent post.. thanks a lot!
Its very important to note that the table/class ‘LinkingAuthorBook’ need not be defined.However, on the Author form, there is a list that displays book_id. How can we configure that to show book_name? Can you help?
I am yet to explore the power of Doctrine/Symfony when it comes to defining name & value separately in select/radio/checkbox elements.
Thanks Again. -
#15 written by Leo 1 year ago
Thank you for your post.
I’m having problems though, I get the error Couldn’t find class ContactRegel
I have not defined this as a table, just as a refclass in 3 tables.
this is my schema:
http://pastebin.com/Q3pEUkSkwhats going wrong here?
-
#16 written by Nelson 1 year ago
I just wanna say thanks for your very clear post… thankfully I read it before I get myself in much problems… I was thinking in using emebed forms… but this… this is so much better than that! thanks again…
anyway, I wish you were explained those local and foreign fields in the schema.yml file… it took me some time to get it right…
-
#17 written by Mike Purcell 1 year ago
Hi, excellent post.. thanks a lot!
Its very important to note that the table/class ‘LinkingAuthorBook’ need not be defined.However, on the Author form, there is a list that displays book_id. How can we configure that to show book_name? Can you help?
I am yet to explore the power of Doctrine/Symfony when it comes to defining name & value separately in select/radio/checkbox elements.
Thanks Again.You can reference book_name by changing your book_list widget to get names, vs ids. Check out http://melikedev.com/2010/04/29/symfony-sfwidgetformdoctrinechoice-vs-sfwidgetformchoice-when-to-use-which/ for more info.
-
#18 written by Mike Purcell 1 year ago
Thank you for your post.
I’m having problems though, I get the error Couldn’t find class ContactRegel
I have not defined this as a table, just as a refclass in 3 tables.
this is my schema:
http://pastebin.com/Q3pEUkSkwhats going wrong here?
I had to actually create the linking table in my given example. After the table was created, I rebuilt the model. I don’t see how you could do it without the reference class, otherwise doctrine won’t know how to store the links.
-
#19 written by Mike Purcell 1 year ago
I just wanna say thanks for your very clear post… thankfully I read it before I get myself in much problems… I was thinking in using emebed forms… but this… this is so much better than that! thanks again…
anyway, I wish you were explained those local and foreign fields in the schema.yml file… it took me some time to get it right…
Duly noted. I added some comments to avoid the confusion. Thanks for the feedback.
-
Hi Mike, got one question.
Let’s say I want to maintain the order of selected books, given your schema, what will be the YAML definition to create that extra ‘sequence’ column?Also, if you open the BaseAuthorForm you will see towards the end…
$link = array_diff($values, $existing);
if (count($link))
{
$this->object->link(‘Book’, array_values($link));
}How can we leverage this to save the ‘sequence’ field?
-
#22 written by Mike Purcell 1 year ago
@Leo: Nice!
@Prasad: To answer your first question about the schema, you should have a new column on your linking_book_author table. It appears you named yours `sequence`. So you should have 3 cols:
book_id
author_id
sequence_id
Now when you build your schema (php symfony doctrine:build-schema) you should see the `sequence_id` as part of your schema for the `linking_book_author` table. When you rebuild your model doctrine will now be aware of this column.For a detailed explanation to your second question; of how to save your `sequence_id` check out this other post: http://melikedev.com/2010/04/06/symfony-saving-metadata-during-form-save-sort-ids/
-
#24 written by Mike Purcell 1 year ago
Thanks for the article. It works! I was wondering if you were able to add “Versionable” to the schema and make it work? There seems to be some foreign key issues with LinkingAuthorBook when “Versionable” is enabled. Author with Versionable works fine, however.
Glad to hear it worked for ya. No I haven’t used versionable yet, if you get it working plz post your findings.
-
#30 written by Calvin 1 year ago
Hi there,
Thanks for this post. However, I’m desperately trying to find out how to make this admin_double_list work for the admin generator in Symfony 1.4.
All of the articles I’ve read about this issue talk about widgets but having come form version 1.1 I’ve no idea whether this is relevant at all to the admin generator?!
It used to just work by adding the through_class and type into the generator.yml file!!
Any ideas?
Best regards and with enormous thanks,
C -
#32 written by Emmanuel 1 year ago
Here is my code:
http://pastebin.com/419XYtj6
Thanks a lot and sorry my bad english! -
#33 written by Mike Purcell 1 year ago
Here is my code:
http://pastebin.com/419XYtj6
Thanks a lot and sorry my bad english!Your code looks good… I’m not seeing where the problem could be occuring. Did you do the rebuild stack? (build-filters, build-forms, and clear cache). I also assume you have sfFormExtraPlugin installed?
-
#37 written by glavvred 1 year ago
hi there, Mike.
thanks for the clues to m:m, but i’ve got a problem here – is it possible to set two or more m:m connections to one table at once?
i mean – one company can have multiple delivery zones and multiple subway stations as well.
will you please take a look?
http://pastebin.com/d4JpJrNY -
#38 written by Mike Purcell 1 year ago
@glavvred: Yes, its perfectly acceptable to link a single table to multiple tables, and as you normalize more complex datasets you will find yourself doing it more often. Your schema looks good, except I would take the relations off the linking tables. You only need to set relation info on the lookup tables (via refclass), not the linking tables.
Also, don’t forget to add a new entry into your form object to display the data from the 2nd link.
-
#39 written by glavvred 1 year ago
that finally worked as planned – was some kind of bug w\ that fieldnames – as i turned them all into field_names_separated_like_this the case was solved. thanks for clues once more.
final version – http://pastebin.com/165Mc8C4 -
#40 written by Mike Purcell 1 year ago
That is odd… Maybe they didn’t line up with the table names? So if you had Linking_Author_Book as the table name in the schema file, but did refClass: LinkingAuthorBook, then ya that won’t work.
Also I would take the relations of your linking tables unless you need them for something else. Doctrine is fully aware of the links via the refClass you established on the lookup tables.
-
#41 written by Marcelo 1 year ago
Hi Mike, the book_list is declared in schema ? Or it’s just an additional field created in /lib/form/doctrine/AuthorForm.class.php ?
Because I created an additional field this way:
$this->widgetSchema['assign_list'] = new sfWidgetFormDoctrineChoice(array(‘model’ => ‘sfGuardUser’));
$this->widgetSchema['assign_list']->setOption(‘renderer_class’, ‘sfWidgetFormSelectDoubleList’);and the form couldn’t save the list
-
#42 written by Marcelo 1 year ago
Marcelo:
Hi Mike, the book_list is declared in schema ? Or it’s just an additional field created in /lib/form/doctrine/AuthorForm.class.php ?
Because I created an additional field this way:Now I understood that field_list was created automatically, worked! Thanks
$this->widgetSchema['assign_list'] = new sfWidgetFormDoctrineChoice(array(‘model’ => ’sfGuardUser’));
$this->widgetSchema['assign_list']->setOption(‘renderer_class’, ’sfWidgetFormSelectDoubleList’);
and the form couldn’t save the list -
#44 written by Mike Purcell 1 year ago
@Jon: Check at which level? Database or Model? If you want to check on database level (using MySQL) just do a ‘show create table ‘, it will list out your foreign keys. If you want to check on the model level you can check in your schema.yml file or in the base classes.
In the schema yml file you should see something like:
relations:
Author:
class: Author
refClass: LinkingAuthorBook
local: author_id
foreign: book_idAnd in the base class file (after you rebuild model with FK relations), you should see something like:
$this->hasMany('Book', array(
'refClass' => 'LinkingAuthorBook',
'local' => 'author_id',
'foreign' => 'book_id')); -
#45 written by Jon 1 year ago
Thanks Mike, I don’t explain very well.
To create a relation, I do:
$author->book[] = $book;
$author->save();or something similar.
How can I check if this relation exists? If I save the relation and it exists, I will receive a error for duplicate PKs. But I want know how can I check if this relation exists before that I save it.I can check directly the LinkingAuthorBook model passing the author_id and book_id, but i want know if exists other method.
Thanks and sorry for my English
-
#47 written by Emmanuel 1 year ago
I find the problem! The name of the widget must be equal to $this->widgetSchema array key. In this case:
“codecs_list” => $this->widgetSchema['codecs_list']->setOption(‘renderer_class’, ‘sfWidgetFormSelectDoubleList’)
Emmanuel:
Here is my code:
http://pastebin.com/419XYtj6
Thanks a lot and sorry my bad english!Your code looks good… I’m not seeing where the problem could be occuring. Did you do the rebuild stack? (build-filters, build-forms, and clear cache). I also assume you have sfFormExtraPlugin installed?
-
one question, example… i want also to save in book the number of authors selected. I rewrite the save function but i don;t know how to count the numbers of selected authors in the new save function…
i read that you wrote to use: $author->LinkingAuthorBook->count() but it doesn’t work….
any ideea?thank you
-
Do you think a many-to-many problem could cause the doctrine:build-schema command to fail? I am having a hell of a time trying to figure this problem out. I posted the details of my problem here:
http://www.symfonyexperts.com/question/show/id/156
I am offering a $20 prize to anyone who can help.
-
#52 written by rodrigoBlack 1 year ago
Thanks for the tutorial. For me it is working fine except when i try to access LinkingAuthorBook module created by admin-generator an error appears:
500 | Internal Server Error | InvalidArgumentException
The “/linkingAuthorBook/:Array/edit.:sf_format” route has some missing mandatory parameters (:Array).I know the error is related to the routing file of the generated module because column has the attribute Array.
Thanks in advance.
-
#55 written by Birdy 1 year ago
Thanks for the article Mike,
I was looking for a way to add a record dynamically, without using a form. Since the record has a many-to-many relationship, it was not obvious to me. But finally I found:
$author = Doctrine::getTable(‘author’)->find(array(‘Stephen Hawking’));
$book = new Book();
$book->setName(‘A Short History Of Time’);
$book->author[] = $author;According to the answer I found on the web, Doctrine takes care of the related records.
http://stackoverflow.com/questions/2646089/how-doctrine-saves-new-related-objects -
Regarding the problem I ran into, apparently symfony/doctrine has a well known bug where it mangles names in the schema where the a single character follows an underscore. So these fields will cause errors:
phone_1
phone_2
phone_3
You can rename the fields themselves:
phone_one
phone_two
phone_three
Or you can just use an alias in the schema:
phone_1:
name: phone_1 as phone_one
type: string(45)
fixed: false
unsigned: false
primary: false
notnull: true
autoincrement: false
phone_2:
name: phone_2 as phone_two
type: string(45)
fixed: false
unsigned: false
primary: false
notnull: true
autoincrement: false
phone_3:
name: phone_3 as phone_three
type: string(45)
fixed: false
unsigned: false
primary: false
notnull: true
autoincrement: false -
Hi!
Thanks for the useful stuff!
I’m in a situation when I need to keep some additional info in the reference table.
In a couple of words, the same example, but let’s relate not authors, but people to a book so in the reference table I need to keep info, what the specific person did for the book.
The reference table should look like follows:
person_id
book_id
role
Where role can be let’s say author, editor, translator etc.
Now the question is how to set the relation with the role?
Thanks a lot! -
#63 written by Mike Purcell 6 months ago
@Ed: Hello. If I were you I would use role_id vs role, this way you could link to a ‘lookup’ table, so if you ever need to change a value you only have to do it in one place. What I did to accomplish what you are asking is embed the book/author linking form within the book form. Then on the book/author linking form I created a dropdown select list of ‘roles’, so for every linking row, you can select a role_id.
-
#70 written by lilou 2 months ago
Hi!
thanks for this article!!
i’m using symfony and doctrine 1.4.
I’m in the same situation than Ed. I would like to embed a form related to a reference table (i’ve n:m relation between product and lab):
product_id
lab_id
quantityin the Product form , i embeded the productLabForm to add quantities. But the problem is if i have this case:
product 1, lab 1, quantity 20
product 1, lab 2, quantity 30
product 1, lab 3, quantity 40juste one occurence is saved in the database, there is a problem for saving all the occurences, the new added quantities for the same product erase the old ones
((
i’m really desperate, i don’t find any documentation about this case..Do you have an idea? -
#72 written by Luis Angel 1 month ago
Friend first of all apologize for making this comment as a question … I know there are blogs for that … and stuff but I’ve asked for help wherever and have not been able to help .. but hey .. this is my problem
Working with MySQL 1.4.9 + Symfony and Doctrine …
I have three tables …..CesIngreso:
actAs {Timestampable: ~}
columns:
name: {type: string (7), notnull: true, unique: true}
- In which I store two data, which is income (In, Out)CesSecciones:
columns:
sections: {type: string (50), notnull: true}
start: {type: time}
end: {type: time}
- I store three data plus the start and end of each Section, Sessions (Morning, Afternoon, Special)CesUser:
columns:
Photo: {type: string (255)}
user: {type: string (30), notnull: true}
name: {type: string (50), notnull: true}
apeuno: {type: string (50), notnull: true}
apedos: {type: string (50), notnull: true}
date: {type: date, notnull: true}
- I store the data of Users (n number of users)Need to create a form for Attendance Record …
record attendance of users who are registered in the system: … but more importantly
that users need when you go to register your
assistance ….. I can only record 1 input and 1 output (CesIngreso) for each section (Morning, Afternoon, Extraordinary) daily …
Friend I have tried it in different ways, but I can not make a user can save only 1 input and 1 output for each work session on the day …..
The form should only take …. Users, Section, Income …. and I want to do is that for example the user Pepe … when you go to register your attendance in Section (Morning, Afternoon, Extra ), if you have already registered an entry in the morning I can only record the output and so on for each section …
I hope I’ve managed to explain …. and thank you very much for
advance …. any help or idea is welcome … and sorry if I should not ask here … greetings -
- Comment Feed for this Post
- Symfony /w Doctrine – Many to Many (M:M) – Primary Keys are Your Friend | Me Like Dev
- Symfony – Saving Metadata During Form Save (sort ids) | Me Like Dev
- VieuxSteak.ch » Doctrine m2m relations examples
- Symfony – Doctrine – Call to a member function evictAll on a non-object
- Symfony – Standard API for Logging Using __callStatic
- Symfony – Symfony 2 Security Audit Results
- Symfony – Serve External Apis from External Hosts – jQuery – Yui
- Symfony – sfGuardPlugin – Use Email Instead of Username
- Symfony – PHP – Possibly Forking 1.x so Invested Companies don’t Lose Millions
- Symfony – Remove .php From Controller using Symlink
- Symfony – Add ReCaptcha to JQuery Dialog (Lightbox)
- Symfony – Custom (Private) Plugins – Naming Convention
- Symfony – Standard API for Logging


Thank you for this! I had been going crazy trying to nail down exactly how to work with M:M relations and forms, and this cleared it up for me.
In my case, I did not specify the foreign relations manually, I just created the linking table and had symfony detect the relations. It worked fine for the models, but the forms did not generate the fields for the M;M relation. After modifying the schema as you did and rebuilding, the forms appeared as they should (and the model did not change at all, surprise).
Thanks again!