Zend_Form’s from Doctrine models: Part 2

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.