12.18.07

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

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

Now that we've got a good base down for how widgets and validators work, we're going to extend our application. Today's work will be to add articles for our author, and we'll see lot's more new widgets and validators. Let's review the original schema.

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
    bio:                      longvarchar
  article:
    _attributes:          { phpName: Article }
    id:
    author_id:
    body:                    longvarchar
    file_attachment:   varchar(255)

We've handled the author, now onto the articles. Currently, after we add an author, we clear the form so the user can add another author. Let's change that behavior; when a user creates an author, let's forward him/her to a new form where she can add articles for that 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->redirect('/articles/editArticle?id='.$this->form->getObject()->getId());
  		}
  	}	
  }

Here's our new action to handle the articles:

  public function executeEditArticle()
  {  	
  	$this->form = new ArticleForm();
  	
  	if ($this->getRequest()->isMethod('post'))
  	{
  		$this->form->bind($this->getRequestParameter('article'));
  		if ($this->form->isValid())
  		{
  			$this->form->updateObject();
  			$this->form->save();
  			$this->getUser()->setFlash('message', 'article saved successfully');
  			
  			$this->form = new ArticleForm();
  		}
  	} else {
  		$this->author = AuthorPeer::retrieveByPk($this->getRequestParameter('id'));
  		$this->forwardUnless($this->author, 'articles', 'editAuthor');
  		// Let's set the author_id field to be the given author by default
  		$this->form->setDefault('author_id', $this->author->getId());
  	}
  	
  }
In the template editArticleSuccess:
<?php if ($sf_user->hasFlash('message')): ?>
	<div>
		<?php echo $sf_user->getFlash('message'); ?>
	</div>
<?php endif; ?>

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

What have we done? The exact same thing as before. In fact, it's so repetitive that I might consider having a form partial or even consolidating the work in the action. If you try to load your form, you will get an error, there's one more piece we're missing:

In the model class Author:

class Author extends BaseAuthor
{
  ...
	public function __toString()
	{
		return $this->getFirstName().' '.$this->getLastName();
	}
  ...
}

The relationship select field echoes the object itself to get it's text for the select options. To handle this, we can simply add our __toString() method.

So this time around, setting up a new form was pretty easy. And that's the beauty of the new system. Our form still needs some work, but that won't take long either. Browse to the ArticleForm.class.php file so we can tidy up our form.

public function configure()
  {
  	$this->widgetSchema['file_attachment'] = new sfWidgetFormInputFile();
  	
  	$this->validatorSchema['body']->setOption('required',true);
  	$this->validatorSchema['file_attachment'] = new sfValidatorFile();
  	$this->validatorSchema['file_attachment']->setOption('mime_types', array('application/pdf'));
  	$this->validatorSchema['file_attachment']->setOption('max_size', 1024);
  }

This does several things. First, we change our file_attachment field from a text input to a file input. Remember, now that we're manually printing out the form tag, we'll need to add the multipart enctype also:

...
<form action="<?php echo url_for('/articles/editArticle'); ?>" method="post" enctype="multipart/form-data">
...

Next, we have our body field be required. Finally, we add some file validation via the sfValidatorFile validator. First, we set the mime type to expect application/pdf. Obviously, this can take an array of mime types. I won't get into it now, but you can also pass a string value if it refers to "mime_category." The only mime_category by default is called web_images. In other words, if you set 'mime_types' to 'web_images', it would automatically validate to all the normal mime types for images. We also set a maximum size of 1024 bytes. I made it real low initially so that we could see it faily.

We'll need to make a few modifications to our action.

...
  		$this->form->bind($this->getRequestParameter('article'), $this->getRequest()->getFiles('article'));
  		if ($this->form->isValid())
  		{
  			$file = $this->form->getValue('file_attachment');
  			$path = sfConfig::get('sf_web_dir').'/uploads/'.rand(1, 1000);
				$extension = $file->getExtension($file->getOriginalExtension());
				$file->save($path.'.'.$extension);
  			
  			$this->form->updateObject();
  			$this->form->save();
  			$this->getUser()->setFlash('message', 'article saved successfully');
  			
  			$this->form = new ArticleForm();
  		}
...

Now, let's play with our form. If we try to upload a file too big (1024 bytes), we get a nice error. If we upload a file that's not a pdf, we also get a nice error message. Finally, if we increase our max size to something normal and upload a pdf, we should have succes. I should note, I'm actually NOT having success. The above code leaves me with a blank screen. I'm not sure why - and it's late, so I'll leave it. This may just be a temporary bug, the code I have is still very young.

Ok, that's it for today. There was a lot of functionality added, but with fewer surprises and difficulty. Apart from uploading the file, we added an entire form today with almost no code at all. Tomorrow I'll run through ALL the widgets and validators and how to use them. Finally, day 7 will bring us to decorators. Decorators are the things that decide how your form will look. The default decorator, as you've probably noticed, uses a table format to print out your form. On day 7 we'll try to write our own decorator for tableless forms. Stay tuned.

Thanks for the shares!
  • StumbleUpon
  • Sphinn
  • del.icio.us
  • Facebook
  • TwitThis
  • Google
  • Reddit
  • Digg
  • MisterWong