Tuesday, September 20, 2011

Symfony Forms Framework: Merge 2 forms

Recently I had to create a form to create/update users in our system. Some time ago we decided to save are users in 2 tables. The first table would contain all login information and the second his personal information. This is a simple example of the DB design:

User db design

User db design

I would not recommend doing this for so little fields. But in our system we have a lot more fields, and it helps us to optimize our queries.

Wouldn't it be better if we could merge the 2 forms? The answer is yes. And it's pretty easy to do so in Symfony … too bad that it's not documented on the Symfony website.

When you generate the forms with Symfony, you'll get a UserForm and a UserInfoForm.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* USER FORM.
*
* @PACKAGE    FORM
*/
class UserForm extends BaseUserForm {
 
public function configure() {
 
}
 
}
 
/**
* USERINFO FORM.
*
* @PACKAGE    FORM
*/
class UserInfoForm extends BaseUserInfoForm {
 
public function configure() {
 
}
 
}

The idea is to display one form to insert/update those 2 tables. If you are familiar to creating forms with Symfony you know that you could achieve it by instantiating the 2 forms in an action and send them to the templates:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/**
* ACTION CLASS
*/
class userActions extends sfActions
{
public function executeCreate($request) {
$this->userForm = new UserForm();
$this->userInfoForm = new UserInfoForm();
$this->setTemplate('edit');
}
 
public function executeEdit($request) {
$this->userForm = new UserForm(UserPeer::retrieveByPK($request->getParameter('id')));
$this->userInfoForm = new UserInfoForm(UserInfoPeer::retrieveByPK($request->getParameter('id')));
}
 
public function executeUpdate($request) {
$this->userForm = new UserForm(UserPeer::retrieveByPK($request->getParameter('id')));
$this->userInfoForm = new UserInfoForm(UserInfoPeer::retrieveByPK($request->getParameter('id')));
 
$this->userForm->bind($request->getParameter($this->userForm->getName()));
$this->userInfoForm->bind($request->getParameter($this->userInfoForm->getName()));
 
// ETC ...
}
}
 
// TEMPLATE CODE EDITSUCCESS.PHP
<?php $user = $userForm->getObject(); ?>
 
<form action="<?php echo url_for('user/update'.(!$user->isNew() ? '?id='.$user->getId() : '')) ?>" method="post">
<table>
<tfoot>
<tr>
<td colspan="2">
<input type="submit" value="Save" />
</td>
</tr>
</tfoot>
<tbody>
<?php echo $userForm ?>
<?php echo $userInfoForm ?>
</tbody>
</table>
</form>

This is really annoying to work this way. You always have to instance 2 forms in the action and the templates. Wouldn't it be better if we could merge the 2 forms? The answer is yes. And it's pretty easy to do so in Symfony … too bad that it's not documented on the Symfony website.

Here is how you do it:

1. UserForm.class.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/**
* USER FORM.
*
* @PACKAGE    FORM
*/
class UserForm extends BaseUserForm {
 
public function configure() {
$this->mergeForm(new UserInfoForm(UserInfoPeer::retrieveByPK($this->getObject()->getId())));
}
 
/**
* OVERRIDE THE SAVE METHOD TO SAVE THE MERGED USER INFO FORM.
*/
public function save($con = null) {
parent::save();
 
$this->updateUserInfo();
 
return $this->object;
}
 
/**
* UPDATES THE USER INFO MERGED FORM.
*/
protected function updateUserInfo() {
// UPDATE USER INFO
if (!is_null($userInfo = $this->getUserInfo())) {
 
$values = $this->getValues();
if ( $userInfo->isNew() ) {
$values['user_id'] = $this->object->getId();
}
 
$userInfo->fromArray($values, BasePeer::TYPE_FIELDNAME);
 
$userInfo->save();
}
}
 
/**
* RETURNS THE USER INFO OBJECT. IF IT DOES
* NOT EXIST RETURN A NEW INSTANCE.
*
* @RETURN USERINFO
*/
protected function getUserInfo() {
 
if (!$this->object->getUserInfo()) {
return new UserInfo();
}
 
return $this->object->getUserInfo();
}
}

2. actions.class.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public function executeCreate($request) {
$this->form = new UserForm();
$this->setTemplate('edit');
}
 
public function executeEdit($request) {
$this->form = new UserForm(UserPeer::retrieveByPK($request->getParameter('id')));