Spiga

Using Zend_Form with Zend_Config

March 17, 08 by Andrew Vayanis

In my previous post Zend Framework, A First Look, I discussed the lacking nature of ZF’s documentation, in particular, with regards to Zend_Form. I have since then learned that this is partly due to the fact that Zend_Form is a relatively new component. However, I still wanted to make use of Zend_Form in my current project and decided to trudge through the learning curve of creating a simple custom login form in conjunction with Zend_Config; the end result being an easy to maintain, custom form and this guide. Hopefully, this guide will make it easier for anyone else looking to take advantage of this very cool feature.

Before I begin talking about how Zend_Form works, I think it is important to explain a bit about Zend_Config and how an INI file is translated into PHP. As the Zend_Config Documentation points out, it makes use of PHP native parse_ini_file, however, this only goes so far as reading in the contents of the file and turning it into a 1 dimensional associative array(2 if a section is provided). Zend_Config extends this behavior by taking the keys of each association and breaking them down further based upon the specified key separator (default is ‘.’). and turning the remainder into arrays. In the end, Zend_Config takes a series of declarations such as the following:

login.elements.username.type = "text"

And turns it into:

Array
(
    [login] => Array
        (
            [elements] => Array
                (
                    [username] => Array
                        (
                            [type] => text
                        )
                )
        )
)

Now, back to Zend_Form. I think my biggest gripe with the documentation provided for Zend_Form is that it does not explain how anything is working behind the scenes. For instance, it does not explain that Zend_Form normalizes config statements by prepending ’set’ when making calls to member methods. This is invaluable as it explains why

    $form->setElementDecorators(array('ViewHelper'));

in PHP becomes

    elementDecorators.helper = "ViewHelper"

in a Zend_Config_Ini. Internally, elementDecorators tells Zend_Form to call setElementDecorators(); This small example also shows why it is important to know how Zend_Config_Ini actually translates an INI to PHP…

Array
(
    [elementDecorators] => Array
        (
            [helper] => ViewHelper
        )
)

As we can see this declares an array of decorators to be passed into setElementDecorators(); The keys, in this case “helper”, do not necessarily matter as it is only needed to index the array, the values are what matter, as that is what is passed and used within Zend_Form. Similarly, it is important to know that almost all aspects of form and form elements, excluding form element type, are implemented internally as options. The following is an example INI file with comments explaining particular sections.

    ; A basic Form config
 
    ; First level attributes are automatically treated as options.
    action = "login/submit"
    method = "post"
    id = "login"
 
    ; Form Decorators
 
    ; Again these decorators are treated as options of the main form.
    decorators.elements.decorator = "FormElements"
    decorators.table.decorator = "HtmlTag"
    decorators.table.options.tag = "table"
    decorators.form.decorator = "Form"
 
    ; Username Element
 
    ; Form Element Type is specified explicityly, not as an option.
    elements.username.type = "text"
 
    ; Username attributes (Label, Required, and validators are all
    ; declared as options).
    elements.username.options.label = "Username:"
    elements.username.options.required = true
    elements.username.options.validators.alnum.validator = "alnum"
    elements.username.options.validators.regex.validator = "regex"
 
    ; Validator parameters such as pattern to regex are also
    ; declared as options. I have not looked into it, but I
    ; assume this is due to the way Zend_Validate handles its
    ; own constructors.
    elements.username.options.validators.regex.options.pattern = "/^[a-z]/i"
    elements.username.options.validators.strlen.validator = "StringLength"
    elements.username.options.validators.strlen.options.min = "5"

Bringing it all together, the following is the first version of my login form.

    [login]
    ; General Form Information
    login.action = "login/submit"
    login.method = "post"
    login.id = "login"
 
    ; Form Decorators
    login.decorators.elements.decorator = "FormElements"
    login.decorators.table.decorator = "HtmlTag"
    login.decorators.table.options.tag = "table"
    login.decorators.form.decorator = "Form"
 
    ; Username Element
    login.elements.username.type = "text"
    login.elements.username.options.label = "Username:"
    login.elements.username.options.required = true
    login.elements.username.options.validators.alnum.validator = "alnum"
    login.elements.username.options.validators.regex.validator = "regex"
    login.elements.username.options.validators.regex.options.pattern = "/^[a-z]/i"
    login.elements.username.options.validators.strlen.validator = "StringLength"
    login.elements.username.options.validators.strlen.options.min = "5"
 
    ; Password Element
    login.elements.password.type = "password"
    login.elements.password.options.label = "Password:"
    login.elements.password.options.required = true
    login.elements.password.options.validators.strlen.validator = "StringLength"
    login.elements.password.options.validators.strlen.options.min = "6"
 
    ; Submit Form Element
    login.elements.submit.type = "submit"
    login.elements.submit.options.label = "Submit"
 
    login.elementDecorators.viewHelper = "ViewHelper"
    login.elementDecorators.errors = "Errors"
 
    login.elementDecorators.tableData.decorator.td = "HtmlTag"
    login.elementDecorators.tableData.options.tag = "td"
    login.elementDecorators.tableData.options.class = "test2"
 
    login.elementDecorators.label.decorator = "Label"
    login.elementDecorators.label.options.tag = "td"
 
    login.elementDecorators.tableRow.decorator.tr = "HtmlTag"
    login.elementDecorators.tableRow.options.tag = "tr"

This creates a login form with a username and password field along with a submit button. However, instead of being wrapped in a definition list using definition titles for labels and definition data for input fields, I have wrapped the labels and input fields within a table similar to the google login form. Notice though, that I have sectioned this form using [login]. Tthis is so that I can define all my necessary forms in one forms.ini file and load each form by section. I have also prepended each declaration with login. as a way to keep my form definitions organized and easy to read (to me at least).

Concluding this guide, I would like to reiterate, how useful I think Zend_Form is as it solves one of the most tedious process for any PHP project; form creation and validation. I hope it continues to evolve and mature, and I hope others find it as handy as I have. Although, if anyone has an easier solution, please let me know.

Similar Posts

Add your comment

30 responses for this post

  1. Gravatar
    Vincent Says:

    Thanks for the article! it was very very helpful.

    I love using ini config files, it makes maintaining code a million times easier than messing with php coding.

    but i’ve hit a brick wall. i’ve tried setting custom validation error messages with no luck… do you have any ideas?

    ; orderFullName element
    cartForm.elements.orderFullName.type = text
    cartForm.elements.orderFullName.options.label = Full Name
    cartForm.elements.orderFullName.options.size = 25
    cartForm.elements.orderFullName.options.maxlength = 25
    cartForm.elements.orderFullName.options.validators.strlen.validator = StringLength
    cartForm.elements.orderFullName.options.validators.strlen.options.min = 2
    cartForm.elements.orderFullName.options.validators.strlen.options.max = 25
    cartForm.elements.orderFullName.options.required = true
    cartForm.elements.orderFullName.options.required.setMessages = Required field

  2. Gravatar
    Andrew Vayanis Says:

    I was actually going to throw in another post regarding this, but I will show you now what I did to set custom validation messages because the defaults are definitely horrible.

    elements.username.type = “text”
    elements.username.options.label = “Username:”
    elements.username.options.required = true
    elements.username.options.validators.notempty.validator = “NotEmpty”
    elements.username.options.validators.notempty.options.messages.isEmpty = “A valid username is required.”
    elements.username.options.validators.notempty.breakChainOnFailure = true
    elements.username.options.validators.alnum.validator = “alnum”
    elements.username.options.validators.alnum.breakChainOnFailure = true
    elements.username.options.validators.alnum.options.messages.notAlnum = “Invalid username. Please enter a valid username.”
    elements.username.options.validators.strlen.validator = “StringLength”
    elements.username.options.validators.strlen.options.min = “5″
    elements.username.options.validators.strlen.options.max = “30″
    elements.username.options.validators.strlen.options.messages.stringLengthTooShort = “Invalid username. Please enter a valid username.”
    elements.username.options.validators.strlen.options.messages.stringLengthTooLong = “Invalid username. Please enter a valid username.”

    I am still learning the Zend Framework myself, but I hope this helps. Also, feel free to ask any other questions, and I will answer what I can.

  3. Gravatar
    VINCENT Says:

    Thanks Andrew for the reply, I really appreciate the reply, you got me out of a big hole (I was about to abandon config files for my forms and use nasty php).

    I’m also still learning the Zend Framework (24 hours into it) and I’ve decided to completely rebuild my apps using it :) .

    I think its a very good framework to use, it is a bit of a learning curve to get into the MVC pattern, but after that, its pretty easy. Most of it is just getting around the idea of a MVC file structure and a bootstrapper.

    I must say however, the Zend Framework does provide very good documentation on the PHP side, but it does lack documentation on formatting config ini files and config xml files, which is a shame because all web 2.0 websites driven by AJAX is built on the idea of text config files. I hope that they fix that up soon.

    Other than that, thank god for open source discussion.

    btw. how did you figure out to use messages instead of setmessages (from setMessages();)?

  4. Gravatar
    Andrew Vayanis Says:

    Don’t want this to come off as snobby, so I preface before saying it, but as stated in the post, when ZF looks up class methods using the Zend_Config, method calls are prepended with ’set’. Therefore, internally, ZF is actually calling setMessages().

    As you said the documentation is pretty good on the PHP side, and a bit lacking on the config side. However, there is also a bit of a disconnect which I will hope to bridge (with my own posts) as I learn the framework myself. The tidbit about prepending ’set’ to method calls is just one of many examples I am sure you will find as you go through things.

  5. Gravatar
    Matthew Weier O'Phinney Says:

    Something to note: you were reading the documentation prior to the final release of 1.5.0. I put a lot of work into the documentation in that final week, and the results are online now.

    In the documentation, there is a ‘Configuration’ section for each subcomponent of Zend_Form. In these, I have language such as the following:

    * If ’set’ + key refers to a Zend_Form_Element method, then the value provided will be passed to that method.
    * Otherwise, the value will be used to set an attribute.

    Hopefully this addresses your documentation concerns with Zend_Form. I realize there are a lot more places where it needs improvement — e.g., more examples on decorators — but hopefully your primary concerns have been addressed.

  6. Gravatar
    Andrew Vayanis Says:

    I just went to look at the new documentation, and this does address my concerns. However, it might be worth pointing out that these types of disconnects, are prevalent elsewhere within the Zend Framework’s documentation. Unless, they have also been addressed with the release of 1.5?

    Anyhow, I still like how things are coming, keep up the good work!

  7. Gravatar
    Apit Says:

    Hi. I am new zfform. Your post helps me understand and appeciate customizability of zfform more. Thx.

  8. Gravatar
    Chris Woodford Says:

    the comment on how to customize the error messages for StringLength is worth it’s weight in gold. I was having a hell of a time trying to figure that out. cheers!

  9. Gravatar
    Paul Says:

    Hi,

    I am fearly new to ZF too and I am happy with these articles that describes the way to use Zend_Form using Zend_Config_Ini.

    However when i use the this way to render a form I do get an error-message:

    Notice: Array to string conversion in …\library\Zend\View\Helper\HtmlElement.php on line 104

    I am using Zend Framework 1.6 on xampp with PHP 5.2.6.

    I construct the form using:
    $siteRootDir = Zend_Registry::get(’siteRootDir’);
    $config = new Zend_Config_Ini($siteRootDir . ‘/configuration/forms.ini’, ‘login’);
    $form = new Zend_Form($config->user->login);

    The ini-file I am using is:

    [login]
    ; general form metainformation
    user.login.action = “/user/login”
    user.login.method = “post”

    ; username element
    user.login.elements.username.type = “text”
    user.login.elements.username.options.validators.alnum.validator = “alnum”
    user.login.elements.username.options.validators.regex.validator = “regex”
    user.login.elements.username.options.validators.regex.options.pattern = “/^[a-z]/i”
    user.login.elements.username.options.validators.strlen.validator = “StringLength”
    user.login.elements.username.options.validators.strlen.options.min = “6″
    user.login.elements.username.options.validators.strlen.options.max = “20″
    user.login.elements.username.options.required = true
    user.login.elements.username.options.filters.lower.filter = “StringToLower”

    ; password element
    user.login.elements.password.type = “password”
    user.login.elements.password.options.validators.strlen.validator = “StringLength”
    user.login.elements.password.options.validators.strlen.options.min = “6″
    user.login.elements.password.options.required = true

    ; submit element
    user.login.elements.submit.type = “submit”

    Can anyone help me to fix this problem?
    Thanks in advance…

  10. Gravatar
    Brian W Says:

    It should be like this:

    $siteRootDir = Zend_Registry::get(’siteRootDir’);
    $config = new Zend_Config_Ini($siteRootDir . ‘/configuration/forms.ini’, ‘login’);
    $form = new Zend_Form($config->user->toArray());

  11. Gravatar
    Erick Says:

    So much code for a stupid form? How much is the “maintainability” of this drivel a big enough factor to warrant this enormous backend?

  12. Gravatar
    Andrew Vayanis Says:

    When you say this enormous backend, are you referring to the use of Zend_Config with Zend_Form, or just Zend_Form in general. If it is just the config, I can somewhat agree with you. The definitions are definitely verbose. However, it can still be beneficial since you can define your form fields, and input validation without having to write very much actual code.

    On the other hand, if you are referring to Zend_Form in general, then I will disagree because you get more than just maintainability. You also functionality such as input validation.

  13. Gravatar
    Bob Hanson Says:

    Andrew,

    first thank you so much for your post. While there is documentation on Z for ZF, they dont go into too much by the way of really using ini’s. This post has really clarified quite a bit for me.

    i do have an issue. I have all my elements setup and displaying etc…the problem im having is with the validation piece. i have the validation setup in my ini for each element, but it doesnt seem to work. I can get the element to display, i can submit data to my db, but im confused on how to get the validators to fire?

    i think im missing something?

  14. Gravatar
    Jesse LaVere Says:

    Great article! Very helpful.

    I am trying to disable an option in a html select in an ini file.

    I thought something like the code below would work after reading your docs, but it doesn’t.

    schoolForm.elements.status.attrib.disable = “Inactive”

    Thanks for any help.

  15. Gravatar
    Andrew Vayanis Says:

    @ Bob

    Hey, I have been terribly with keeping up my blog recently. I hope you found a solution to your answer. However, if you haven’t, I would have to see what you have in your controller to tell if something is wrong. In particular, are you calling the validation function correctly?

  16. Gravatar
    Andrew Vayanis Says:

    @ Jesse

    I know I have done that before, but I can’t access the project from where I am now. However, I believe its along the lines of schoolForm.elements.status.options.disable = “disable”

  17. Gravatar
    manuscule Says:

    And for an element select in a ini file ??

  18. Gravatar
    Andrew Vayanis Says:

    Sorry, but I am not sure what the question is?

  19. Gravatar
    manuscule Says:

    how i can insert a dynamic data list in a html select in an ini file ?

  20. Gravatar
    Marc Says:

    Hi There. Firstly, thank you for at last shedding light on this tutorial. I do have somewhat of a dilemma in getting group elements to work, and was wondering if you could help. I have a form class which sets out the decorators as follows:

    false
    )
    ),
    array(
    ‘HtmlTag’, array(
    ‘tag’=>’li’
    )
    )
    );

    protected $_buttonElementDecorator = array(
    ‘ViewHelper’
    );

    protected $_standardGroupDecorator = array(
    ‘FormElements’, array(
    ‘HtmlTag’, array(
    ‘tag’=>’ol’
    )
    ),
    ‘Fieldset’
    );

    protected $_buttonGroupDecorator = array(
    ‘FormElements’,
    ‘Fieldset’
    );

    protected $_noElementDecorator = array(
    ‘ViewHelper’
    );

    public function __construct($options = null)
    {
    $this->addElementPrefixPath(‘Application_Form_Decorator’, ‘Application/Form/Decorator/’, ‘decorator’);
    $this->addElementPrefixPath(‘Application_Filter’, ‘Application/Filter/’, ‘filter’);
    $this->addPrefixPath(‘Application_Form_Element’, ‘Application/Form/Element/’, ‘element’);

    $this->_setupTranslation();

    parent::__construct($options);

    $this->setAttrib(‘accept-charset’, ‘UTF-8′);
    $this->setDecorators(array(
    ‘FormElements’,
    ‘Form’
    )
    );
    }

    protected function _setupTranslation()
    {
    if (self::getDefaultTranslator())
    return;

    $path = Bootstrap::$root . ‘/translate/forms.php’;
    $translate = new Zend_Translate(‘array’, $path, ‘en’);
    self::setDefaultTranslator($translate);
    }
    }

    and then i declare the forms as follows

    addElement(‘text’, ‘name’, array(
    ‘decorators’ => $this->_standardElementDecorator ,
    ‘label’ => ‘SubCategory Name:’ ,
    ‘attribs’ => array(
    ‘maxlength’ => 20
    ) , ‘validators’ => array(
    array(
    ‘StringLength’ , false , array(
    4 , 20
    )
    )
    ) ,
    ‘required’ => true
    )
    );

    $this->addElement(‘text’, ‘description’, array(
    ‘decorators’ => $this->_standardElementDecorator ,
    ‘label’ => ‘Description:’ ,
    ‘attribs’ => array(
    ‘maxlength’ => 500
    ) , ‘validators’ => array(
    array(
    ‘StringLength’ , false , array(
    4 , 500
    )
    )
    ) ,
    ‘required’ => true
    )
    );

    $this->addElement(’select’, ‘category_id’, array(
    ‘decorators’ => $this->_standardElementDecorator ,
    ‘label’ => ‘Category:’ ,
    ‘attribs’ => array(
    ‘validators’ => array(
    ‘NotEmpty’ => true
    )
    ) ,
    ‘multiOptions’ => getCategoryArray()
    )
    );

    $this->addDisplayGroup(array(
    ‘name’ ,
    ‘description’ ,
    ‘category_id’
    ),
    ’subcategorycreate’, array(
    ‘disableLoadDefaultDecorators’ => true , ‘decorators’ => $this->_standardGroupDecorator , ‘legend’ => ‘Edit SubCategory’
    )
    );

    $this->addElement(’submit’, ’submit’, array(
    ‘decorators’ => $this->_buttonElementDecorator ,
    ‘label’ => ‘Save’
    )
    );

    $this->addDisplayGroup(array(
    ’submit’
    ),
    ’subcategoryeditsubmit’, array(
    ‘disableLoadDefaultDecorators’ => true ,
    ‘decorators’ => $this->_buttonGroupDecorator ,
    ‘class’ => ’submit’
    )
    );
    }
    }

    but i cant get any of this to use the groupdisplay decorators properly from the ini file. any ideas?

  21. Gravatar
    Marc Says:

    i think i ran out of space there dammit. sorry

  22. Gravatar
    Andrew Vayanis Says:

    Hey Mac,
    Some of the text got filtered out by wordpress unfortunately, so I am not sure what the exact dilemma is. Are you trying to define that form class using an INI file? Or maybe trying to initialize it using an INI file? It might be easier to email me directly regarding your question so I can see your code completely.

    You can contact me at andrew@vayanis.com

    Andrew

  23. Gravatar
    Andrew Vayanis Says:

    @manuscule I apologize for the late reply, I have been busy and away for a few weeks. Anyhow, regarding your question, if you have dynamic data that needs to be loaded into the form, I think it makes the most sense to only initialize your form using default values defined within an INI. Then, where appropriate using a model or perhaps action helper, dynamically modify the form where necessary.

  24. Gravatar
    marc Says:

    Hi andrew, thank you, I have mailed you in detail.

  25. Gravatar
    Stacey Claman Says:

    Can you believe it? I read it twice. While I am not as proficient on this subject, I concur with your closings because they make sense. Gives Thanks and goodluck to you.

  26. Gravatar
    Neamar Says:

    Is there a way to reverse this (Zend_Form to Zend_Config), for instance to save an Zend_Form instance ? I can’t find anything about this, so i guess it’s impossible :(

  27. Gravatar
    Andrew Vayanis Says:

    What do you mean save a Zend_Form instance? What exactly are you trying to accomplish?

  28. Gravatar
    Neamar Says:

    Well, i would like to make a function taking a Zend_Form object as parameter, and returning the XML of this Zend_Form. This could be useful for serialising, multi server sharing, and XSLT representation.

  29. Gravatar
    Andrew Vayanis Says:

    Well certainly converting it to XML could be useful for XSLT, but if you are sharing across multiple PHP servers you could transfer it using the native PHP serialization function. In any case, I don’t know of a way within the Zend Framework to take a Zend_Form instance and turn it into XML. That could be something useful to create and contribute.

  30. Gravatar
    Neamar Says:

    I used XML Form for easy “form building” with javascript drag’n'drop, and for XSLT too (even if i still think it’s a bad idea… my boss didn’t agree :\)

    I ended up using Zend Config and modifying directyl the Zend_Config object instead of the Zend_Form : since i made only one setConfig() per page, it seems a good trade-off.

Leave a Reply