Embedded i18n form in an embedded relation creates 2 records - symfony1

Symfony 1.4
Propel (with sfPropel15Plugin)
I have a multilanguage Gallery with the following schema:
# Galleries
pi_gallery:
_attributes:
phpName: Gallery
isI18N: true
i18nTable: pi_gallery_i18n
_propel_behaviors:
sortable: ~
id: ~
active:
type: boolean
default: true
required: true
created_at: ~
updated_at: ~
pi_gallery_i18n:
_attributes:
phpName: GalleryI18n
id:
type: integer
foreignTable: pi_gallery
foreignReference: id
required: true
primaryKey: true
onDelete: cascade
culture:
isCulture: true
type: varchar
size: 7
required: true
primaryKey: true
name:
type: varchar
size: 255
required: false
description:
type: longvarchar
required: false
# Images
pi_gallery_image:
_attributes:
phpName: GalleryImage
isI18N: true
i18nTable: pi_gallery_image_i18n
id: ~
gallery_id:
type: integer
foreignTable: pi_gallery
foreignReference: id
required: true
image:
type: varchar
size: 255
required: true
created_at: ~
updated_at: ~
pi_gallery_image_i18n:
_attributes:
phpName: GalleryImageI18n
id:
type: integer
foreignTable: pi_gallery_image
foreignReference: id
required: true
primaryKey: true
onDelete: cascade
culture:
isCulture: true
type: varchar
size: 7
required: true
primaryKey: true
description:
type: varchar
size: 255
required: false
I'm trying to embed the Image forms in the Gallery using the following:
# GalleryForm.class
public function configure()
{
unset(
$this['alias'],
$this['created_at'],
$this['updated_at']
);
$this->widgetSchema['article_id']->setOption('renderer_class', 'sfWidgetFormPropelJQueryAutocompleter');
$this->widgetSchema['article_id']->setOption('renderer_options', array(
'model' => 'Article',
'url' => '/article/ajax'
));
$this->validatorSchema['article_id'] = new sfValidatorPass();
$this->embedI18n(array('es', 'en', 'de', 'it', 'fr'));
$this->widgetSchema->setLabel('en','English');
$this->widgetSchema->setLabel('es','EspaƱol');
$this->widgetSchema->setLabel('de','Deutsch');
$this->widgetSchema->setLabel('it','Italiano');
$this->widgetSchema->setLabel('fr','Francais');
$this->embedRelation('GalleryImage'); // Embeds the Relation between the GalleryImage model and the Gallery Model
}
# GalleryImageForm.class:
public function configure()
{
unset(
$this['created_at'],
$this['updated_at'],
$this['gallery_id'],
$this['sortable_rank']
);
if ($this->isNew()) unset($this['id']);
$this->embedI18n(array('es', 'en', 'de', 'it', 'fr'));
$image = $this->getObject()->getImage();
$template = (!is_null($image) || $image != "") ? '<div>%file%<br />%input%<br />%delete% %delete_label%</div>' : '';
$this->widgetSchema['image'] = new sfWidgetFormInputFileEditable(array(
'label' => 'Imagen',
'file_src' => '/'.sfConfig::get('sf_upload_dir_name').'/images/galleries/thumbs/'.substr($this->getObject()->getImage(),0,-4) . '.jpg',
'is_image' => true,
'edit_mode' => !$this->isNew() && $image != "",
'with_delete' => true,
'delete_label'=>'Eliminar archivo existente',
'template' => $template
));
$this->validatorSchema['image_delete'] = new sfValidatorPass();
$this->validatorSchema['image'] = new sfValidatorFile(array(
'path' => sfConfig::get('sf_upload_dir').'/images/galleries',
'required' => false,
'mime_types' => 'web_images'
));
}
This appears to embed the forms as expected ... initially. The GalleryForm appears with Multilanguage Descriptions and the ImageForms embed beneath them. So far so good.
Saving the form however shows that all is not good.
Two records are saved initially, one with just the image and the other with just the i18n fields. The i18n fields also have the id of the second record added so there is no way of relating the image to the i18n fields. Maybe the order of saving the forms is wrong?
Has anyone successfully got a form to work that embeds I18n in an embedded Relation? Or does anyone have any idea of a workaround? I've read about something about overriding saveEmbeddedForms but I don't even know where to start with that.
Any help appreciated.

Fixed in the sfPropelORMPlugin:
https://github.com/propelorm/sfPropelORMPlugin/issues/13
https://github.com/propelorm/sfPropelORMPlugin/pull/76
https://github.com/propelorm/sfPropelORMPlugin/issues/38

Related

Overriding Symfony Form's Save Function

I have two issues, need some help with.
I have a table which is referenced by a foreign key to a second table:
member_child:
_attributes: { phpName: MemberChild }
id: { type: INTEGER, size: '11', primaryKey: true, autoIncrement: true, required: true }
member_id: { type: INTEGER, size: '11', required: true, foreignTable: member, foreignReference: id }
child_id: { type: INTEGER, size: '11', required: true, foreignTable: child, foreignReference: id }
and child:
child:
_attributes: { phpName: Child }
id: { type: INTEGER, size: '11', primaryKey: true, autoIncrement: true, required: true, onDelete: cascade }
username: { type: VARCHAR, size: '45', required: true, defaultValue: '' }
display: { type: TINYINT, size: '1', required: true, defaultValue: '1' }
...etc
(obviously this is propel)
Now, when I want to create a child object, using a form, I need to do two things:
On submit, submit a member id
override the doSave function so when the child is created, I can also create the member_child object
How can I accomplish these issues?
I agree, you can use embedForm like pankar said. Also you can override save method of your forms like this:
$this->childForm = new ChildForm();
$this->childMemberForm = new ChildMemberForm();
//binding, checking if form was sent etc.
if ($this->childForm->isValid() && $this->childMemberForm->isValid())
{
//save method should return saved object
$childObject = $this->childForm->save();
//therefore this id could be used by next object
$this->childMemberForm->save(childObject->getId());
}
I hope that will help you!
You can always use the built-in Symfony feature sfForm::embedForm in your parent form in order to save the child one, but I haven't figure out a a way of properly get this working.
One post I came across some time ago actually did provide me with the solution. Have a look and see if it fits your needs. Of course it's in Doctrine but I suppose it can be easily ported in Propel

Many to many relation on same table

I've got a little problem with many to many relations on the same table using Symfony 1.4 with the Propel ORM. My schema.yml looks like this:
propel:
item:
_attributes: { phpName: Item }
id: { phpName: Id, type: INTEGER, size: '10', primaryKey: true, autoIncrement: true, required: true }
type_id: { phpName: TypeId, type: INTEGER, size: '11', required: true, foreignTable: type, foreignReference: id, onDelete: RESTRICT, onUpdate: RESTRICT }
owner_id: { phpName: OwnerId, type: INTEGER, size: '11', required: true, foreignTable: owner, foreignReference: id, onDelete: RESTRICT, onUpdate: RESTRICT }
place_id: { phpName: PlaceId, type: INTEGER, size: '11', required: true, foreignTable: place, foreignReference: id, onDelete: RESTRICT, onUpdate: RESTRICT }
picture_id: { phpName: PictureId, type: INTEGER, size: '11', required: false, foreignTable: picture, foreignReference: id, onDelete: RESTRICT, onUpdate: RESTRICT }
supplier_id: { phpName: SupplierId, type: INTEGER, size: '11', required: false, foreignTable: supplier, foreignReference: id, onDelete: RESTRICT, onUpdate: RESTRICT }
name: { phpName: Name, type: VARCHAR, size: '255', required: true }
amount: { phpName: Amount, type: INTEGER, size: '11', required: true }
wished_amount: { phpName: WishedAmount, type: INTEGER, size: '11', required: true }
costs: { phpName: Costs, type: DECIMAL, size: '7', scale: '2', required: true }
description: { phpName: Description, type: LONGVARCHAR, required: false }
use_until: { phpName: UseUntil, type: DATE, required: false }
last_used: { phpName: LastUsed, type: TIMESTAMP, required: false }
last_updated: { phpName: LastUpdated, type: TIMESTAMP, required: false }
_indexes: { item_FI_1: [type_id], item_FI_2: [owner_id], item_FI_3: [place_id], item_FI_4: [picture_id], item_FI_5: [supplier_id] }
item_has_item:
item_id: { type: INTEGER, required: true, foreignTable: item, foreignAlias: item, foreignReference: id, primaryKey: true}
parent_item_id: { type: INTEGER, required: true, foreignTable: item, foreignAlias: parent_item, foreignReference: id, primaryKey: true}
_indexes: { item_has_item_FKIndex1: [item_id], item_has_item_FKIndex2: [parent_item_id] }
However, the needed admin_double_list in the admin generator does not appear automatically. It does, when the second foreignTable is adjusted to another table, for example sf_guard_user. I've read that this could be fixed in Propel 1.3, but I don't know which Propel version is included with Symfony 1.4.
I hope I gave enough information for my problem to be solved by you guys. Otherwise I will have to make another table which holds the items that have child item, like this:
Item <---- Item_has_item <---- Combined_item
EDIT AFTER REACTION FROM j0k
After the reaction from j0k, I did update the sfPropelORMPlugin, and it did recognise the relation. However, I still got an error while saving (only when a relation is made):
Unable to execute INSERT statement [INSERT INTO `item_has_item` (`ITEM_ID`) VALUES (:p0)]
[wrapped: SQLSTATE[23000]: Integrity constraint violation: 1452 Cannot add or update a
child row: a foreign key constraint fails (`SI/item_has_item`, CONSTRAINT
`item_has_item_FK_1` FOREIGN KEY (`parent_item_id`) REFERENCES `item` (`id`))]
I have found, after a long extensive search, that the form generator fails to correctly understand the many-to-many relation on the same table. In the function saveItemHasItemList($con = null):
$c = new Criteria();
$c->add(ItemHasItemPeer::ITEM_ID, $this->object->getPrimaryKey());
ItemHasItemPeer::doDelete($c, $con);
$values = $this->getValue('item_has_item_list');
if (is_array($values))
{
foreach ($values as $value)
{
$obj = new ItemHasItem();
$obj->setItemId($this->object->getPrimaryKey());
$obj->setItemId($value);
$obj->save();
}
}
As you can see, the ItemId is twice set, while the ParentItemId is not set, hence the constraint value. It should be generated as:
$c->add(ItemHasItemPeer::ITEM_ID, $this->object->getPrimaryKey());
$c->add(ItemHasItemPeer::PARENT_ITEM_ID, $this->object->getPrimaryKey());
&&
$obj->setParentItemId($this->object->getPrimaryKey());
$obj->setItemId($value);
This solves the saving problem, however, it still does not show the already selected relations. To solve that, you should also change the updateDefaultsFromObject():
foreach ($this->object->getItemHasItemsRelatedByItemId() as $obj)
Should be:
foreach ($this->object->getItemHasItemsRelatedByParentItemId() as $obj)
Remember, these functions are located in form/base/BaseItemForm.class.php, but the functions should be overwritten in /form/ItemForm.class.php.
I believe this is an Propel bug, so I will post it tomorrow on the Propel TRAC.
If you are using the last symfony 1.4, it uses Propel 1.4.2.
You should try to use a new version of the Propel Plugin. It uses Propel 1.6.

Checkboxes are not checked

I cannot get DB values of games to be selected:
Game:
actAs:
Timestampable: ~
columns:
id: { type: integer(4), primary: true, autoincrement: true, unsigned: true }
game_name: { type: string(100), notnull: true }
logo: { type: string(100), notnull: true, comment: "Game Logo" }
indexes:
it:
fields: game_name
type: unique
Campaign:
actAs:
Timestampable: ~
columns:
id: { type: integer(4), primary: true, autoincrement: true, unsigned: true }
name: { type: string(100), notnull: true }
percentage: { type: integer(4), notnull: true, unsigned: true }
is_active: { type: integer(1), notnull: true, unsigned: true }
start: { type: datetime, notnull: true }
end: { type: datetime, notnull: true }
CampaignGames:
actAs:
Timestampable: ~
columns:
id: { type: integer(4), primary: true, autoincrement: true, unsigned: true }
campaign_id: { type: integer(4), notnull: true, unsigned: true }
game_id: { type: integer(4), notnull: true, unsigned: true }
indexes:
tc:
fields: [campaign_id, game_id]
type: unique
relations:
Campaign: { onDelete: CASCADE, local: campaign_id, foreign: id, foreignAlias: CampaignCampaignGames }
Game: { onDelete: CASCADE, local: game_id, foreign: id, foreignAlias: GameCampaignGames }
I have added games checkbox here which belongs to Game model to let the user add games to CampaignGames, but unfortunately they never checked... And these values are present in DB.
class AdminconsoleCampaignForm extends CampaignForm
{
public function configure()
{
parent::configure();
$this->widgetSchema['is_active'] = new sfWidgetFormSelectRadio(array(
'choices' => array(1 => 'On', 0 => 'Off'),
));
$games = Doctrine_Core::getTable('Game')->getGames();
$this->widgetSchema['game_id'] = new sfWidgetFormSelectCheckbox(array(
'choices' => $games
));
$this->validatorSchema['game_id'] = new sfValidatorChoice(array(
'choices' => array_keys($games)
, 'multiple' => true
, 'required' => false
));
$this->removeFields();
}
Also tried to use
$this->widgetSchema['game_id']->setDefault(array($data));
No luck. How to resolve it? I'm really stuck on that.
There are two things that caught my attention:
1. You're not using Doctrine's boolean data type
Try changing your schema.yml to the following:
Campaign:
[...]
columns:
[...]
is_active:
type: boolean
notnull: true
default: 0 # Or whichever default value you prefer
[...]
This way Symfony/Doctrine will take care of anything regarding the is_active row of your Campaign record.
If you now rebuild your model your BaseCampaignForm.class.php will define the is_active widget automatically like this:
$this->setWidgets(array(
[...]
'is_active' => new sfWidgetFormInputCheckbox(),
[...]
);
$this->setValidators(array(
[...]
'ist_active' => new sfValidatorBoolean(array('required' => false)),
[...]
);
Note: That required is set to false is there because if a checkbox isn't selected it's not posted either. The sfValidatorBoolean takes care of this and disables the value all by itself. If you would set it to true than the user wouldn't be able to uncheck the box and submit the form without a validator exception.
2. You tried to set form object defaults in its form's widget
In your code you used:
$this->widgetSchema['game_id']->setDefault(array($data));
This won't work because you're using a Form with an object attached to it (a BaseFormDoctrine). All the default values are taken right out of the object assigned to that form (in your case a Campaign object because you're extending CampaignForm).
(Major pitfall) If you want to set default values on a form object you have to set them on the form object itself :
$this->getObject()->setGameId($id);
Default object values can't be set using the object form's widgets. These default values will always be overwritten by the actual object values (which makes sense because the form represents the object).
Glad if I was able to help you in some way.
If your choices are based on doctrine records (which they are), then you should use sfWidgetFormDoctrineChoice. Change the renderer_class option if you want to get radio buttons / checkboxes instead of a select tag.
Your $game array probably has the keys by order (0,1,2,3,4) and you end up having a select like:
<select>
<option value="0">Option 1</option>
<option value="1">Option 2</option>
<option value="2">Option 3</option>
</select>
But your object ids do not coincide with those keys.
You have to change the line:
$games = Doctrine_Core::getTable('Game')->getGames();
to:
$c = Doctrine::getTable('Game');
$c->setAttribute(Doctrine_Core::ATTR_COLL_KEY, 'id');
$games = $c->getGames();
so that the arrays gets the keys = ids. Like:
<select>
<option value="3">Option 1</option>
<option value="7">Option 2</option>
<option value="9">Option 3</option>
</select>

Symfony: How to save data from sfWidgetFormDoctrineChoice with multiple checkboxes

I have problem with save data from choices widget.
Here is part of schema:
Client:
columns:
id:
type: integer
primary: true
autoincrement: true
grupy:
type: array
options:
collate: utf8_unicode_ci
charset: utf8
relations:
Grupy:
type: many
local: grupy
foreign: id
class: KlientGrupy
KlientGrupy:
options:
collate: utf8_unicode_ci
charset: utf8
columns:
id:
type: integer
primary: true
autoincrement: true
item:
type: string(255)
relations:
Klienci:
type: many
local: id
foreign: grupy
ClientForm class:
class ClientForm extends BaseClientForm
{
public function configure()
{
$this->widgetSchema['grupy']->setOption('multiple', true);
$this->widgetSchema['grupy']->setOption('expanded', true);
$this->widgetSchema['grupy']->setOption('add_empty', false);
$this->widgetSchema['grupy']->setAttribute('class', 'checkBoxLabel');
}
}
BaseClientForm class:
$this->setWidgets(array(
'id' => new sfWidgetFormInputHidden(),
'grupy' => new sfWidgetFormDoctrineChoice(array('model' => $this->getRelatedModelName('Grupy'), 'add_empty' => true)),
));
When i save with one checkbox then all is ok, but when i try do it for more than one i get that problem:
SQLSTATE[HY093]: Invalid parameter number: number of bound variables does not match number of tokens
You can find answer in comment in my question

symfony - embeddedForm - multiple checkboxes

I have article admin module and a tag module
Tags are simply a single tag per row item
What I'd like to do is to embed the list of all the tags (as checkboxes) into my article module
Could I do this with embedded forms?
EDIT:
This is my schema:
article:
id: ~
title: { type: VARCHAR, size: '255', required: true }
tags: { type: VARCHAR, size: '500' }
created_at: { type: TIMESTAMP, required: true }
updated_at: { type: TIMESTAMP, required: true }
tag:
id: ~
tag: { type: VARCHAR, size: '500', required: true }
ord_id: { type: INTEGER, required: true }
created_at: ~
updated_at: ~
item_tag:
id: ~
item_id: { type: INTEGER, required: true, foreignTable: item, foreignReference: id, onDelete: cascade }
tag_id: { type: INTEGER, required: true, foreignTable: tag, foreignReference: id, onDelete: restrict }
created_at: ~
item:
id: ~
article_id: { type: INTEGER, foreignTable: article, foreignReference: id, onDelete: cascade }
So when I need the tags to be displayed and will update the above tables
If you have defined the relationship between the article and tags correctly in your model, then the generated Forms should contain tag select widgets.
Search "sfWidgetFormChoice" in the Forms documentation for more information:
http://www.symfony-project.org/jobeet/1_4/Doctrine/en/10
Note: The examples are created using the Doctrine ORM but everything should work the same way with Propel as well.

Resources