Written April 19, 2008 in webdev, zend framework

I’ve been doing a lot of work with Zend Framework the past few days. One of the things that’s been bugging me is how much code I have in my controllers to save data. The validation and filtering is handled by the Zend_Form object, but the forms don’t always match the models as far as fieldnames and other information, so I don’t just want to throw a form result set at a Zend_Db_Table_Row and let it figure things out.

Result: I taught my forms to know how to fill and save themselves. They use the model layer to accomplish this, but work mostly by extending the normal Zend_Form with a ‘populate’ and a ’save’ method. The populate method can be static. It takes a primary key and returns an array that can be used to populate the form. The save method is not static, and depends on the form already being populated and having been validated.

As a quick disclaimer, I by no means would say that this is the best practice for this kind of thing … and this code is quickly hacked up edited code that I put together from what I’m really doing. No guarantees that it works … however, the theory works for me!

OK, a few housekeeping things first. I’m using the typical ZF modular directory structure, but there’s only one module. My directory structure looks like this:

approot/
  /app
    /controllers
    /config
    /forms
    /models
    /views
  /html
     index.php
  /lib
    /Zend

In the application bootstrap, index.php, I added the /models and /forms folders to the include path. This allows me to autoload them just based on the name of the object.

A good example of a form would be as follows:

class EditForm extends Zend_Form {
  function __construct($options = null) {
    parent::__construct($options);

    $this->setAction('/blog/edit');
    $this->setAttrib('id','blogedit');
    $this->setMethod('post');

    $id = new Zend_Form_Element_Hidden('id');
    $this->addElement($id);

    $url = new Zend_Form_Element_Text('url');
    $url->setLabel('URL of Blog Homepage');
    $url->setRequired(true);
    $this->addElement($url);

    $feedurl = new Zend_Form_Element_Text('feedurl');
    $feedurl->setLabel('URL of Specific RSS Feed');
    $feedurl->setRequired(true);
    $this->addElement($feedurl);

    $submit = new Zend_Form_Element_Submit('submit');
    $submit->setLabel('Submit');
    $this->addElement($submit);
  }

  public function fill($id) {
    if(is_array($id)) {
      throw new Zend_Exception('ID must be a single integer, not an array.');
      return false;
    }
    $blogindex = new Blogindex();
    $rowset = $blogindex->find($id);
    if(count($rowset) < 1) {
        throw new Zend_Exception('No Blogindex record found.');
        return false;
    } else {
      $bi = $rowset->current();
      $stack = array('id' => $bi->id,
                    'url' => $bi->url,
                    'feedurl' => $bi->feedurl,
                    'check_frequency' => $bi->check_frequency);
    }
    return $stack;
  }
}

  public function save() {
    $id = $this->getValue('id');
    if(empty($id)) {
      throw new Zend_Exception('No ID currently in form... populate it first.');
      return false;
    } else {
      $blogindex = new Blogindex();
      $rowset = $blogindex->find($this->getValue('id'));
      $bi = $rowset->current();
      $bi->url = $this->getValue('url');
      $bi->feedurl = $this->getValue('feedurl');
      $bi->save();
      return true;
    }
  }

So to use the form, you’d just call the fill and save actions.

    function editAction() {
      // TODO Add in ACL based on ID.
      $this->initView();
      $this->view->form = new Zfblogs_Form_Blogedit();
      $params = $this->_getAllParams();
      if(!empty($params['submit']) && $params['submit'] == 'Submit') {
        if($this->view->form->isValid($params)) {
          $this->view->form->save();
          $this->_redirect('/user/index');
        } else {
          $this->view->message = 'Some errors were encountered. Please fix them
and hit Submit again.';
        }
      } elseif(!empty($params['id'])) {
        $this->view->form->populate($this->view->form->fill($params['id']));
      }
    }

Comments? Suggestions on ways to make it even easier?


Related:

4 comments on ' Zend Framework: Should Forms Save Themselves? '

  1. Great idea. I do something similar but in reverse: rather than having a form that can populate itself from the model I have a model that can populate itself from the form *data*. This made more send to me since in most cases I run into the Model is being populated and not the Form.

    The one problem I see with your code however is the mixing of storage (database) code and you form code. This isn’t going to be a very scalable solution for Forms that do not relate to your database storage.

  2. The work you’ve done here is very similar to an idea I had as I was designing Zend_Form in the beginning; the key difference was my thought was to create a model class that encapsulated both the form *and* the data storage object (as not every model uses a database backend).

    Nice work!

  3. I do this sort of thing but I use an ORM layer so I have independant model classes, I wouldn’t advise mixing model classes in with the form in case you want act on them without the form - like have the system create a default user - or you want to have a number of different types of forms that affect the same model class (which is just like having different views on the same data in MVC).

    I extend Zend_Form and override the constructor to allow a model class to be passed in (e.g. User), this sets the forms initial values. I also create a getter (getUser()) and override isValid to set posted data back in to the model class. I don’t do the actual saving in the form as the controller might want to do some checks or set some additional fields, I just get the controller to call the getter and then it has an object with all the data set in from the form.

Trackbacks/Pingbacks

Leave a comment

name (req'd)

email (req'd)

website