12.15.07

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

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).

Welcome to day 3 or the introduction to the new form system in Symfony 1.1. Yesterday we looked into the automatic generation of the Propel Form objects that fuel the forms for your objects. Today, let's clean up those forms and make them useful.

Yesterday, while we got to the point where we could submit our form (with validation), we didn't actually save our object to the database. For now, we'll make the form behave in the following way:

  • If the forms submits and fails validation, the object is not saved and the form is returned with the data the user entered
  • If the validation passes, then we'll save our object to the database and return a blank form so that we can enter in another author.

public function executeEditAuthor()
  {
  	$this->form = new AuthorForm();
  	if ($this->getRequest()->isMethod('post'))
  	{
  		$this->form->bind($this->getRequestParameter('author'));
  		if ($this->form->isValid())
  		{
  			$this->form->updateObject();
  			$this->form->save();
  			$this->getUser()->setFlash('message', 'author saved successfully');
  			$this->form = new AuthorForm();
  		}
  	}
  }

We've added a few things above. We remember from yesterday that we called the bind() method to connect the submitted data with the form object. Today, we call the new method isValid() which return true if validation passed, false otherwise. Once we know that our form is valid, we update and save the object.

// This transfers the validated data from our form object to the AuthorForm object that is associated with our form
$this->form->updateObject();
// This performs the save on the propel object, effectively saving the model to the database
$this->form->save();
// Let's setup a flash message to display. Notice that this is no longer accessible via $this->setFlash() - another Symfony 1.1 change
$this->getUser()->setFlash('message', 'author saved successfully');
// Create a new, blank author form so that we have a blank form with which we can make more authors
$this->form = new AuthorForm();

Notice that if the form doesn't validate, we simply return the form as before - complete with validation errors and the user's data. Inside the template, add this to the top so that we print out the flash message:

<?php if ($sf_user->hasFlash('message')): ?>
	<div>
		<?php echo $sf_user->getFlash('message'); ?>
	</div>
<?php endif; ?>

Try it out, assuming you enter valid data - and our validation is pretty weak at this point - the form will save your author to the database, print out the success message and return a blank form.

What's next? Well, let's fix up our form a bit. For starters, the birthday field doesn't work right, as the years only extend in the 5 year radius of the current year. Let's change this up. For changes to our form that aren't going to be action-specific (we'll ALWAYS want our birthday field to have years that extend further back), we make our changes in the AuthorForm.class.php file of the lib/form directory. Remember, the configure() function is called when a form is created - so we can make our changes right in there.

public function configure()
	{
		$years = range(date('Y') - 75, date('Y') - 10);
		$this->widgetSchema['birthday']->setOption('years', array_combine($years, $years));
	}

Let's break it down. First, we're wanting to modify our birthday widget, so we need to know how to get at it. Fortunately, since we're in a subclass of sfForm, we can get at our widgetSchema quite easily via $this->widgetSchema. Remember, the widget schema is essentially the array that holds our widgets. So, to get the widget we want, we only have to do this:

// we get to our birthday widget by simply calling the name of our field 'birthday' as the key of our widgetSchema "array"
$birthday_widget = $this->widgetSchema['birthday'];

Now that we know how to get to our widget, let's change the year range. Let's back up for a quick moment and explain widget options. The customization of a particular widget is done by setting certain "options" for that widget. Each widget has different options, and when the widget documentation is completed, you'll be able to easily see what options you can customize for each widget. In this case, the sfWidgetFormDate widget - which is responsible for our birthday widget - has several options:

format       - the order and format of how the 3 select widgets are formatted, '%month%/%day%/%year%' by default
days         - array of the days to display
months       - array of the months to display
year         - array of the years to display
can_be_empty - whether or not to print the "blank" values at the top of each date select box - true by default
empty_values - array specifying what to put as the "blank" value if can_be_empty is true

To modify the years on our birthday widget, we just need to modify the 'year' option of the widget.

//sets up a year range array - I'll have our birthday widget print years as far back as 90 from today, and as recent as 10 from today
$years = range(date('Y') - 90, date('Y') - 10);
//we use the setOption() function to change the value of our widget's year option
$this->widgetSchema['birthday']->setOption('years', array_combine($years, $years));

Try it out. We now have a birthday widget with more appropriate years.

What else should we change? Well, the years_experience field seems a little long. The field will be accepting values like "5" "15", and we'll want to shorten up this field a bit. Back to our configure() function - let's add the following after our existing code:

$this->widgetSchema['years_experience']->setAttribute('size', 3);

Things are getting easy now. First, we get at our years_experience widget by using its name as the array key of our $this->widgetSchema class variable. Then we use the setAttribute function to set size=3. If you didn't guess it already, the "attributes" part of a widget is all the stuff that's printed inside the html entity (e.g. class, style, onclick etc). For any widget, you can call the setAttribute function, and Symfony will automatically print it out with the form. In this example, we now have a size="3" portion of our form - making it an appropriate size for accepting years.

One more quick change to that years_experience field. Even though the field automatically validates to only accept a number (since we defined the field to be of type integer), let's make it more clear to the user that we're expecting a number and not a word like "ten". To do this, we'll set the default value of this field to "0".

class AuthorForm extends BaseAuthorForm
{
  public function configure()
  {
  ...
  $this->setDefault('years_experience', 0);
  ...

That's it. Now, unless our form has a binded value for 'years_experience', the form will start with the value 0.

There's lots more we can do with forms. There's many widgets we haven't seen yet, and we haven't even started true validation of our form. Tomorrow we'll look into doing validation that will make our form truly useful. In the upcoming days, we'll see more form widgets, look into how relationships are handled, study the form decorator and more.

Thanks for the shares!
  • StumbleUpon
  • Sphinn
  • del.icio.us
  • Facebook
  • TwitThis
  • Google
  • Reddit
  • Digg
  • MisterWong
Posted by ?????? on 2010-10-04
fgdg
Posted by gf on 2011-08-29
fsdfs