Connecting Zend_Form to Multiple Controllers with Helpers

So in my previous post I have mentioned Zend_Form and some of the basic components and how you can use it. If you haven’t read that yet, than do and come back here. Or if you have the basic knowledge on what is and how Zend_Form works you can go on. In this post I will mention how you can use one form in multiple controllers.


So we keep going with my previous FooController:

class FooController extends Zend_Controller_Action
{
    /**
     *
     * @return Zend_Form
     */
    public function createForm()
    {
        $form = new Zend_Form();
        $form->setAction('foo')
             ->setMethod('post');

        $username = new Zend_Form_Element_Text("username");
        $username->setAttrib("size", 10)
                 ->setLabel("Username")
                 ->addValidator("alnum") //Well, there are not alphanumeric characters in my app for username...
                 ->setRequired(true);

        $password = new Zend_Form_Element_Password("password");
        $password->setAttrib("size", 10)
                 ->setLabel('Password')
                 ->setRequired(true); //You want that this field is required for a valid login submission...

        $loginButton = new Zend_Form_Element_Submit("loginButton");
        $loginButton->setLabel("Send");

        $form->addElements(array($username, $password, $loginButton));
        return $form;
    }

    public function indexAction()
    {
        $this->view->message = "Hello World!";

        $form = $this->createForm();
        
        if ($this->getRequest()->isPost()) { //Request object has this isPost() method which returns if post values exists or not...
            if ($form->isValid($_POST)) { //Well how do you think form was validating the given inputs?
                $this->view->message = "Hello " . $form->getValue("username"); //No more Hello World!
            }
        }
        $this->view->form = $form;
    }
}

So that’s nothing new. The problem occurs if you have another controller, let’s say BarController, and if you have to use the same login form there too, what will you do? Copy paste would not be the best of choice. So you can also create your own base controller class and your FooController and BarController may extend MyLibrary_Controller_Action. And your base class can be like:


class Parkyeri_Controller_Action extends Zend_Controller_Action
{
    /**
     *
     * @return Zend_Form
     */
    public function createLoginForm()
    {
        $form = new Zend_Form();
        $form->setAction('foo')
             ->setMethod('post');

        $username = new Zend_Form_Element_Text("username");
        $username->setAttrib("size", 10)
                 ->setLabel("Username")
                 ->addValidator("alnum")
                 ->setRequired(true);

        $password = new Zend_Form_Element_Password("password");
        $password->setAttrib("size", 10)
                 ->setLabel('Password')
                 ->setRequired(true);

        $loginButton = new Zend_Form_Element_Submit("loginButton");
        $loginButton->setLabel("Send");

        $form->addElements(array($username, $password, $loginButton));
        return $form;
    }
}

//My FooController evolves to something like this than...
//BarController will be similar too...

class FooController extends Parkyeri_Controller_Action
{
    public function indexAction()
    {
        $this->view->message = "Hello World!";

        $form = $this->createLoginForm();
        
        if ($this->getRequest()->isPost()) {
            if ($form->isValid($_POST)) {
                $this->view->message = "Hello " . $form->getValue("username");
            }
        }
        $this->view->form = $form;
    }
}

That’s not bad to use it like this. But if the form count increases your base controller will start to increase the line count and it will be quite hard to manage it. Don’t think only forms, you can also put some access controls or maybe some banner management things in your base controller. Because you would like to enable most of these controls in your entire site and this is the part where you need some help. I introduce you Zend_Controller_Action_Helper.

After a brief introduction on what is an action helper and why you could need them, read this article on additional and detailed information about helpers. The tutorial there is nice too. So one example is worth a thousand words. So I’m changing my FooController like this:

class FooController extends Zend_Controller_Action
{
    public function indexAction()
    {
        $this->view->message = "Hello World!";

        $form = $this->_helper->formLoader('login'); //WTF is ($)this(->_)helper(->)formLoader????
        
        if ($this->getRequest()->isPost()) {
            if ($form->isValid($_POST)) {
                $this->view->message = "Hello " . $form->getValue("username");
            }
        }
        $this->view->form = $form;
    }
}

As you see all I have done was to call a formLoader method! If you read the article I have mentioned previously you will remark that, that’s not a method but an instance. You can reach the class by this syntax because you have set a direct() method in it. If you are familiar with Strategy Pattern, it’s not anything new. If you are not, it basically allows for easy calls (of course some other things too, but that’s how I see it :). So here is the important formLoader object:

class Parkyeri_Controller_Action_Helper_FormLoader extends Zend_Controller_Action_Helper_Abstract
{
    /**
     * @var Zend_Loader_PluginLoader
     */
    public $pluginLoader;

    public function __construct()
    {
        $this->pluginLoader = new Zend_Loader_PluginLoader();
    }

    /**
     * Load a form with the provided options
     *
     * @param  string $name
     * @param  array|Zend_Config $options
     * @return Zend_Form
     */
    public function loadForm($name, $options = null)
    {
        $module = $this->getRequest()->getModuleName(); //Get the current module's name
        if (empty($module)) { //If it's empty than set it to the default module
            $module = $this->getFrontController()->getDispatcher()->getDefaultModule();
        }
        $moduleDirectory = $this->getFrontController()->getControllerDirectory($module);
        //This is a predefined path just like, controllers or models or views
        //You can change this to match your requirements or take it from a config file
        $formsDirectory = dirname($moduleDirectory) . '/forms';

        //I'm setting a prefix path for the classes existing in the forms directory
        //You may need a module prefix according to ZF standarts if you are not in default module
        //The second part, "Form_", is optional yet descriptive on what the class really is.
        $prefix = (('default' == $module) ? '' : ucfirst($module) . '_') . 'Form_';
        //Well you should set this for Zend_Loader to find the class you want...
        $this->pluginLoader->addPrefixPath($prefix, $formsDirectory);

        $name = ucfirst((string) $name);
        $formClass = $this->pluginLoader->load($name);
        return new $formClass($options);
    }

    /**
     * Strategy pattern: call helper as broker method
     *
     * @param  string $name
     * @param  array|Zend_Config $options
     * @return Zend_Form
     */
    public function direct($name, $options = null)
    {
        return $this->loadForm($name, $options);
    }
}

So that’s pretty understanding code and the code is not different from here as I have used it as a base. I have added some comments which are not much required but helps people to understand how it works. This file Should be in a directory path like: lib/Parkyeri/Controller/Action/Helper as you may remark from the class name.

From now on, we have the helper and controller, two things are missing: Form and Bootstraps file. Let’s continue with the form which is very easy and the same as my previous form. Only some naming and some definitions changes from my previous post:

class Form_Login extends Zend_Form
{
    public function  __construct($options = null)
    {
        parent::__construct($options); //Do not forget to call your parent's contructor.
        $this->setAction('login')
             ->setMethod('post')
             ->addDecorator("HtmlTag", array('tag' => "span"));

        $username = new Zend_Form_Element_Text("username");
        $username->setAttrib("class", "txtField")
                 ->setAttrib("size", 10)
                 ->setValue("username")
                 ->addDecorator("Label", array('tag' => null))
                 ->addDecorator('HtmlTag', array('tag' => "span"))
                 ->setLabel(null)
                 ->addValidator("alnum")
                 ->setRequired(true);

        $password = new Zend_Form_Element_Password("password");
        $password->setAttrib("class", 'txtField')
                 ->setAttrib("size", 10)
                 ->addDecorator("Label", array('tag' => null))
                 ->addDecorator('HtmlTag', array('tag' => "span"))
                 ->setLabel(null)
                 ->setValue('password')
                 ->setRequired(true);

        $loginButton = new Zend_Form_Element_Submit("loginButton");
        $loginButton->setAttrib("class", "loginButton")
                    ->setValue("")
                    ->setDecorators(array(array("decorator" => "ViewHelper"),
                                          array("decorator" =>"HtmlTag", "options" => array('tag' => "span", "id" =>"foo"))))
                    ->setLabel(null);

        $this->addElements(array($username, $password, $loginButton));
        return $this;
    }
}

Pretty easy, ha? Note that this file must be located somewhere like here: /app/modules/default/forms/Login.php. If the module is not default and if it’s Parkyeri then the location should be like: /app/modules/Parkyeri/forms/Login.php. And class name should be like: Parkyeri_Form_Login. The reason of this is the formLoader class we are using. These are some predefined requirements. You may change these with changing a bit of code in loadForm() method.

So let’s go on with the Bootstraps. There must be a lots of other definitions like routing, database connections, layouts, and else. But this is just a line you should add during initialization:

//Additional bootstraping...
Zend_Controller_Action_HelperBroker::addPrefix('Parkyeri_Controller_Action_Helper');
//Then you have to dispatch the fron controller

Well, well, well, yet another mistery to solve: Zend_Controller_Action_HelperBroker. It’s the missing part where it’s not explained yet. How the formloader is called how is it set, when the direct() method is called, these were the questions that were not answered. And the answer is Zend_Controller_HelperBroker. Basically, it does these things. It connects your helpers and controllers. It allows you to call the correct helper when the need arises. Helps you maintain the dependencies and require calls. With the code line above, you are setting a folder path where the helper broker will look and include the helpers found in that directory. You may also add instances to your helper broker but this will cause too much trouble for you to maintain your dependencies.

So it was a bit long post but I was able to document the part where I have come so far. Now you can use one form class on multiple controllers with using helpers. I hope this will inspire you to find additional uses of helpers and will help you understand Zend Framework more.

On the next post, I will mention the Zend_Auth. How you can connect it to your login form and with your models. And even mayde how you can use Dojo elements in your application for ajax calls or for creating forms.