In part one of this article we saw how to embed forms in a one-to-one relationship. But I often run into a situation where I want to edit a parent model, it’s children models, and maybe add a new child model while I’m at it, greedy I know, but isn’t that the reason why we use the admin generator. Anyway in this article we will examine the one-to-many relationship.
Before we get started here’s a sneak peak of the CategoryForm after the modifications to be done.

Ok, lets get started, consider the following schema:
category:
_attributes: { phpName: Category }
id: { type: INTEGER, size: '11', primaryKey: true, autoIncrement: true, required: true }
name: { type: VARCHAR, size: '255', required: true }
created_at: { type: TIMESTAMP, required: false }
updated_at: { type: TIMESTAMP, required: false }
subcategory:
_attributes: { phpName: Subcategory }
id: { type: INTEGER, size: '11', primaryKey: true, autoIncrement: true, required: true }
name: { type: VARCHAR, size: '255', required: true }
category_id: { type: INTEGER, size: '11', required: true, foreignTable: category, foreignReference: id, onDelete: CASCADE, onUpdate: RESTRICT }
created_at: { type: TIMESTAMP, required: false }
updated_at: { type: TIMESTAMP, required: false }
// lib/forms/CategoryForm.class.php
public function configure() {
// remove timestamps
unset($this['created_at'], $this['updated_at']);
// embed forms only when editing
if (!$this->isNew()) {
// embed all subcategory forms
foreach ($this->getObject()->getSubcategorys() as $subcategory) {
// create a new subcategory form for the current subcategory model object
$subcategory_form = new SubcategoryForm($subcategory);
// embed the subcategory form in the main category form
$this->embedForm('subcategory'.$subcategory->getId(), $subcategory_form);
// set a custom label for the embedded form
$this->widgetSchema['subcategory'.$subcategory->getId()]->setLabel('Subcategory: '.$subcategory->getName());
// change the name widget to sfWidgetFormInputDelete
$this->widgetSchema['subcategory'.$subcategory->getId()]['name'] = new sfWidgetFormInputDelete(array(
'url' => 'category/deleteSubcategory', // required
'model_id' => $subcategory->getId(), // required
'confirm' => 'Sure???', // optional
));
}
// create a new subcategory form for a new subcategory model object
$subcategory_form = new SubcategoryForm();
// embed the subcategory form in the main category form
$this->embedForm('subcategory', $subcategory_form);
// set a custom label for the embedded form
$this->widgetSchema['subcategory']->setLabel('New Subcategory');
}
}
public function bind(array $taintedValues = null, array $taintedFiles = null) {
// remove the embedded new form if the name field was not provided
if (is_null($taintedValues['subcategory']['name']) || strlen($taintedValues['subcategory']['name']) === 0 ) {
unset($this->embeddedForms['subcategory'], $taintedValues['subcategory']);
// pass the new form validations
$this->validatorSchema['subcategory'] = new sfValidatorPass();
} else {
// set the category of the new subcategory form object
$this->embeddedForms['subcategory']->getObject()->setCategory($this->getObject());
}
// call parent bind method
parent::bind($taintedValues, $taintedFiles);
}
public function configure(){
unset($this['created_at'], $this['updated_at'], $this['category_id']);
}
// apps/backend/modules/category/actions/actions.class.php
public function executeDeleteSubcategory(sfWebRequest $request) {
$sub_category = SubcategoryPeer::retrieveByPk($request->getParameter('id'));
$sub_category->delete();
$this->redirect('@category_edit?id='.$sub_category->getCategory()->getId());
}
That’s it, I hop that you enjoyed this article.
Comments So Far » (34 Comments). Add Yours
1. jukea
07 January, 2009
Great work! Your code is clean and concise.
I like it. however, I’d suggest you made it possible to edit subcategories even when the object is new (isNew()==true).
To push the enveloppe even more, I’d like to be able to add multiple embeded forms, ideally using AJAX (why not jquery?).
And to wrap this in some way (plugin ? widget ?) would be a great benefit for the symfony community. But I’m asking for a lot I guess
2. Ahmed El.Hussaini
07 January, 2009
@jukea: thanks for you kind comment, and no it’s not too much to ask, I was planning on extending the admin generator to be 100% Ajax. Anyway, when it’s ready you’ll be the first to know.
3. nvidhive
07 January, 2009
Thank you for your post.
I have gone over the steps of the tutorial and I encounter this error when attempting to delete a Subcategory:
Action “category/deleteSubcategory” does not exist.
I have placed the sfWidgetFormInputDelete class file in /lib/form
Is there something I am doing wrong or missing?
4. Ahmed El.Hussaini
07 January, 2009
@nvidhive: No you didn’t do anything wrong, I thought I’d leave the creation of the action for the developers to delete the subcategory. Anyway I’ll will update the post within the hour. Sorry for any inconvenience.
5. Ahmed El.Hussaini
07 January, 2009
@nvidhive: I forgot to mention, it’s better to place the widget in any lib folder, but not in lib/form folder.
6. nvidhive
07 January, 2009
Wow!
Thank you so much for your quick response. One final thing:
I am obviously missing something from my routing.yml
How would you suggest handling this?
error is as follows:
The “/category/:id/edit.:sf_format” route has some missing mandatory parameters (:id)
I realize you are probably writing this article from a perspective that the reader is much more knowledgeable, but perhaps this final tidbit will help others as well.
Thanks again!
7. Ahmed El.Hussaini
08 January, 2009
@nvidhive: I’m really sorry, it’s my mistake, I fixed the typo, please check the deleteSubcategory function above.
8. jukea
08 January, 2009
wow, great! I’m looking forward for your next version ! Now, you just have to switch to doctrine, and you’ll be my hero
9. Ahmed El.Hussaini
08 January, 2009
@jukea: You know what, I’m having serious thoughts about changing to Doctrine, I’m tired of building complex criteria using Propel. Don’t get me wrong, Propel is a good ORM, but having a prior background and experience in Hibernate makes Doctrine like Pink Floyd music to my ears.
Maybe soon I’ll get to be your hero.
10. Malte Blättermann
09 January, 2009
Hello,
Is this method compatible with 1.1, too?
That would be great.
Thx, Malte
11. Ahmed El.Hussaini
09 January, 2009
@Malte Blättermann: I’ve not tried it with symfony 1.1 but as far as I know embedding forms is a new feature in symfony 1.2, so I guess it can’t be implemented in symfony 1.1, sorry.
12. nvidhive
09 January, 2009
Thank you again for your update.
For anyone interested, with the help of the community I was able to convert this to work with Doctrine. Just the schema change and 1 line in 2 files.
//config/doctrine/schema.yml --- Category: tableName: category actAs: Timestampable: ~ columns: name: type: string(255) notnull: true Subcategory: tableName: subcategory actAs: Timestampable: ~ columns: name: type: string(255) notnull: true category_id: type: integer notnull: true relations: Category: foreignAlias: Subcategorys local: category_id foreign: id type: one onDelete: CASCADE onUpdate: RESTRICT //lib/sfWidgetFormInputDelete.class.php *Replace* $this->setOption('icon', sprintf('http://%s%s/sfPropelPlugin/images/delete.png', $request->getHost(), $request->getRelativeUrlRoot())); *With* $this->setOption('icon', sprintf('http://%s%s/sfDoctrinePlugin/images/delete.png', $request->getHost(), $request->getRelativeUrlRoot())); //apps/backend/modules/category/actions/actions.class.php *Replace* $sub_category = SubcategoryPeer::retrieveByPk($request->getParameter('id')); *With* $sub_category = Doctrine::getTable('Subcategory')->find($request->getParameter('id'));It works perfectly!
13. Ahmed El.Hussaini
09 January, 2009
@nvidhive: great job, thank you very very much.
14. Waff
17 January, 2009
Great article, thanks!
I’ve tried to extend this example whith I18N behavior:
…
Subcategory:
tableName: subcategory
actAs:
Timestampable: ~
I18n:
fields: [desc]
columns:
name:
type: string(255)
notnull: true
desc:
type: string(255)
notnull: true
…
in SubcategoryForm:
public function configure()
{
$this->embedI18n(array(’hu’, ‘en’));
}
when i save the form(Category) symfony throws an exception:
SQLSTATE[23000]: Integrity constraint violation: 1048 Column ‘id’ cannot be null
‘INSERT INTO subcategory_translation (id, lang, desc) VALUES (?, ?, ?)’, array(null, ‘en’, ‘w’)
symfony try to insert the translation table first not subcategory and there is no id yet.
what is the workaround? i’ve try to put some code to the bind method but not works.
thanks for your help!
15. Ahmed El.Hussaini
17 January, 2009
@Waff: First of all thanks for your kind comment, second of all as I see from the schema example in your comment you’re using Doctrine, unfortunately I’ve tested this method only using Propel, but I’ve not actually tried adding I18N support to it, I’ll look into it ASAP and update the method to support I18N, but I’m afraid it will also be in Propel but I’m sure you can manage to import it to Doctrine.
16. Paul
19 January, 2009
Thank’s for the wonderfull guide!
I’ve a question: how can I sort the fields with embed forms? for example in your example, I want see the fields in this order:
-Name
-New Subcategory
-Section
17. Ahmed El.Hussaini
23 January, 2009
@Paul: I think you can do the sorting in the form class.
18. juro
04 February, 2009
Hi,
when I “embed all subcategory forms” using the foreach loop, how can I create a widget, so that I can place it in the generator.yml?
As soon as I want to customise my form using the generator.yml, I loose the list of subcategories.
Thanx for your input
juro
19. juro
04 February, 2009
Answering my own question (thanks to Jon Wage):
$subcategories = new sfForm();
foreach ($this->getObject()->getSubcategorys() as $subcategory) {
$subcategory_form = new SubcategoryForm($subcategory);
$subcategories->embedForm(’subcategory’.$subcategory->getId(), $subcategory_form);
$subcategories->widgetSchema['subcategory'.$subcategory->getId()]->setLabel(’Subcategory: ‘.$subcategory->getName());
$subcategories->widgetSchema['subcategory'.$subcategory->getId()]['name'] = new sfWidgetFormInputDelete(array(
‘url’ => ‘category/deleteSubcategory’, // required
‘model_id’ => $subcategory->getId(), // required
‘confirm’ => ‘Sure???’, // optional
));
}
$this->embed(’subcategories’, $subcaterories);
In generator.yml, I can place ’subcategories’ anywhere I want to.
20. all inclusive
16 February, 2009
hey people this is perfect exactly what i have been looking for a long time here … thanks so much everyone! just great!
21. Sander
19 February, 2009
Thanks for the article, really helped me a lot
But now i’m trying to get this to work with a file upload in for example the subcategory.
When you create a new subcategory, the file is ignored. The form doesn’t even try to upload it.
The file only uploads after the subcategory is created. And you upload the file for a second time
Anyone has an idea how to get the file to upload the first time?
22. refptr
13 March, 2009
@Ahmed: Great Works, thanks a lot.
@Sander: #5667 (In generated forms, Doctrine 1.1 does not save model after linking relations.)
23. spl13
06 April, 2009
I’m trying to override the bind method so that the embedded forms aren’t saved. The problem is that they’re saved before they get to bind. Where should I be removing them?
24. murkein
13 May, 2009
it’s why propel?
25. Ahmed El.Hussaini
14 May, 2009
@murkein: what do you mean exactly. Did you mean why I’m using Propel instead of Doctrine ?
26. leyan
26 May, 2009
hi i get a embedded form ‘user’ into a form ‘client’ and everything is ok but my user class has a realtion many-to-many with a ‘group’ class so when i try to save the object user from the embedded form the groups (from a group list widget) doesnt save, so i try to save it by hand so i call the saveUserGroup() method from my BaseUserGroup class and the i get an exception that the form is not valid. my question is: there is something to do in relations many-to-many to save embedded forms? thx
27. leyan
26 May, 2009
i found this:
http://groups.google.es/group/symfony-devs/msg/99f98f386da3a2fd
t is an issue because the generated form classes
from your schema have generated functions for saving many to many
relationships which are invoked in the overridden doSave() method of each
model form. Since embedded forms don’t call save() or doSave() on the
embedded forms the m2m relationships are never saved. We discovered this
issue last week but haven’t gotten to talk to Fabien about it yet. This is
what I did for sfFormDoctrine to fix the issue.
public function saveEmbeddedForms($con = null, $forms = null)
{
if (is_null($con))
{
$con = $this->getConnection();
}
if (is_null($forms))
{
$forms = $this->embeddedForms;
}
foreach ($forms as $key => $form)
{
if ($form instanceof sfFormDoctrine)
{
$form->bind($this->values[$key]);
$form->doSave($con);
$form->saveEmbeddedForms($con);
}
else
{
$this->saveEmbeddedForms($con, $form->getEmbeddedForms());
}
}
}
i try this way and it works, im using 1.2.5, dont know if the issue is solved in newer version. so if anyone has this problem this a clue
28. Benjamin
03 June, 2009
Hi,
I have some problems with the delete widget:
Fatal error: Class ’sfWidgetFormInputDelete’ not found
I put the widget into /var/www/sf/lib/widget/ do I have to run any command to enable it?
Thanks for your help!
29. Ahmed El.Hussaini
03 June, 2009
@leyan: nice work ,thanks for the tip, I’ll try to test it as soon as possible
30. Ahmed El.Hussaini
03 June, 2009
@Benjamin: just put the contents of the zip file in your_project/lib directory
31. docz
09 June, 2009 (4 weeks ago)
In this image: http://forum.symfony-project.org/index.php/fa/1152/0/
How can I remove “Parameters” “Value” and “Data” and their boxes, and move “Company description” “Country” and so on on the same level as “Main Contact”, “Parameters” making it seem to be part of the same form? This is all generated code, so when I can’t seem to find the templates used for the embedded form fields.
Thank you in advance for any help.
Doc-Z
32. Ahmed El.Hussaini
10 June, 2009 (4 weeks ago)
@docz: are you sure the link you provided is correct ?, it’s not working with me.
33. docz
10 June, 2009 (4 weeks ago)
It seems the link only works if one visits the symfony forum first. Try this link instead: http://www.interactive.no/img/screenshot.GIF
Docz
34. shmayek
18 June, 2009 (3 weeks ago)
Very nice. But how to add choice to add a new subcategory or choose existing one (with null category_id to be clear).
The similar solution for Section. There is only combobox with sections, but how to add possibility to choos to add new section or choose existing? Maybe you have idea hot to approach this or know a good tutorial or similar solutions to look for?