Creating simple, extendible CRUD, using Zend Framework


The Form

Creating a nice, easy to maintain form, starts with a form class. Creating your forms procedurally in your controller/actions is horrid. please don’t do it.

To start with creating your form classes, you need your own namespace in your library. If you don’t have this, register one. This can be done by adding an _initAutoloading method to your Bootstrap. below is a short example. its not comprehensive (you can also do this in your ini i believe, but I use php configuration files similar to DASPRiD‘s, and i’m not trying to show how to set up autoloading here.)

class Bootstrap extends Zend_Application_Bootstrap
{
    //...
    /**
    * Initilise autoloader and library namespaces
    */
    public function _initAutoloading()
    {
        $loader = Zend_Loader_Autoloader::getInstance();
        $loader->registerNamespace('My_');
    }
    //...
}

Your next task, create a form. This is pretty simple. I will demonstrate using just a few fields. Create a file in your library called My/Form.php

class My_Form extends Zend_Form
{
    /**
    * Set up form fields, filtering and validation
    */
    public function init()
    {
        $this->setMethod(Zend_Form::METHOD_POST);
        //Username
        $this->addElement($uname = new Zend_Form_Element_Text('username'));
        $uname->setLabel('Username')
              ->addValidator('Db_NoRecordExists', false, array('table' => 'users',
                                                               'field' => 'username'))
              ->addValidator('Alnum', false, array('allowWhiteSpace' => true));
        //Email
        $this->addElement($email = new Zend_Form_Element_Text('email'));
        $email->setLabel('Email')
              ->addValidator('Db_NoRecordExists', false, array('table' => 'users',
                                                               'field' => 'email'))
              ->addValidator('EmailAddress', false);
        //First name
        $this->addElement($firstname = new Zend_Form_Element_Text('firstname'));
        $firstname->setLabel('First name');
        //Last name
        $this->addElement($lastname = new Zend_Form_Element_Text('lastname'));
        $lastname->setLabel('Last name');
    }
}

Now that your basic form is created, you need to add a method to perform your CRUD. I’m not actually going to cover the D (delete) as thats realy simple, and doesn’t require the form, which is the focus of this post.

This method should take an array for the post data, and a Zend_Db_Table_Row to provide the save functionality. In this example the DB columns have the same names as the form fields, this means we can set values with less code. As we are using Zend_Db, there should be no injection problems with this method, as everything is automagically quoted.

    //...
    public function process (array $post, Zend_Db_Table_Row  $row)
    {
        $this->setDefaults($row->toArray());
        // If the id (primary key) is null then this is a new row, else it is an existing record
        if (null !== $row->id) {
            // Record already exists, exclude it from db record validation.
            $this->getElement('username')
            ->addValidator('Db_NoRecordExists',
                                 false,
                                 array('table'    => 'users',
                                          'field'     => 'username',
                                          'exclude' => array ('field' => 'id',
                                                                      'value' => $row->id)));
            $this->getElement('email')
            ->addValidator('Db_NoRecordExists',
                                 false,
                                 array('table'    => 'users',
                                         'field'     => 'email',
                                         'exclude' => array ('field' => 'id',
                                                                    'value' => $row->id)));
        }
        if (sizeof($post) && $this->isValid($post)) {
            try {
                $row->setFromArray($this->getValues());
                $row->save();
                return true;
            } catch (Exception $e) {
                $this->addDescription('There was an error saving your details');
                return $this;
            }
        }
        return $this;
    }

The Controller / Action

Now that we have created our nice form (which is capable of CR and U)  now we need to use it from within out controller and model to perform the update or insert and interact with the user.

For this, you need 3 actions in your controller, Create, update, and delete (the delete I will not cover for the before mentioned reasons).

class UserController extends Zend_Controller_Action
{
 
    public function newAction ()
    {
        $this->_helper->ViewRenderer->setScriptAction('userform');
        $users = My_Users();
        $user = $users->getNewUserForm($this->getRequest()->getPost());
        if (true === $user) {
            $this->_helper->flashMessenger()->addMessage('New User Created');
            $this->_helper->redirector->gotoUrlAndExit(/** confirmation url here **/);
        }
        $this->view->form = $user;
    }
 
    public function editAction()
    {
        $this->_helper->ViewRenderer->setScriptAction('userform');
        if (false === ($id = $this->_getParam('id', false))) {
            throw new Exception ('Tampered URI');
        }
        $users = My_Users();
        $user = $users->getEditUserForm($this->getRequest()->getPost());
        if (true === $user) {
            $this->_helper->flashMessenger()->addMessage('Details Saved');
            $this->_helper->redirector->gotoUrlAndExit('user/edit/' . $id);
        }
        $this->view->form = $user;
    }
}

To explain what all this is about, Both actions do pretty similar things (infact this can be consolidated into a single action, but for clarity and ease of replication, I am using two).

There are two things which are pretty important in this, one is the use of the redirector, and the other is the checking of the ID before it is used. In my opinion when an id is passed in a url which is invalid, then a 404 should be raised. So in my error controllers i look for a variety of exception types so that i can debug, but only output a 404 for these in production.

Now looking more closely at the action code, we have calls to getxxxxUserForm() on our models. The return value of this is what we wish to inspect, as you could see in the process method we created earlier, we return boolean true, only when the record saves correctly.  So we do a strict check for this boolean value, and if it is true, we know that we can safely redirect our user. The redirect is an important step, it stops the browser trying to post the data again if the user clicks refresh after their record is created / updated. And the final note is that the flashMessenger is used to pass a message to inform that user that their action was completed on.

Also worth noting is that i have set both actions to use the same action name in the viewRenderer. This allows you to consolidate this common script into one. (repeat after me, Dont Repeat Yourself!). The view script for this is pretty simple.

<div class="messages">
<?php foreach(Zend_Controller_Action_HelperBroker::getStaticHelper('flashMessenger')
             ->getMessages() as $message) : ?>
<p><?php echo $this->escape($message);?></p>
<?php endforeach;
</div>
<?php echo $this->form;?>

The Model

In the model, we now need two simple methods to tie the lot together. Now some people might (read: Will) argue that the model should be performing validation, I would argue that with this method, the Model *is* performing the validation, you are simply making use of a library class to perform this function, much in the same way you use Zend_Db_Row. Feel free to flame about this below, but I’m sticking with this, and it provides clean easy seperation, and provides a good clean mechanism to provide user feedback.

class My_Users
{
    /**
    * @var Zend_Db_Table_Abstract
    */
    protected $_table;
    //....
 
    /**
    * Retrieves a new user record, and processes a form against it.
    *
    * @param array $post
    * @return boolean|Zend_Form Boolean true if a save successfully occurs, a populated form on all other conditions
    */
    public function getNewUserForm(array $post)
    {
        $form = new My_Form();
        return $form->process($post, $this->_table->createRow());
    }
 
    /**
    * Retrieves a user record, and processes a form against it.
    *
    * @param array $post
    * @return boolean|Zend_Form Boolean true if a save successfully occurs, a populated form on all other conditions
    */
    public function getEditUserForm(array $post, $id)
    {
        $row = $this->_table->fetchRow($table->select()->where('id = ?', $id));
        $form = new My_Form();
        return $form->process($post, $row);
    }
}

Well, this really doesn’t need much explanation. this simply provides glue between the parts above. You can make these a little more complex, and infact you probably should (I do!) by sanitizing your input from the controller in the form of the user id, $id, in the edit method.

In conclusion

This post has partly been inspired by comments in #zftalk and in part, by the search for a good way to deal with forms, and I look forward to any feedback. Before there is any cirisitm of the validation and filtering in the form, I have kept is minimal to illustrate how the form element deals with two validators of the same name (by overwriting the first instance) and how that can be leveraged to your advantage, and also to keep the code minimal to outline the actual process, and not how to add validators. for that go and RTFM! ;)

  1. #1 by Saeven on June 16, 2009 - 3:13 pm

    Great post – thanks a bunch for this; it really becomes an eye opener to what can be achieved when fully vested into ZF.

    On that note (am repeating a question in #zftalk here), it would have <> been more intuitive to make a convenience method on the Form return a User object on process, instead of ‘vesting’ the User object into a function that ties it to the existence of a Form component. I’d be curious if you could argue the benefits of your approach vs. what I might have done had I not stumbled on your blog.

    Thanks a bunch.
    Alex

  2. #2 by TheAshMan on June 17, 2009 - 9:11 am

    This is a brilliant idea. as of late ive been messing around with the form in my controller and it has led to messy code. Ive been trying to think of ways around this and then i’ve just read this.

    thanks Bittarman
    TheAshMan

  3. #3 by Alexander Ilyin on June 18, 2009 - 2:50 pm

    Have you seen Ta-da Lists by 37Signals? Their application is almost identical to the logic.

  4. #4 by Alexander Ilyin on June 18, 2009 - 5:22 pm

    Sorry, miss chat.

  5. #5 by Alexander Ilyin on June 18, 2009 - 5:54 pm

    Why not sue Zend_Application_Module_Autoloader for loading Forms classes and Models?

  6. #6 by Ryan on June 18, 2009 - 8:16 pm

    I don’t use module auto-loading for a fairly simple reason, simplicity. Why add that extra layer when its really not needed. There are further reasons beyond this, such as the fact that with this user form for example i might extend it further or simply re-use it in the same way in my admin interface as I do in the public facing site.

    I have not seen the 37-Signals app, I am intrigued now, and will go and check it out, with a name like theirs tough, I am flattered. Thank you for the comments.

  7. #7 by Housni on June 25, 2009 - 10:57 pm

    Very nice demonstration :)
    I’m new to ZF and this brings out its potential.

    However, I must point out you have a small typo in your form class:
    $uname->setLabel(‘Email’)
    should be:
    $email->setLabel(‘Email’)

  8. #8 by DB Thapa on July 5, 2009 - 10:18 am

    very nice

  9. #9 by Hari K T on July 22, 2009 - 12:46 am

    Yes pretty cool …

  10. #10 by FR on August 2, 2009 - 7:06 am

    Any ideas of where one can LEARN ZF?…so much of this is so advanced!

  11. #11 by Anonymous on August 15, 2009 - 6:43 am

    TheAshMan :
    This is a brilliant idea. as of late ive been messing around with the form in my controller and it has led to messy code. Ive been trying to think of ways around this and then i’ve just read this.
    thanks Bittarman
    TheAshMan

  12. #12 by me on September 22, 2009 - 10:25 pm

    Good tutorial,

    but why you do it in process() function instead of the controller action methods?

    In the “Edit Case” you have to exclude the 2 elements inside the action method, and form validating inside the controller.

  13. #13 by admin on September 23, 2009 - 9:36 am

    @me:

    I do it in a process method of the form, to keep the form level interaction away from the model, and the controller.

    As you can see from my controller actions, they still have a fair chunk of code in them, and it is only the things which *should* be the responsibility of the controller. That is passing the data about, delegating to the model, and deciding the appropriate method to use for the response (redirect of render the view).

    I recently became aware of a post by Matthew Weierophinney where he deals with this problem in a similar way except he deals with the processing in the model, which I don’t like so much, but the idea is pretty much the same.

  14. #14 by Anonymous on December 10, 2009 - 11:55 pm

    Just wondering what post from Matthew Weierophinney you were talking about. Do you happen to have a link?

  15. #15 by Bobby on January 6, 2010 - 7:40 pm

    I believe the post by Matthew Weierophinney that he is talking about is:
    http://weierophinney.net/matthew/archives/200-Using-Zend_Form-in-Your-Models.html

  16. #16 by Anonymous on January 28, 2010 - 5:55 am

    your comment form doesn’t have any validation?…. Its strange that great brains like you simply miss these things!!!

  17. #17 by Ryan Mauger on January 28, 2010 - 8:45 pm

    Hey, don’t blame me, blame wordpress.
    Though really, I don’t care if you post with a fake or nonsense name, or put up an invalid link or email address, so validation would be a little pointless.
    I would go and do a bit of hacking, but I’d end up rewriting the whole system, as I really wouldnt be happy working with the wordpress codebase. So until I have time to finish my own blog software, this will do.

  18. #18 by Dilo on February 10, 2010 - 5:25 pm

    great post Ryan! I’m also new to zf so am reading posts trying to catch it ;) . Would like to know (if possible) your feedback about integrating your proposal with Doctrine and any clue on how to do it. Thanks in advance!

  19. #19 by saurabh on July 2, 2010 - 1:39 pm

    Wow, simply superb…

    I am a big fan of Matthew, his comments/posts are highly knowledgable. But i must confess your method beats Matthew’s all ends up.

    Trulay amazing. A perfect example of CRUD using Zend Framewokrk. Must be made a part of ZF Manutal.

  20. #20 by Riki Risnandar on August 19, 2010 - 5:55 am

    nice tutorial, thanks. i hope its more simpler to create crud in zend framework

  21. #21 by Patrick B on August 30, 2010 - 11:02 pm

    I’m not sure why creating a form procedurally in the controller is “horrid.” I could take this statement a number of ways, and would like to know exactly what you have in mind.

    I would agree that constructing a form by adding all the elements and validators in the controller is misplaced, but I’m not sure why calling $form = new My_Form() in the controller is terrible.

    Putting the form’s processing logic in the form class, in a “process()”, method is also a nice idea. That way it will be there for any client.

    But having the model know about the form seems convoluted at best. Models are not supposed to know about views. What’s the benefit of that?

  22. #22 by Anonymous on February 3, 2011 - 3:38 pm

    Alexander Ilyin :
    Have you seen Ta-da Lists by 37Signals? Their application is almost identical to the logic.

  23. #23 by kazi tanvir ahsan on February 20, 2011 - 2:54 am

    I got the Matthew’s presentation as well but you have your originality and different from Matthew.Cheers….

  24. #24 by kazi tanvir ahsan on February 20, 2011 - 2:55 am

    your design is different from Matthew and easy to follow.

Comments are closed.