Autogenerating forms from Doctrine models

June 2, 2008 – 7:37 am Tags: , , ,

In a previous post I mentioned how Django’s model forms are awesome.

I really like the idea of being able to generate forms automatically from models - I mean the models already should contain most of the data you’d need: the fields, field types and how they will be stored.

Since I was already quite familiar with Doctrine’s internals, I knew it would be possible to find out all the data on the models quite easily, and set upon creating a class which generates Zend Framework’s Zend_Form based form classes from Doctrine models…

Theory of Operation

The idea is quite simple. Simply get all the columns from the model and use them to generate fields for a form, and thanks to Doctrine, the implementation was simple too.

I borrowed some basic ideas from Django: a basic form should be easy to create, since this is essentially a convenience feature, but it also has to be possible to modify them to adapt to different tasks.



The very simplest form would look like this:

<?php
class ExampleForm extends CU_ModelForm
{
    //This defines what model the form uses
    protected $_model = 'Example';
}

This would give you a class with the same interface as Zend_Form, but it would automatically get fields from the model called Example. Having the same interface as Zend_Form means that you can essentially use the class just like any other Zend_Form instance; it just has some more magic. :)

Basic usage

Creating new records

//ExampleController.php
class ExampleController extends Zend_Controller_Action
{
    public function formAction()
    {
        $form = new ExampleForm(array(
            'action' => '.',
            'method' => 'post'
        ));
 
        //the form does not have a submit button by default
        $form->addElement('submit', 'Save');
 
        if($this->getRequest()->isPost() && $form->isValid($_POST))
        {
            //This saves the form's data to the DB.
            //$record will be the new model instance created when saving.
            $record = $form->save();
 
            //redirect elsewhere after completion
            $this->_redirect('/something');
        }
 
        //assign the form to the view
        $this->view->form = $form;
    }
}
 
//view script for formAction
<h2>Fill this form</h2>
<?= $this->form; ?>

The formAction method is a pretty typical form process action: it creates a form and validates it if the request was a form submission, or displays it if it wasn’t or something was not valid. In the action, we add a submit button to the form, as the form generator does not add a submit button by default as you may wish to use the forms as subforms or such.

Editing existing records

Editing existing records is similar to what seen above, but we must first load a record and assign it to the form before rendering, validating or saving:

//Get row with id=10
$record = Doctrine::getTable('Example')->find(10);
$form->setInstance($record);

After using setInstance, the default field values will be read from the record passed to the method. Also, when calling save(), any modifications will be saved to this record instead of a new one.

Modifying the form behavior

The class in its current implementation supports both creating new records and editing existing ones, and it can also display some relations as select boxes and subforms. You can also make it ignore chosen columns so it won’t autogenerate fields for them and such. Other options include giving labels for fields and switching their types.

Advanced settings example

<?php
class AdvancedForm extends CU_ModelForm
{
    protected $_model = 'Article';
 
    //By default, many-relations will be ignored but let's enable them
    protected $_generateManyFields = true;
 
    //Let's ignore these two cols so they don't get a field
    protected $_ignoreColumns = array('created_at', 'updated_at');
 
    //Make the content column's field type 'textarea' instead of the default 'text'
    protected $_fieldTypes = array(
        'content' => 'textarea'
    );
 
    //Give some human-friendly labels for the fields:
    protected $_fieldLabels = array(
        'name' => 'Article name',
        'content' => 'Article content',
        'category_id' => 'Category'
    );
 
    //Give a label to a many relation
    protected $_relationLabels = array(
        'ArticleComment' => 'Comment'
    );
 
    //this method is called before the form is generated
    protected function _preGenerate()
    {
        //We need to tell the form loader where our custom many-relation tables are located
        $this->getPluginLoader(self::FORM)->addPrefixPath('My_Form', 'My/Form');
    }
 
    //this method is called after the form is generated
    protected function _postGenerate()
    {
        //Add a submit button
        $this->addElement('submit', 'Save');
    }
}

In the above snippet you can see all the configuration options and the two event methods. Most of the variables should be quite self-explanatory. However, the plugin loader part may be a bit confusing at first, so let’s look at what it does.

The many relations for models are rendered as subforms. For this, the parent form needs to know where the subforms are located so it can use them. By default, it will look at App/Form/Model/ModelName.php, so in the above example, it would look at App/Form/Model/ArticleComment.php. If you put your forms in the default directory, you won’t need to call addPrefixPath, but in the case where they aren’t in that path, you’ll need to tell it where they are. The ModelName.php forms should be other forms that extend CU_ModelForm.php, so for example the ArticleComment.php would contain a class called My_Form_ArticleComment and it would extend CU_ModelForm just like this one does.

Also, as I mentioned earlier, the form will not generate a submit button by default as it does not know what you wish to do with the form in the end. However, here we add a _postGenerate method that automatically adds a submit button so we won’t have to worry about it.

Download

The code for CU_ModelForm can be found in my public SVN repo, more specifically the files you need are CU/ModelForm.php and CU/Validate/DbRowExists.php. ModelForm.php is the main class and DbRowExists.php is used by the relation support to validate that the selected item exists in the database and is valid for the relation.



Please let me know what you think, any bugs and ideas :)

Check the second model form post for some more usage examples and info on internals.

  1. 5 Responses to “Autogenerating forms from Doctrine models”

  2. I hope I didn’t forget to mention anything important. I have a bad habit of doing that with blogposts and then editing them in a hurry… anyway, let me know if there’s anything you’d like more specific details on.

    By Jani Hartikainen on Jun 2, 2008

  3. Great idea. I’ve been missing something like this for awhile now. Haven’t tried your code yet but this would really be nice to have. Keep it up.

    By Daniel on Jun 2, 2008

  4. I’ll put this to the test tonight or tomorrow! It looks good, but I haven’t peaked at your code yet. :)

    By Josh Team on Jun 5, 2008

  5. I am playing with it now, really quick this line on the AdvancedForm is missing a ;

    protected $_generateManyFields = true

    should be

    protected $_generateManyFields = true;

    So far very impressive!

    By Josh Team on Jun 13, 2008

  6. great job
    i’m using it and works fine
    i also added a private function to CU_ModelForm to autoload form labels from a textfile
    with ZendTranslate

    something like:

    protected function _loadLabels(){
    $translate = new Zend_Translate(’csv’, Zend_Registry::get(’rootDir’).’/language/pt-br/lang.csv’, ‘pt_BR’);

    foreach($this->_getColumns() as $name => $definition)
    {
    $this->_fieldLabels[$name] = $translate->_($name);
    }
    }

    By Luiz Felipe on Jun 30, 2008

Post a Comment