Zend_Form’s from Doctrine models: Part 2

June 4, 2008 – 3:08 pm Tags: ,

Previously I wrote about my class for generating Zend_Form forms from Doctrine models. This time, let’s look at some more usage examples and how it works internally, to make it easier for you to utilize it.

Edit 10.10.2008: Updated the post to reflect some minor changes in the CU_ModelForm class

Making a file uploader form

This is something you might need to do sometimes, and infact I’ve already used the modelform class with file uploads. The basic idea is to override the field types for our file columns and to do some custom stuff in the save method.

Let’s imagine we are storing the files somewhere on the server, and they are stored in the DB as filenames. This example uses Rob Allen’s great Zend_Form file upload element, so if you don’t have one, be sure to check it out.

class FileForm extends CU_ModelForm
{
    protected $_model = 'File';
    protected $_fieldTypes = array('filename' => 'File');
    protected $_fieldLabels = array('filename' => 'Upload file');
 
    //We'll store the default file data of existing records here
    protected $_file = '';
 
    protected function _preGenerate()
    {
        //Add path to the file element to pluginloader
        $this->getPluginLoader(self::ELEMENT)->addPrefixPath('App_Form_Element', 'App/Form/Element');
    }
 
    public function setRecord($inst)
    {
        $this->_file = $inst->filename;
        parent::setRecord($inst);
    }
 
    public function save($persist = true)
    {
        //first, have the parent class parse the values to the instance
        $inst = parent::save(false);
 
        if($inst->filename['name'] != '')
        {
            //We have a file uploaded
            //put code here to move it etc.
 
            //Assign the correct filename to the instance
            $inst->filename = 'the name of file';
        }
        else
        {
            //If we don't have a file, we still get an empty FileValue
            //so we must replace the filename with the old value
            $inst->filename = $this->_filename;
        }
 
        //Finally, save the instance if required and return it.
        if($persist)
            $inst->save();
 
        return $inst;
    }
}

So this class extends the basic functionality to allow it to process an uploaded file. The variables are first used to define the behavior of the form, and then we extend the methods to customize it even more. We need to tell the form where the File element class resides, so we do that in _preGenerate.

To not lose the original filename when editing existing rows, we store the filename in a variable in the form in setRecord. The big processing is finally done in the save method. We first let the parent process the form data and assign it all to the instance. Then, we do our custom handling of the file fields by checking if the filename field contains an array with file details and process it.

The persist parameter is used by the class to determine whether or not to call the save() method of the instance, so we should abide to the behavior. Finally the record is returned.



This is a pretty typical scenario. Often you need some custom processing that simply can’t all be put into the main class, so you must override the methods. Luckily it’s quite easy as you can see.

Modifying generated elements

Sometimes you may also want to modify the generated elements. For example, if you have a textarea, you might want to modify the cols and rows attributes of it, or perhaps assign onclick handlers or CSS classes.

The best place to do this is the _postGenerate method, as at that point everything is already generated. For example…

protected function _postGenerate()
{
    //Get element for column "data" and modify it
    $el = $this->getElement($this->_fieldPrefix . 'data');
    $el->setAttrib('class', 'data');
}

So it’s quite simple. The class prefixes all fields and so you must use the _fieldPrefix variable to get it to find out the correct name for the field. You can also change the prefix the same way as using the other variables like _fieldLabels and _model.

Internals

There are also some more internal methods that can be of use for modifying the way the forms work.

The _record variable is used to store the record when editing an existing row, however it’s recommended that you use the getRecord accessor, which will initialize an empty instance in case of a new row.

When you have many-relations enabled, you can use _getFormName to get a link to the related record’s form. It takes the relation’s alias and the related record as arguments, both of which you can get from the model or its table. A reference to the table is stored in variable _table.

Related record subforms are given a Fieldset decorator, so they get rendered with the fieldset-element in HTML.

There are also methods for getting the names of the buttons used in the related record subforms: _getNewButtonName and _getDeleteButtonName.

For fetching a list of the columns used for creating the form, you can use _getColumns. It will return all column definitions, except those which are either primary keys, have been ignored by _ignoreColumns or don’t have a column type (which should not happen).

For fetching a list of un-ignored relations, you can use _getRelations.

Share this:
  1. 12 Responses to “Zend_Form’s from Doctrine models: Part 2”

  2. Thanks for this, it really saves time and hassle.

    However, this crashes with Doctrine 1.1RC2, complaining about the missing Constant MANY_AGGREGATE in Doctrine_Relation.

    Changing it to Doctrine_Relation::MANY got it working again but I’m not sure it’s fine.

    By desfrenes on Mar 8, 2009

  3. I suggest getting the code from the updated branch: modelform-adapter branch

    It works a little differently from this older version. You can find some examples of it at the Zend Framework proposal for it

    By Jani Hartikainen on Mar 8, 2009

  4. Nice version. I hope it will make it to the Zend distro.

    By desfrenes on Mar 9, 2009

  5. I’m trying to use your classes from the modelform-adapter branch, and have run into a few issues.

    1. I cannot get email validation to work.

    2. I am required to go through $form->save(); Instead of something like:
    $message = new Message();
    $message->fromArray($form->getValues(true));
    $message->posted = new Doctrine_Expression(‘NOW()’);
    $message->save();

    3. I think fields that do not have labels should go through ucwords.

    By Benjamin "balupton" Lupton on Jun 19, 2009

  6. Regarding #1, I feel I should elaborate more, I’ve extended this to have notblank on all columns as well. So if I submit a blank message, I will get:
    Message: Validation failed in class Message 4 fields had validation errors: * 1 validator failed on posted (notblank) * 1 validator failed on name (notblank) * 1 validator failed on message (notblank) * 2 validators failed on email (email, notblank)

    Which is the doctrine validator kicking up errors. What I would like is something similar to Zend_Form where errors are displayed nicely.

    By Benjamin "balupton" Lupton on Jun 19, 2009

  7. Hello I had Problem with CammelCase Syntax for my TableFields.

    I resolved this problems when i changed the function _getColums to:

    $name = $this->_table->getFieldName($name);
    $columns[$name] = $definition;

    By me on Jul 15, 2009

  8. Greate, but it seems not to work properly with tables that act as translatable.

    By daniel on Sep 30, 2009

  9. Great job!

    I am also missing translatable and versionable support. Are you still developing on the modelform and do you plan to include these features.

    By mh on Jan 21, 2010

  10. I’m confused about the new code. CU_ExampleForm extends CU_Model_Form, like last time, but there’s no ModelForm.php anymore in that branch. Was it replaced w/ some other code?

    By Joe Devon on Apr 8, 2010

  11. The new code uses CU_Form_Model instead of ModelForm. See the unit test here, maybe it’ll give you some tips:

    http://codeutopia.net/code/branches/modelform-adapter/tests/library/CU/Form/ModelTest.php

    By Jani Hartikainen on Apr 8, 2010

  12. Great class!!!
    Just starting to use Zend + Doctrine and your class (experience with Symfony+Doctrine)… and have a question:

    I have a one to one relation (a record in Table1 points to one record in Table2). Now I generate a form for Table1 and I should be able to automatically embed the form for Table2 right?

    But how do I do that? I do have a form for Table2 but I can’t seem to figure out how to load it. Its called LoginForm in the application/forms folder (where form for Table1 also resides).
    – For now I get class not found errors on the line: $label = (string)Doctrine_Manager::connection()->getTable($alias) .. this is the model class from table2 it says it cant find.
    – If I replace $alias with $relation->getClass() it works (no erros) but I get html code in the label.
    – Aside from that I only get a list of id’s to table2 but I expect a subform for a new record.

    Thanx a lot!
    Chris

    By Chris de Jager on Jun 24, 2010

  1. 1 Trackback(s)

  2. Jun 13, 2008: Autogenerating forms from Doctrine models | CodeUtopia

Post a Comment

You can use some HTML (a, em, strong, etc.). If you want to post code, use <pre lang="PHP">code here</pre> (you can replace PHP with the language you are posting)