12.14.07

7 Days of Symfony 1.1 - Forms, Widgets and Validators (Day2)

Posted by ryan in symfony


Note: Many things here may be out of date. This article is up to date only through early 2008 (which was a while ago).

Day 2, time to actually makes some forms. Let's jump right in. First, let's define a model to play with:

schema.yml:

propel:
  author:
    _attributes:          { phpName: Author }
    id:
    first_name:          varchar(30)
    last_name:           varchar(30)
    email:                   varchar(75)
    website:               varchar(100)
    birthday:              date
    years_experience: integer
  article:
    _attributes:          { phpName: Article }
    id:
    author_id:
    body:                    longvarchar
    file_attachment:   varchar(255)

That's it - just a simple model so that can exercise the new handling of forms in symfony. The command to build the model in Symfony 1.1 is exactly the same, but it packs a new, very powerful, punch. From the command line:

symfony propel-build-all

As usualy, Symfony generates helping models in your lib/model project directory. However, Symfony now adds a NEW directory - lib/forms. Just like with the model classes in lib/model, Symfony has automatically generated form classes to make creating and handle forms a breeze.

Let's go ahead and immediately see this in action. Create a new module, I'll call mine "articles". In that model, let's create an action called editAuthor:

  public function executeEditAuthor()
  {
  	$this->form = new AuthorForm();
  }

Now, in the template editAuthorSuccess.php:

<form action="<?php echo url_for('/articles/editAuthor'); ?>" method="post">
	<table>
		
		<?php echo $form; ?>
		
		<tr>
			<td><input type="submit" value="submit form"/></td>
		</tr>
	</table>
</form>

Et voila! Navigate of to this action to find a completed and formatted form. Most fields are textboxes, as they properly should be. You'll also notice that the birthday field has been output as a select year, month date field. As we'll find out later, foreign keys will automatically show up as select boxes as well.

Go ahead and submit the form. Nothing happens - it just submits back to the same page and reprints the form. Back in your actions, add this:

public function executeEditAuthor()
  {
  	$this->form = new AuthorForm();
  	if ($this->getRequest()->isMethod('post'))
  	{
  		$this->form->bind($this->getRequestParameter('author'));
  	}
  }

Now, refresh tha page and submit. What has changed. Well actually, ALOT. Try putting your name into the name fields, when you hit submit, they'll come back with your name automatically in them. But there's even more. Try putting a string like "ten" into the "years experience" field. When you submit now, you should get an error! That's right, you haven't actually written any form code, but your form is already validating. Remember how our name fields were of type varchar(30)? Try putting more than 30 characters into either of those fields. Once again, Symfony automatically validates the field and throws a validation error. What's more, we're just one line of code away from saving the author to the db if - and only if - validation clears.

How did Symfony go about setting up that form for you? Let's look at the file lib/forms/AuthorForm.class.php:

class AuthorForm extends BaseAuthorForm
{
  public function configure()
  {
  }
}

Just like with the models, Symfony automatically gives you a file that you can modify (which we'll do a bunch of later). The true work is done in the base class BaseAuthorForm located in the base subfolder (again, just like the models). Let's look at that file:

public function setup()
  {
    $this->setWidgets(array(
      'id'               => new sfWidgetFormInputHidden(),
      'first_name'       => new sfWidgetFormInput(),
      'last_name'        => new sfWidgetFormInput(),
      'email'            => new sfWidgetFormInput(),
      'website'          => new sfWidgetFormInput(),
      'birthday'         => new sfWidgetFormDate(),
      'years_experience' => new sfWidgetFormInput(),
    ));

    $this->setValidators(array(
      'id'               => new sfValidatorInteger(array('required' => false)),
      'first_name'       => new sfValidatorString(array('max_length' => 30, 'required' => false)),
      'last_name'        => new sfValidatorString(array('max_length' => 30, 'required' => false)),
      'email'            => new sfValidatorString(array('max_length' => 75, 'required' => false)),
      'website'          => new sfValidatorString(array('max_length' => 100, 'required' => false)),
      'birthday'         => new sfValidatorDate(array('required' => false)),
      'years_experience' => new sfValidatorInteger(array('required' => false)),
    ));

    $this->widgetSchema->setNameFormat('author[%s]');

    $this->errorSchema = new sfValidatorErrorSchema($this->validatorSchema);

    parent::setup();
  }
  ...
}

There's a bit more to this file that I left out, but we'll look into it with more detail coming up. The code here may be a little daunting, but let's sort it out.

Every form is made up of widgets. What's a widget? A widget is anything that produces html. More specifically, each widget represents each form field of our form. When we echo the form, it simply spits out all of the widgets inside that form. It's that simple, it really is.

To help "hold" the widgets that are part of a form, we have a sfWidgetFormSchema class. Now, that's much more complicated than it sounds like. In essence, all that class does is act as an array that holds the widgets of the form. It really is that simple. By calling the setWidgets method, we basically put all of the widgets (in this case there's 4 of them) inside our sfWidgetFormSchema class (which acts just like an array!). The setWidgets method handles interaction with sfWidgetFormSchema so we don't have to. But, it's honestly very simple - this basically just allows us to write less.

    $this->setWidgets(array(
      'id'               => new sfWidgetFormInputHidden(),
      'first_name'       => new sfWidgetFormInput(),
      'last_name'        => new sfWidgetFormInput(),
      'email'            => new sfWidgetFormInput(),
      'website'          => new sfWidgetFormInput(),
      'birthday'         => new sfWidgetFormDate(),
      'years_experience' => new sfWidgetFormInput(),
    ));

When a form is created (in our case, via $this->form = new AuthorForm), the setup() method above is ran. It's first job is to give this empty form some real-life fields. The setWidgets does this job. Remember that the sfWidgets just puts our widgets into our array-like object, sfWidgetFormSchema.

This part means: "I want to have a hidden field, whose name is 'id'"
'id'               => new sfWidgetFormInputHidden(),

And this part means: "I want to have an input field whose name should be 'first_name'"
'first_name'       => new sfWidgetFormInput(),

That's the beauty of it. Symfony is setting up your form to automatically have all the necessary fields AND have them use the correct form element. There are more form elements than the above, and we'll get to a lot of them in the coming days.

Just as forms contain widgets, forms also contain validators with the same name as their associated widgets. Once again, we have an sfValidatorSchema - fancy word for the array that holds the validators of our form. In this case, the method setValidators takes care of populating our sfValidatorSchema object.

    $this->setValidators(array(
      'id'               => new sfValidatorInteger(array('required' => false)),
      'first_name'       => new sfValidatorString(array('max_length' => 30, 'required' => false)),
      'last_name'        => new sfValidatorString(array('max_length' => 30, 'required' => false)),
      'email'            => new sfValidatorString(array('max_length' => 75, 'required' => false)),
      'website'          => new sfValidatorString(array('max_length' => 100, 'required' => false)),
      'birthday'         => new sfValidatorDate(array('required' => false)),
      'years_experience' => new sfValidatorInteger(array('required' => false)),
    ));

Now that we've got all our necessary fields, we want to setup some validation on those fields. This is just as simple:

This part says: "I'm going to be accepting a value called 'first_name' - it's max length should be 30 and it's not required
'first_name'       => new sfValidatorString(array('max_length' => 30, 'required' => false)),

We're going to look into MANY more things that validators can do in the coming days. One last piece of important code that handles the format of the "name" attribute in the outputted html

For the "first_name" field, this tells the form to output with a name of "author[first_name]"
$this->widgetSchema->setNameFormat('author[%s]');

Let's quickly revisit our action before the day is up look it over again.

public function executeEditAuthor()
  {
  	$this->form = new AuthorForm();
  	if ($this->getRequest()->isMethod('post'))
  	{
  		$this->form->bind($this->getRequestParameter('author'));
  	}
  }

Now this makes more sense. When we create the form, it automatically sets up all the basic fields and validators. What about the bind() method? The bind method is quite powerful. It inputs the data that was submitted (store in $this->getRequestParameter('author') in this case) and "binds" it with the form object. This has many effects:

  1. it validates the data and throws errors where necessary
  2. it (in the upcoming days) allows you to "save" the form, which will save the new data to the model object
  3. if it has to print the form out again, it will do so with the new "binded" data.
 

That's it for today - what a marathon. Tomorrow we'll greatly enhance our project by adding all sorts of validation, modifying our widgets and actually saving our new authors!

Thanks for the shares!
  • StumbleUpon
  • Sphinn
  • del.icio.us
  • Facebook
  • TwitThis
  • Google
  • Reddit
  • Digg
  • MisterWong
Posted by fsdf on 2011-07-19
sdf