In the last part of this series of articles we introduced the ability to edit multiple child models in the edit screen of the parent model. In this part we will take it a step further by adding I18N behavior to the child models. To make things a little bit clear before we start coding please take a look at the result we want to achieve below.

Before starting I strongly recommend reading part one, and two if you haven’t done so already. OK lets get started, we will be modifying the following files:
- CategoryForm.class.php which represents the parent model
- SubcategoryI18nForm.class.php which represents the form of the i18n model of the child model
- Subcategory.class.php which represents the child model
Files need for this article
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, isI18N: true, i18nTable: subcategory_i18n }
id: { type: integer, required: true, primaryKey: true, autoincrement: true }
category_id: { type: INTEGER, size: '11', required: true, foreignTable: category, foreignReference: id, onDelete: CASCADE}
created_at: { type: TIMESTAMP, required: false }
updated_at: { type: TIMESTAMP, required: false }
subcategory_i18n:
_attributes: { phpName: SubcategoryI18n }
id: { type: integer, required: true, primaryKey: true, foreignTable: subcategory, foreignReference: id, onDelete: CASCADE }
culture: { isCulture: true, type: varchar, size: 7, required: true, primaryKey: true }
name: { type: varchar, size: 50 }
SubcategoryI18nForm
public function configure() {
unset($this['culture'], $this['id']);
}
Subcategory
public function getI18nObject($culture = 'en') {
$this->setCulture($culture);
$i18ns = $this->getSubcategoryI18ns();
if (isset($i18ns[0])) {
return $i18ns[0];
}
return null;
}
CategoryForm
public function configure() {
// remove timestamps
unset($this['created_at'], $this['updated_at']);
// embed forms only when editing
if (!$this->isNew()) {
$user_culture = sfContext::getInstance()->getUser()->getCulture();
// embed all subcategory forms
foreach ($this->getObject()->getSubcategorys() as $subcategory) {
// get the subcategory_i18n model object relative to the current user culture
$subcategory_i18n_object = $subcategory->getI18nObject($user_culture);
// create a new subcategory_i18n form for the current subcategory model object
$subcategory_i18n_form = new SubcategoryI18nForm($subcategory->getI18nObject('en'));
// get widget schema of subcategory_i18n form
$subcategory_i18n_form_widget_schema = $subcategory_i18n_form->getWidgetSchema();
// set the input delete widget
$subcategory_i18n_form_widget_schema['name'] = new sfWidgetFormInputDelete(array(
'url' => 'category/deleteSubcategory', // required
'model_id' => $subcategory->getId(), // required
'confirm' => 'Sure???', // optional
));
// create a new subcategory form for the current subcategory model object
$subcategory_form = new SubcategoryForm($subcategory);
// embed the i18n form
$subcategory_form->embedForm('subcategory_i18n'.$subcategory_i18n_object->getId(), $subcategory_i18n_form);
// subcategory form widget schema
$subcategory_form_widget_schema = $subcategory_form->getWidgetSchema();
// disable label
$subcategory_form_widget_schema['subcategory_i18n'.$subcategory_i18n_object->getId()]->setLabel(false);
// 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());
}
// create a new subcategory form for a new subcategory model object
$subcategory_form = new SubcategoryForm();
// create a new subcategory form for a new subcategory_i18n model object
$subcategory_i18n_form = new SubcategoryI18nForm();
// embed the subcategory_i18n form in the parent subcategory form
$subcategory_form->embedForm('subcategory_i18n', $subcategory_i18n_form);
// subcategory form widget schema
$subcategory_form_widget_schema = $subcategory_form->getWidgetSchema();
// disable label
$subcategory_form_widget_schema['subcategory_i18n']->setLabel(false);
// 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) {
if(!$this->isNew()) {
$user_culture = sfContext::getInstance()->getUser()->getCulture();
// remove the embedded new form if the name field was not provided
if (is_null($taintedValues['subcategory']['subcategory_i18n']['name']) || strlen($taintedValues['subcategory']['subcategory_i18n']['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());
// get subcategory embedded forms
$subcategory_embedded_forms = $this->embeddedForms['subcategory']->getEmbeddedForms();
// set subcategory parent of the subcategory_i18n model object
$subcategory_embedded_forms['subcategory_i18n']->getObject()->setSubcategory($this->embeddedForms['subcategory']->getObject());
// set the culture of the subcategory_i18n model object
$subcategory_embedded_forms['subcategory_i18n']->getObject()->setCulture($user_culture);
}
}
// call parent bind method
parent::bind($taintedValues, $taintedFiles);
}
As you can see this is a lot of code to copy and paste every time you need the functionality, that’s why I’m currently working on extending the Admin Generator in order to have this functionality available when you generate an admin module by default.

1 Comment
Robert Speer | October 2nd, 2009 at 5:03 pm
I’m wondering if not using embedI18n() means this example is this out of date now?