Note: These examples are using Zend Framework 1.9.5, with comes with Zend Server Community Edition 4.0.6 for OS-X
As I begin working on the admin area for a work application being built with Zend Framework, I've been spending a lot of time starting and stopping on it. Why? Chalk it up to the extensibility of Zend Framework, who have clearly taken the "configuration" side of the "configuration vs. convention" philosophy when it comes to frameworks. Before you get all worked up, remember that (a) I last used Zend Framework 3 years ago and (b) I got very used to relying on framework conventions to help me speed things up, a tactic used with great success in building CakePHP and CodeIgniter applications. Alas, those days are behind me and I am getting schooled in the wonders of "figuring everything out" and "object chaining is cool" and "why are there always more than one way to do something with this framework?!?".
So one of my first tasks is to create a form to allow people to create new leagues for a fantasy sports league. Need a very simple form with three things in it: league name, sport and scoring period (which is how often we need to compile standings). Sounds easy right?
After searching around online for some examples of building simple forms, I was dismayed to discover there were two different ways of building the form. I could (a) do it the long way and create specific instances of the form elements using Zend_Form_Element_X or (b) do it the short way and add them to the form by use of Zend_Form::addElement() and pass it the type of form element I want via an array. For reasons I cannot explain initially, I decided to do things the long way. Later on, I found out that doing it this way saved me from rewriting.
So, the approach I decided to use was to create a separate file that contains the form I want to use. Why? Because I wanted to reuse this form for both "add a new league" functionality and "edit an existing league" functionality. After figuring out that I needed to create a Bootstrap file for the module that the form was in (otherwise auto loading wouldn't work) here's what I cranked out:
-
class Admin_Form_League extends Zend_Form
-
{
-
public function __construct($options = null)
-
{
-
parent::__construct($options);
-
$this->setAction($options['action'])->setMethod('post');
-
-
$sport = new Zend_Form_Element_Select(
-
'sport',
-
'required' => true,
-
)
-
);
-
$scoringPeriod = new Zend_Form_Element_Select(
-
'scoring_period',
-
'required' => true,
-
)
-
);
-
-
$this->addElement($leagueId);
-
}
-
-
}
-
}
Perhaps you've noticed something different about this example compared to others you might have seen elsewhere? Okay, I won't keep you in suspense about it. I'm using the $options parameter when creating an instantiation of this particular form to pass some dynamic information. Without this, it would be very difficult to reuse this form. In this case, I pass in up to three different parameters: the target for the form, the label I want on the submit button and (in the case where we're editing an existing record) the primary key of that league in the database.
So, then the next step is to tell my controller I want to use that form. Ignore the database related stuff as I'm using Doctrine and my use of that probably deserves it's own blog post down the line.
-
class Admin_IndexController extends Zend_Controller_Action
-
{
-
protected $_flashMessenger = null;
-
-
public function init()
-
{
-
$this->_flashMessenger = $this->_helper->getHelper('FlashMessenger');
-
$this->initView();
-
}
-
-
public function indexAction()
-
{
-
$leagues = Doctrine::getTable('Leagues');
-
'action' => '/admin/index',
-
'submitLabel' => 'Add New League'
-
));
-
-
if ($this->_request->getPost()) {
-
$formData = $this->_request->getPost();
-
-
if ($form->isValid($formData)) {
-
$newLeague = new Leagues();
-
$newLeague->fromArray($form->getValues(true));
-
$newLeague->save();
-
} else {
-
$form->populate($formData);
-
}
-
}
-
-
$this->view->leagues = $leagues->findAll()->toArray();
-
$this->view->messages = $this->_flashMessenger->getMessages();
-
$this->view->form = $form;
-
}
-
-
}
Nice and simple, and effective. I also have another action that is for the editing or deleting an existing league. Here's the controller for that
-
class Admin_LeaguesController extends Zend_Controller_Action
-
{
-
public function init()
-
{
-
$this->_flashMessenger = $this->_helper->getHelper('FlashMessenger');
-
$this->initView();
-
}
-
-
public function editAction()
-
{
-
$leagueId = $this->_getParam('id', 0);
-
$leagues = Doctrine::getTable('Leagues');
-
-
if ($this->_request->getPost()) {
-
$formData = $this->_request->getPost();
-
'action' => '/admin/leagues/edit',
-
'submitLabel' => 'Update League',
-
'leagueId' => $formData['id']
-
));
-
-
if ($form->isValid($formData)) {
-
$updateLeague = $leagues->find($formData['id']);
-
$updateLeague->name = $formData['name'];
-
$updateLeague->sport = $formData['sport'];
-
$updateLeague->scoring_period = $formData['scoring_period'];
-
$updateLeague->save();
-
$this->_flashMessenger->addMessage('Updated league');
-
$this->_redirect('/admin');
-
} else {
-
$form->populate($formData);
-
}
-
} else {
-
$leagueId = $this->_getParam('id', 0);
-
-
if ($leagueId == 0) {
-
$this->_redirect('/admin');
-
} else {
-
'action' => '/admin/leagues/edit',
-
'submitLabel' => 'Update League',
-
'leagueId' => $leagueId
-
));
-
}
-
}
-
-
$results = $leagues->findById($leagueId)->toArray();
-
'name' => $results[0]['name'],
-
'sport' => $results[0]['sport'],
-
'scoring_period' => $results[0]['scoring_period']
-
);
-
$form->populate($formData);
-
$this->view->form = $form;
-
}
-
-
public function deleteAction()
-
{
-
$leagueId = $this->_getParam('id', 0);
-
-
if ($leagueId == 0) {
-
$this->_redirect('/admin');
-
}
-
-
$leagues = Doctrine::getTable('Leagues');
-
$deleteLeague = $leagues->find($leagueId);
-
-
if ($deleteLeague->delete()) {
-
$deleteMessage = 'Deleted league';
-
} else {
-
$deleteMessage = 'Unable to delete league';
-
}
-
-
$this->_flashMessenger->addMessage($deleteMessage);
-
$this->_redirect('/admin');
-
}
-
}
Nothing too earth-shattering I would think. Armed with this, I can now display my form in two different places:
-
<h1>Admin home</h1>
-
<h2>Leagues</h2>
-
<ul>
-
<?php foreach ($this->messages as $message) : ?>
-
<li><?php echo $message ?></li>
-
<?php endforeach; ?>
-
</ul>
-
<?php endif; ?>
-
<table border=1>
-
<tr>
-
<th>League Name</th>
-
<th>Sport</th>
-
<th>Scoring Period</th>
-
</tr>
-
<?php foreach ($this->leagues as $league) : ?>
-
<tr>
-
<td><a href="/admin/leagues/edit/id/<?php echo $league['id'] ?>">Edit</a> <a href="/admin/leagues/delete/id/<?php echo $league['id'] ?>" onClick="return confirm('Are you sure you want to delete this league?');">Delete</a></td>
-
</tr>
-
<?php endforeach; ?>
-
<tr>
-
</tr>
-
</table>
-
<h1>Edit league</h1>
-
</p><p>Use the form below to edit an existing league</p>
-
<table border=1>
-
<tr>
-
<th>League Name</th>
-
<th>Sport</th>
-
<th>Scoring Period</th>
-
</tr>
-
<tr>
-
</tr>
-
</table>
So I do that, and load up the form and...what the? Damn, the default decorator for these elements is ugly. I want my form fields to line up nicely inside my table (shut up, I know I should use CSS but this is an admin panel and I am in a hurry) so after finding this article by Zend Framework architect MWOP I figured out how to create the exact decorator I wanted. Here's the final version of my reusable form.
-
class Admin_Form_League extends Zend_Form
-
{
-
public function __construct($options = null)
-
{
-
parent::__construct($options);
-
$this->setAction($options['action'])->setMethod('post');
-
'ViewHelper',
-
'Errors',
-
);
-
-
$name->setDecorators($decoratorOptions);
-
-
$sport = new Zend_Form_Element_Select(
-
'sport',
-
'required' => true,
-
)
-
);
-
$sport->setDecorators($decoratorOptions);
-
-
$scoringPeriod = new Zend_Form_Element_Select(
-
'scoring_period',
-
'required' => true,
-
)
-
);
-
$scoringPeriod->setDecorators($decoratorOptions);
-
$submit->setDecorators($decoratorOptions);
-
-
$this->addElement($leagueId);
-
}
-
-
}
-
}
I'm pretty sure that if I dig around some more, I can find info on how to automatically apply that decorator to ALL my form elements if I needed that. Maybe in the next revision. Hope you find this example helps you to create your own reusable forms and to tweak the decorators yourself.
Tags: decorators, reusable forms, Zend Framework, Zend_Form

[...] Hartjes has a new post to his blog today looking at how to make forms in a Zend Framework application in a bit more [...]
[...] Hartjes has a new post to his blog today looking at how to make forms in a Zend Framework application in a bit more [...]
Creating Usable Forms With Zend Framework...
As I begin working on the admin area for a work application being built with Zend Framework, I've been spending a lot of time starting and stopping on it. Why? Chalk it up to the extensibility of Zend Framework, who have clearly taken the "configurat...
Creating Usable Forms With Zend Framework...
Chris Hartjes writes a very nice article about how to make usable zend_forms; After searching around online for some examples of building simple forms, I was dismayed to discover there were two different ways of building the form. I could (a...
my favorite part for sure of the zend framework has to be zend form. while its not always the best choice for some forms - like those one off forms that require a special layout etc.- the reusability of the forms make them awesome. I find myself using removeElement alot too, if there only needs to be one thing different about the form for it to be used in another part of the application, just modify that one form and boom, done much faster.
the decorators are still the voodoo magic part, and I have had alot of frustration with them to get them to work the way I want. it would be so much nicer to be able to pass a small html template with placeholders instead of trying to figure out the decorator chain.
like form->my_element->setTemplate('{label}{element}');
sometimes design patterns are too fancy for their own good.
sorry it stripped out the html in my above example, it just had tr/td elements in it.
You can use $this->setElementDecorators($decorators, $elements, $exclusionsFlag) inside the form class to set one style for all elements. The second and third parameter help to set exceptions, say for buttons, etc.
As a side note rather than have an exit after a redirect do this:
return $this->_redirect('/admin');
Once you come to do some unit testing etc, it's going to save you some hassle.
Back to forms, if you have more than one or two in your application it is worth creating your own class that extends Zend_Form, and using this for you individual form classes. You will find a lot of things repeating that can be nicely abstracted across several forms.
It's probably not the best idea to override __construct in Zend_Form, since you're calling parent::__construct anyway. A better approach would be to override Zend_Form::init() - this one gets called in constructor and, according to ZF docs, should be used for extending classes. And yes, this just looks cleaner.
Besides, you don't really need to deal with this $options array. Cause whichever array you pass to the form constructor, it gets immediately accessible in form through $this->_attribs.
So, in your case you should be able to use $this->_attribs['action'] instead of $options['action'].
There is actually another option for adding elements: Zend_Form::addElements() using a single array. It's just not very well documented; you have to dig in the API docs and source code to find it. I actually like that approach because adding a new element only requires adding a new array entry and keeps code to create and use the form fairly minimal.
Also, just FYI, there's an init() stub in Zend_Form that you can override for initialization code. This alleviates the need to override the constructor and explicitly call the parent constructor. Makes code a bit cleaner and removes the possibility of forgetting to include the parent constructor call causing issues. To handle your custom leagueId option, I probably would suggest overriding setOptions(), handle checking for it and applying the related form change there, and then calling parent::setOptions($options). That way, no matter where the option is set from ($options to the constructor or a subsequent setOptions() call), the intended effect still occurs.
With regards to adding decorators to all form elements, I don't think there's a way to do this aside from looping over all elements and calling addDecorator() on each one. There's a ticket in JIRA for a similar feature for applying decorators, filters, and validators if you'd like to comment or vote on it: http://framework.zend.com/issues/browse/ZF-3945
@Jo - Yeah, I searched around for some "Zend Framework best practices" and found the return after redirect trick.
@Vika - I'll switch up to use init(), just going based on some existing samples but always willing to learn the proper way to do things.
@Matthew Thanks for the tips as well
[...] Versionskontrolle mit GIT LackRack – Eth0Wiki Ein Server-Rack kostengünstig produziert @TheKeyboard » Blog Archive » Creating Usable Forms With Zend Framework Wie man Zend_Form Formulare ein bisschen mehr wiederbenutzbarer gestalten. Ruby-like iterators in [...]
Great tutorial, thank you!
Small note: $form->isValid($formData) already populates form with data, so $form->populate() in your code is redundant.
Hi Charles,
Great article!
To apply decorator to ALL form elements, i recommend use Plugin Loaders.
Create your own plugin, and add it to the form or form class.
$form->addElementPrefixPath('My_Decorator', 'My/Decorator/', 'decorator');
See more in http://framework.zend.com/manual/en/zend.form.elements.html#zend.form.elements.loaders
Thanks!
A little trick. To reduce repetition, you can create your own Action like this:
abstract class My_Controller_Action extends Zend_Controller_Action
{
}
public function init()
{
$this->_flashMessenger = $this->_helper->getHelper('FlashMessenger');
$this->initView();
}
THEN all you have to do is:
class Admin_LeaguesController extends My_Controller_Action
and you'll have the FlashMessenger automatically working in all controllers that extend My_Controller_Action.
I also do my ACL checks in My_Controller_Action's init()