Code snippets for symfony 1.x

Navigation

Refine Tags

Snippets tagged "widget"

Form widget to allow using the values of a column as labels for the optgroup HTML element

Form widget for symfony 1.4 extending sfWidgetFormDoctrineChoice or sfWidgetFormPropelChoice. The new feature is the possibility to indicate a column which values will be used to group all the fields with that column value within optgroup HTML elements.

You can get the code in this link.

by cram1010 on 2012-07-04, tagged optgroup  sfwidgetformdoctrinechoice  sfwidgetformpropelchoice  widget 

Validator and widget for fuzzy dates

If I understand correctly, ISO-8601 allows for dates represented as YYYY, YYYY-MM, and YYYY-MM-DD, among others. I did not see the possibility of supporting these date formats with the built-in widgets and validators, so I wrote some.

class ynWidgetFormDateFuzzy extends sfWidgetFormDate
{
  public function render($name, $value = null, $attributes = array(), $errors = array())
  {
    // convert value to an array
    $default = array('year' => null, 'month' => null, 'day' => null);
    if (is_array($value))
    {
      $value = array_merge($default, $value);
    }
    else if ( preg_match( '/^(\d+)$/', $value, $matches ) )
    {
      $value = array(
        'year'  => $matches[1],
        'month' => '',
        'day'   => '',
      );
    }
    else if ( preg_match( '/^(\d+)-(\d+)$/', $value, $matches ) )
    {
      $value = array(
        'year'  => $matches[1],
        'month' => $matches[2],
        'day'   => '',
      );
    }
    else
    {
      $value = (string) $value == (string) (integer) $value ? (integer) $value : strtotime($value);
      if (false === $value)
      {
        $value = $default;
      }
      else
      {
        $value = array('year' => date('Y', $value), 'month' => date('n', $value), 'day' => date('j', $value));
      }
    }
 
    $date = array();
    $emptyValues = $this->getOption('empty_values');
 
    $date['%day%'] = $this->renderDayWidget(
      $name.'[day]',
      $value['day'] ? (int) $value['day'] : '',
      array(
        'choices' => array('' => $emptyValues['day']) + $this->getOption('days'),
        'id_format' => $this->getOption('id_format')
      ),
      array_merge($this->attributes, $attributes)
    );
 
    $date['%month%'] = $this->renderMonthWidget(
      $name.'[month]',
      $value['month'] ? (int) $value['month'] : '',
      array(
        'choices' => array('' => $emptyValues['month']) + $this->getOption('months'),
        'id_format' => $this->getOption('id_format')
      ),
      array_merge($this->attributes, $attributes)
    );
 
    $date['%year%'] = $this->renderYearWidget($name.'[year]', $value['year'], array('choices' => $this->getOption('can_be_empty') ? array('' => $emptyValues['year']) + $this->getOption('years') : $this->getOption('years'), 'id_format' => $this->getOption('id_format')), array_merge($this->attributes, $attributes));
 
    return strtr($this->getOption('format'), $date);
  }
}
 
 
class ynValidatorDateFuzzy extends sfValidatorBase
{
  protected function configure($options = array(), $messages = array())
  {
    $this->addMessage('max', 'The date must be before %max%.');
    $this->addMessage('min', 'The date must be after %min%.');
 
    $this->addOption('min', null);
    $this->addOption('max', null);
  }
 
  protected function doClean($value)
  {
    if ( ! is_array( $value ) ) {
      throw new sfValidatorError($this, 'bad_format');
    }
 
    // convert array to date string
    if (is_array($value))
    {
      $value = $this->convertDateArrayToString($value);
    }
 
    if ($max = $this->getOption('max')) {
      $date_max = new DateTime( $max );
 
      if ( $this->lowestPossible( $value )->format('Ymd') > $date_max->format('Ymd') ) {
        throw new sfValidatorError($this, 'max', array('value' => $value, 'max' => 'out of range'));
      }
    }
 
    if ($min = $this->getOption('min')) {
      $date_min = new DateTime( $min );
 
      if ( $this->highestPossible( $value )->format('Ymd') < $date_min->format('Ymd') ) {
        throw new sfValidatorError($this, 'max', array('value' => $value, 'min' => 'out of range'));
      }
    }
 
    return $value;
  }
 
  protected function convertDateArrayToString($value)
  {
    // all elements must be empty or a number
    foreach (array('year', 'month', 'day', 'hour', 'minute', 'second') as $key)
    {
      if (isset($value[$key]) && !preg_match('#^\d+$#', $value[$key]) && !empty($value[$key]))
      {
        throw new sfValidatorError($this, 'invalid', array('value' => $value));
      }
    }
 
    // check empty value correspondence
 
    if (
      ! $value['year'] && ! $value['month'] && ! $value['day']
    ) {
      return $this->getEmptyValue();
    }
    else if (
      ! $value['year']
      && ($value['month'] || $value['day'])
    ) {
      throw new sfValidatorError($this, 'invalid', array('value' => $value));
    }
    else if (
      $value['year'] && ! $value['month'] && $value['day']
    ) {
      throw new sfValidatorError($this, 'invalid', array('value' => $value));
    }
 
    if ( $value['month'] && ! in_array( (int) $value['month'], range(1,12) ) ) {
      throw new sfValidatorError($this, 'invalid', array('value' => $value));
    }
 
    if ( $value['day'] && ! in_array( (int) $value['day'], range(1,31) ) ) {
      throw new sfValidatorError($this, 'invalid', array('value' => $value));
    }
 
    $clean = '';
 
    if ( $value['year'] ) {
      $clean .= sprintf( '%04d', intval( $value['year'] ) );
    }
 
    if ( $value['month'] ) {
      $clean .= sprintf( '-%02d', intval( $value['month'] ) );
    }
 
    if ( $value['day'] ) {
      $clean .= sprintf( '-%02d', intval( $value['day'] ) );
    }
 
    return $clean;
  }
 
  protected function highestPossible( $date )
  {
    if ( preg_match( '/^\d+$/', $date, $matches ) ) {
      return new DateTime( $date . '-12-31' );
    }
    else if ( preg_match( '/^\d+-(\d+)$/', $date, $matches ) ) {
      switch ( $matches[1] ) {
        case '2':
          return new DateTime( $date . '-28' );
        case '1':
        case '3':
        case '5':
        case '7':
        case '8':
        case '10':
        case '12':
          return new DateTime( $date . '-31' );
        default:
          return new DateTime( $date . '-30' );
      }
    }
    else {
      return new DateTime( $date );
    }
  }
 
  protected function lowestPossible( $date )
  {
    if ( preg_match( '/^\d+$/', $date, $matches ) ) {
      return new DateTime( $date . '-01-01' );
    }
    else if ( preg_match( '/^\d+-(\d+)$/', $date, $matches ) ) {
      return new DateTime( $date . '-01' );
    }
    else {
      return new DateTime( $date );
    }
  }
}
 
by yitznewton on 2011-02-16, tagged date  validator  widget 

Converting the form fields in paragraphs with a decorator

I created a reusable object, where I want to display select fields blocked, because this way I display only the value in DIVs, adding an input hidden (or not).

Not resolved the idea of a print version because only a few fields have the option renderer_class.

If someone yells at me to think of something :)

Decorator

<?php
 
class g1mrWidgetFormPrint extends sfWidgetFormInputHidden
{
 
  protected function configure($options = array(), $attributes = array())
  {
    $this->addOption('choices');
    $this->addOption('template');
    $this->addOption('renderHidden', true);
    $this->addOption('class', 'form_print');
    $this->addOption('break', "<br>\n");
    $this->addOption('print_template', <<<EOF
<div class="%class%">
    %value%
    %hiddenField%
</div>
EOF
);
   parent::configure($options, $attributes);
  }
 
  /**
   * Renders the widget.
   *
   * @param  string $name        The element name
   * @param  string $value       The value selected in this widget
   * @param  array  $attributes  An array of HTML attributes to be merged with the default HTML attributes
   * @param  array  $errors      An array of errors for the field
   *
   * @return string An HTML tag string
   *
   * @see sfWidgetForm
   */
  public function render($name, $value = null, $attributes = array(), $errors = array())
  {
    if (is_null($value))
    {
      $value = '';
    }
 
    $choices = $this->getOption('choices');
    if ($choices instanceof sfCallable)
    {
      $choices = $choices->call();
    }
 
      if (is_array($choices)) 
      {  
        $vc = $value;
        if (!is_array($vc))
        {
         $vc= array($vc);
        }
        foreach ($choices as $k => $array)
        {
          if (is_array($array))
          {
            foreach($array as $key => $o)
            {
              if (in_array(strval($key), $vc))
              {
                 $associated[$key] = $o;
              }
            }  
          }
          else
          {
            foreach($choices as $key => $o)
            {
              if (in_array(strval($key), $vc))
              {
                 $associated[$key] = $o;
              }
            }
 
          }
        } 
      }
      else
      {
        $associated = $value;
      } 
 
    $hiddenField = '';
    if($this->getOption('renderHidden'))
    {
        $hiddenField = parent::render($name, $value, $attributes, $errors);
    }
 
    return strtr($this->getOption('print_template'), array(
      '%class%'              => $this->getOption('class'),
      '%value%'              => $this->processValue($associated),
      '%hiddenField%'        => $hiddenField
    ));
  }
 
  public function processValue($x)
  {
    if(is_array($x))
    {
      return implode($this->getOption('break'),$x); 
    }
    else
    {
      return $x;
    } 
  }
 
}
 

Form

//...
 
    public function setPrintable($field, $renderHidden = true)
    {
        if($this->widgetSchema[$field]->hasOption('renderer_class'))
        {
            $this->widgetSchema[$field]->setOption('renderer_class', 'g1mrWidgetFormPrint');
            $this->widgetSchema[$field]->setOption('renderer_options', array('renderHidden' => $renderHidden));
        }
    }
 
 
    public function setPrintables($renderHidden = true)
    {
        foreach($this->getFormFieldSchema()->getWidget()->getFields() as $key => $object)
        {
            $this->setPrintable($key, $renderHidden);
        }  
    }
 
//..    
 

Controler

//...
  public function executeEdit(sfWebRequest $request)
  {
    $this->pedido = $this->getRoute()->getObject();
    $this->form = $this->configuration->getForm($this->pedido);
    if($this->pedido->getIsSent() == 1)
    {
      $this->form->setPrintables(); 
    }
  }
//...
 
by Gilmar Pupo on 2010-10-02, tagged decorator  forms  widget 

symfony 1.x form framework: how to add extra fields into forms?

Assumption: there is a UserMessage class and the related form UserMessageForm. Both were generated by Doctrine based on a schema.yml declaration. Problem: one wishes to you add input fields into this form.

Approach: one can add any pairs of widgets/validators which have no direct relationship to the presented model. Doing so, one need to care about two main aspects:

  1. configuring the form with the extra field
  2. processing of the submitted value (if any) at the proper moment without disturbing the big rest of form functionality.

For step 1, configuring the form: add the widget and the validator to the form.

In .../UserMessageForm.class.php:
 
  public function configure()
  {
    $this->widgetSchema['comment_for_log'] = new sfWidgetFormInputText();
    $this->validatorSchema['comment_for_log'] = new sfValidatorString();
  }
 

For step 2 processing user input after submission: process the value and act accordingly. Typically this happens only after validation did succeed (i.e. not validation errors did occur).

For this purpose, a dedicated method for each field gets called (in case that the method exists). The following example checks whether a non-empty text was entered by the user. If so, it does store a log entry. The function in this example returns a boolean 'false' value to tell symfony not to process this value further.

In .../UserMessageForm.class.php:
 
  public function updateCommentForLogColumn($value)  // constructed method name with camelized field name
  {
    if (! empty($value)) {     // e.g. only if the user entered a comment (btw this is the cleaned value)
      // do whatever you need to do based on the contents of this field.
      // e.g. we create and save a log entry as follows:
      $logEntry = new AppLogEntry();    // assume we have this sort of logging feature
      $logEntry->setComment($value);    // the comment entered by the user
      $logEntry->save();
    }
    return false;  // tell symfony that we have cared about this value, so no further processing will occur
  }
}
 

Basically that's it.

An alternative to the dedicated updateYourSpecialFieldColumn() method could be to override the doUpdate($values) method in the form, allowing to do more general things because there is access to all submitted values (that's basically the only difference between the two approaches, though). I personally prefer the dedicated method whenever possible, as it doesn't disturb any other logic that may (at the same time, or sometimes later) implemented in the same form.

Another good thing is that since all this code is all packed in a Doctrine transaction, so either both the form object (UserMessage) will be saved as well as the log (LogEntry) be generated, or none of the two objects be altered/created at all.

(15.9.2010/SCH)

by Raphael Schumacher on 2010-09-15, tagged doctrine  fields  form  symfony14  validator  widget 

Globally format forms

If you are like me, you see the nice Forms chapter, but when you follow the instructions, your forms don't look nice like the examples.

I gleaned the following techniques from various forum posts. I take NO credit for this, it's heavily snatched from other sources.

First, create your format class, which lays out all forms. I use tables in my design, but you can find examples of div based versions elsewhere, )such as here).

/lib/sfWidgetFormSchemaFormatterCustom.class.php:

<?php
class sfWidgetFormSchemaFormatterCustom extends sfWidgetFormSchemaFormatterTable
{
  protected
    $rowFormat       = "<tr class='formrow'>\n  <th class='formheader'>%label%:</th>\n  <td class='forminput'>%error%%field%%help%%hidden_fields%</td>\n</tr>\n",
    $errorRowFormat  = "<tr><td colspan='formerror' colspan=\"2\">\n%errors%</td></tr>\n",
    $helpFormat      = '<br />%help%',
    $decoratorFormat = "<table class='form'>\n  %content%</table>";
}
 

The key here is to insert into each HTML element the global classes to format your code. Above, you can see I added the 'form' class to the table, 'formheader' to the header rows, and 'forminput' for each field.

Next, you need to globally enable this formatter for all forms:

/config/ProjectConfiguration.class.php:

<?php
 
require_once dirname(dirname(__FILE__)).'/lib/vendor/symfony/lib/autoload/sfCoreAutoload.class.php';
sfCoreAutoload::register();
 
class ProjectConfiguration extends sfProjectConfiguration
{
  public function setup()
  {
    // for compatibility / remove and enable only the plugins you want
    $this->enableAllPluginsExcept(array('sfDoctrinePlugin', 'sfCompat10Plugin'));
    sfWidgetFormSchema::setDefaultFormFormatterName('Custom');
  }
}
 

Naturally, make sure you manage your plugins appropriately. They key line is the:

sfWidgetFormSchema::setDefaultFormFormatterName('Custom');
 

Now, customize your CSS files:

.form {
  padding-top:15px;
}
.formrow {
 
}
.formheader {
  text-align: right;
  font-weight: bold;
  padding-right: 10px;
  vertical-align: middle;
}
td.forminput input {
  width: 300px;
}
.formerror {
 
}
 

Hope this helps others.

by Mike Crowe on 2009-10-24, tagged form  formatter  sfform  widget 

unset all not needed form widgets

Common Probem.

You have a large and small form. For example a contact form.

Form orginal includes 50 fields and your small form is extend form the orginal form and includes 15 fields. normaly you have to unset very 35 field in the small one.

But you could also write this lines:

class ContactSimpleForm extends ContactForm
{
  public function configure()
  {
 
    parent::configure();
 
    $this->wantedFields = array(
        'firstname',
        'lastname',
    );
 
    foreach ($this as $fieldName => $widget){
        if (!in_array($fieldName, $this->wantedFields)){
            unset($this->widgetSchema[$fieldName]);
            unset($this->validatorSchema[$fieldName]);
        }
    }
}
 
by excessive demon on 2009-09-18, tagged form  unset  validator  widget 
(3 comments)

Many-to-many and one-to-many autocompletition

In here I will be using sfWidgetFormJQueryAutocompleterMany ( http://forum.symfony-project.org/index.php/m/77584/#msg_77462 ) to ease the relating of an object to other objects (hence keywords/tags/recepients... management becomes heaven based on non religious views).

The ORM used is Doctrine.

For this to make any sense I will tie the snippet to a quick tutorial like explanation. In the blog tutorials spirit I will now implement the widget into blog post tags management.

Getting the widget

Currently you can download it from the forum thread linked above. If let it will become part of sfFormExtraPlugin ( http://www.symfony-project.org/plugins/sfFormExtraPlugin ). If you find any annoyances / bugs just let me know.

Scheme

tableName: blog_post
 
  actAs:
    Timestampable:
    Sluggable: { fields: [title]}
 
  options:
    collate: utf8_unicode_ci
    charset: utf8
 
  columns:
    id:
      type: integer
      primary: true
      autoincrement: true
 
    title:
      type: string(255)
      notnull: true
 
    content:
      type: clob
      notnull: true
 
    excerpt:
      type: clob
      notnull: true
 
  relations:
    Tags:
      class: Tag
      local: blog_post_id
      foreign: tag_id
      type: many
      foreignType: many
      foreignAlias: BlogPosts
      refClass: BlogPostTag
 
#------------------- Connection for publication -> tags  --------------------
BlogPostTag:
 
  tableName: blog_post_tag
 
  options:
    collate: utf8_unicode_ci
    charset: utf8
  columns:
    blog_post_id:
      type: integer
      primary: true
    tag_id:
      type: integer
      primary: true
 
#------------------- Tag --------------------
Tag:
 
  tableName: tag
 
  options:
    collate: utf8_unicode_ci
    charset: utf8
 
  actAs:
    Sluggable: { fields: [name]}
 
  columns:
    id:
      type: integer
      primary: true
      autoincrement: true
    name:
      type:  string(255)
 

For the applications depending on i18n.

tableName: blog_post
 
  actAs:
    Timestampable:
    I18n:
      fields: [title, content, excerpt]
      actAs:
        Sluggable: { fields: [title], uniqueBy: [lang, title] }
 
  options:
    collate: utf8_unicode_ci
    charset: utf8
 
  columns:
    id:
      type: integer
      primary: true
      autoincrement: true
 
    title:
      type: string(255)
      notnull: true
 
    content:
      type: clob
      notnull: true
 
    excerpt:
      type: clob
      notnull: true
 
  relations:
    Tags:
      class: Tag
      local: blog_post_id
      foreign: tag_id
      type: many
      foreignType: many
      foreignAlias: BlogPosts
      refClass: BlogPostTag
 
#------------------- Connection for publication -> tags  --------------------
BlogPostTag:
 
  tableName: blog_post_tag
 
  options:
    collate: utf8_unicode_ci
    charset: utf8
  columns:
    blog_post_id:
      type: integer
      primary: true
    tag_id:
      type: integer
      primary: true
 
#------------------- Tag --------------------
Tag:
 
  tableName: tag
 
  options:
    collate: utf8_unicode_ci
    charset: utf8
 
  actAs:
    I18n:
      fields: [name]
      actAs:
        Sluggable: { fields: [name], uniqueBy: [lang, name] }
 
  columns:
    id:
      type: integer
      primary: true
      autoincrement: true
    name:
      type:  string(255)
 

Adding the widget to a form

Similar, if not identical, use as sfWidgetFormJQueryAutocompleter, applying a renderer class by doing so: /lib/form/doctrine/BlogPostForm.class.php

class BlogPostForm extends BaseBlogPostForm
{
    public function configure()
    {
        $autocompleteWidget = new sfWidgetFormChoice(array(
          'multiple'         => true,
          'choices'          => $this->getObject()->getTags(),
          'renderer_class'   => 'sfWidgetFormJQueryAutocompleterMany',
          'renderer_options' => array(
            'config' => '{
                json_url: "'.sfContext::getInstance()->getController()->genUrl('tag/autocomplete').'",
                json_cache: true,
                filter_hide: true,
                filter_selected: true,
                maxshownitems: 8        
              }')
        ));
        $this->widgetSchema['tags_list'] = $autocompleteWidget;
    }
}
 

If you support more than one languages / i18n use this set up instead:

class BlogPostForm extends BaseBlogPostForm
{
    public function configure()
    {
        $autocompleteWidget = new sfWidgetFormChoice(array(
          'multiple'         => true,
          'choices'          => $this->getObject()->getTags(),
          'renderer_class'   => 'sfWidgetFormJQueryAutocompleterMany',
          'renderer_options' => array(
            'config' => '{
                json_url: "'.sfContext::getInstance()->getController()->genUrl('tag/autocomplete').'",
                json_cache: true,
                filter_hide: true,
                culture: "'.$this->getCulture().'",
                filter_selected: true,
                maxshownitems: 8        
              }')
        ));
        $this->widgetSchema['tags_list'] = $autocompleteWidget;
    }
    protected function getCulture() {
        return isset($this->options['culture']) ? $this->options['culture'] : sfContext::getInstance()->getUser()->getCulture();
    }
}
 

While browsing a website you could have opened several windows/tabs and change the language in one of them. Then the session for the culture is changed. If the page with the autocompleter is not the one with the changed language the new suggestions will be made for the new language which is wrong. To maintain persistent culture before form submission use the method provided plus the configuration option for the javascript of the widget "culture".

Wiring to an action

Our module is Tag, our action: autocomplet stating this: /modules/tag/actions/actions.class.php

public function executeAutocomplete(sfWebRequest $request)
    {
      $this->getResponse()->setHttpHeader('Content-Type','application/json; charset=utf-8');
 
      $tags = Tag::retrieveSuggestions($request->getParameter('q'), $request->getParameter('l'),$request->getParameter('c'));
 
      return $this->renderText(json_encode($tags));
    }
 

Retrieve results

Our model method used here is retrieveSuggestions : /lib/model/doctrine/Tag.class.php

static public function retrieveSuggestions($q, $l,$c)
  {
  $tags = Doctrine_Query::create()
    ->select('t.*,LOCATE(:token_raw,t.name) AS index')
    ->from('Tag t')
    ->where('t.name LIKE :token')
    ->orderBy('index')
    ->limit($l)
    ->execute(array('token_raw' => $q , 'token' => '%'.$q.'%'));
 
    $jsonTags = array();
    foreach ($tags as $tag)
    {
      $jsonTags[] =array('caption' => (string) $tag->Translation[$culture]->name,'value'=> $tag->getPrimaryKey()) ;
    }
    return $jsonTags;
  }
 

And again for the i18ned applications:

static public function retrieveSuggestions($q, $l, $c)
  {
  $culture = ($c!=null) ? $c : sfContext::getInstance()->getUser()->getCulture();
  $tags = Doctrine_Query::create()
    ->select('t.id,tr.name,LOCATE(:token_raw,tr.name) AS index')
    ->from('Tag t')
    ->leftJoin('t.Translation tr')
    ->where('tr.lang = :culture AND tr.name LIKE :token')
    ->orderBy('index')
    ->limit($l)
    ->execute(array('culture' => $culture,'token_raw' => $q , 'token' => '%'.$q.'%'));
 
    $jsonTags = array();
    foreach ($tags as $tag)
    {
      $jsonTags[] =array('caption' => (string) $tag->Translation[$culture]->name,'value'=> $tag->getPrimaryKey()) ;
    }
    return $jsonTags;
  }
 

The important part here is that you return array with pair of keys:

If you omit key names or use numerical keys it won't work.

The beloved end.

by Anton Stoychev on 2009-04-30, tagged autocomplete  manytomany  onetomany  widget 

Login form in Symfony 1.1, with the new form system

Symfony 1.1 comes with a complete new form system. It works completely according to the MVC draft:

Make sure you have a running Symfony 1.1 based project and application and modules. In this example I build the form inside the myModule module and myLogin action.

My form makes use of i18n, which is in my case autoloaded in settings.yml.

This tutorial uses Symfony 1.1 beta4 and RC1. There are a little important changes with respect to beta3, which I don't cover.

I also expect you to have practical knowledge and a little bit experience with Symfony as system. I will not explain how you i18n implements or modules shield with security.yml.

The form design

The form gets two import fields: username and password. Furthermore is there a hidden field in which the URI comes that the user requested, but got redirected to the loginform (through Symfony’s security.yml and settings.yml). This will be used to go to that URI again after a successful login.

Both imput fields are required I will build a custom validation for a correct username/password check. Also we want to make use of a little new protection feature: CSRF.

The form takes uses i18n for multilinguity, I will use English primarily, but the view has been prepared for other languages.

The start: the action

We will begin with the action which contains the primary control.

/**
 * Executes myLogin action
 *
 * Login functionality.
 *
 * @param void
 * @return void
 * @access public
 */
public function executeMyLogin() {
   // Erase auth data
   $this->getUser()->clearCredentials();
   $this->getUser()->setAuthenticated(FALSE);
 
   // Build login form
   $oForm = new inloggenForm();
 
   if ($this->getRequest()->isMethod('post')) { // When called through POST (form submit)
       $oForm->bind(
           array('username' => $this->getRequest()->getParameter('username'),
                 'password' => $this->getRequest()->getParameter('password'),
                 'referrer' => $this->getRequest()->getParameter('referrer'),
                )
      );
 
      // Save orginal requested location in referrer field
      $oForm->setDefault('referrer', $this->getRequest()->getParameter('referrer'));
      if ($oForm->isValid()) { // When validations OK
          $aValues = $oForm->getValues();
          sfContext::getInstance()->getLogger()->debug($aValues['username']);
 
          // Authentification
          $this->getUser()->setAuthenticated(TRUE);
 
          // To requested page
          $this->redirect($this->getRequest()->getParameter('referrer'));
      }
   } else {
      // Save original requested uri in form
      $oForm->setDefault('referrer', sfContext::getInstance()->getRequest()->getUri());
   }
 
   $this->oForm = $oForm; // form to view
}
 

Logic of authentification is set after POST further. Through the sfForm::bind() method couples the input of the user coupled with the form controller.

The form controller

I made this in myModule/lib/form/inloggenForm.class.php. In the form is defined and coupled with the validations and the formatting.

<?php
class inloggenForm extends sfForm {
    /**
     * Prepare validators
     */
    private function _setValidators() {
        $this->oValGebruikersnaam = new sfValidatorAnd(
                                    array(
                                        new sfValidatorString(array('min_length' => 3, 'max_length' => 50),
                                                              array('min_length' => 'The username should be at least three characters long',
                                                                    'max_length' => 'The username should be fifty characters long at most',
                                                              )
                                        ),
                                        new sfValidatorCallback(array('callback'  => array('cmsLoginValidator', 'execute'),
                                                                      'arguments' => array()
                                                                ),
                                                                array('invalid'  => 'This username/password combination is unknown')
                                        ),
                                    ),
                                    array('required'   => TRUE),
                                    array('required'   => 'The username is mandatory')
        );
        $this->oValWachtwoord = new sfValidatorString(array('required' => TRUE),
                                                      array('required'   => 'The password is mandatory')
        );
        $this->oValReferrer = new sfValidatorString(array('required' => FALSE));
    }
 
    /**
     * Prepare widgets
     */
    private function _setWidgets() {
        $this->oWidGebruikersnaam = new sfWidgetFormInput(array(), array('id' => 'username', 'size' => 25));
        $this->oWidWachtwoord     = new sfWidgetFormInputPassword(array(), array('id' => 'password', 'size' => 10));
        $this->oWidReferrer       = new sfWidgetFormInputHidden(array(), array('id' => 'referrer'));
    }
 
    /**
     * Configure form
     */
    public function configure()     {
        $this->_setValidators();
        $this->_setWidgets();
 
        /*
         * Set validators
         */
        $this->setValidators(array('username' => $this->oValGebruikersnaam,
                                   'password'     => $this->oValWachtwoord,
                                   'referrer'       => $this->oValReferrer,
                             )
        );
 
        /*
         * Set widgets
         */
        $this->setWidgets(array('username' => $this->oWidGebruikersnaam,
                                'password'     => $this->oWidWachtwoord,
                                'referrer'       => $this->oWidReferrer,
                          )
        );
 
        /*
         * Set decorator
         */
        $oDecorator = new sfWidgetFormSchemaFormatterDiv($this->getWidgetSchema());
        $this->getWidgetSchema()->addFormFormatter('div', $oDecorator);
        $this->getWidgetSchema()->setFormFormatterName('div');
        $this->getWidgetSchema()->setHelps(array('username' => 'Please enter your username',
                                                 'password' => 'Please enter your password'
                                           )
        );
 
    }
 
    /**
     * Bind override
     */
    public function bind(array $taintedValues = null, array $taintedFiles = array()) {
        $request = sfContext::getInstance()->getRequest();
 
        if ($request->hasParameter(self::$CSRFFieldName))         {
            $taintedValues[self::$CSRFFieldName] = $request->getParameter(self::$CSRFFieldName);
        }
 
        parent::bind($taintedValues, $taintedFiles);
    }
}
 

There are two private methods:

I overloaded the earlier mentioned bind() method to process the CSRF token internally. This fiels is regulated by sfForm internally, you to do need to worry about it much, only with the actual functionality.

The configure() method contains the functionality. The validators are created and linked to the corresponding fields and also are the widgets. Also, the decorator is defined with I will go into later on.

Validators

In _setValidators() sfValidatorBase objects are made for each field.

The password field check is the simplest, only the required input is checked. The object is of the type sfValidatorString (extend sfValidatorBase) without extra controls, only the required attribute with errortext is specified. In principle, you can use each validator as -empty- container.

The username is a combination; it is required, there are restrictions to the the stringlength and a correct login is checked. The stringcontrole/requirement is checked through sfValidatorString, which is quite simple.

The check for a correct login is done through a special validator, that does a callback to an custom function: sfValidatorCallback. This is explained later.

Since these two validators check the same field , the sfValidatorAnd validator is used that combines several validators. All validators must be satisfied. Of course Symfony also offers sfValidatorOr that checks that at least one underlying validator satisfies.

The custom logincheck

As you can see the callback validator calls to a custom class/method: myLoginValidator::execute().

/**
 * Fiel with myLoginValidator klasse
 *
 * @package -
 */
/**
 * Validation correct username/password
 *
 * @author Jordi Backx (Snowkrash)
 * @copyright Copyright © 2008, Jordi Backx (Snowkrash)
 * @package -
 */
class myLoginValidator {
    /**
     * execute validator
     *
     * @param sfValidatorBase Validator instance that calls this method
     * @param string Value of field that sfValidatorCallback checks
     * @param array Arguments for correct working
     *
     * @return value field when OK. Nothing if error (sfValidatorError exception)
     */
    public static function execute ($oValidator, $sValue, $aArguments) {
        if ( /* check OK */ ) { // Return waarde veld indien controle OK
            return $sValue;
        }
        // Throw exception when not OK
        throw new sfValidatorError($oValidator, 'invalid', array('value' => $sValue, 'invalid' => $oValidator->getOption('invalid')));
    }
}
 

I set this here with no further logic, that is application specific, thus that you 'll have to do yourself. The base structure can be used. The three parameters must be defined, otherwise the whole application crashes.

Widgets

In _setWidget() sfWidget objects are made for each field.

The widgets are the form elements: finally <input>, <select> etc tags in combination with labels and errortexts.

Each widget can have HTML attributes, which will be printed inside the form elements.

The view

Finally the form must be printed to the screen through a view template.

<p><?php echo __('You need to log in to be able to use the Content Management System.') ?></p>
<div id="formContainer">
<?php if ($oForm->getErrorSchema()->getErrors()) { ?>
    <div id="formulierFouten">
    <ul>
    <?php foreach ($oForm->getErrorSchema() as $sError) { ?>
        <li><?php echo __($sError) ?></li>
    <?php } ?>
    </ul>
    </div>
<?php } ?>
<form action="<?php echo url_for('myModule/myLogin') ?>" method="post">
    <?php echo $oForm['username']->renderLabel(__($oForm['username']->renderLabelName())); echo $oForm['username']->renderRow(__($oForm->getWidgetSchema()->getHelp('username'))); ?>
    <?php echo $oForm['password']->renderLabel(__($oForm['password']->renderLabelName())); echo $oForm['password']->renderRow(__($oForm->getWidgetSchema()->getHelp('password'))); ?>
    <?php echo $oForm['referrer']->render(array('value' => $oForm->getDefault('referrer'))) ?>
    <?php echo $oForm['_csrf_token'] ?>
    <label for="inloggen">&nbsp;</label><input type="submit" value="Inloggen" id="inloggen" class="aanmeldenSubmit" />
</form>
</div>
 

You can see all the i18n code (__() helper) and some non-Symfony 1.0 form building. Errorlists are built through the errorSchema which is available within the form object, the texts themself can be translated as you can see.

Also the labels and help texts are squeezed through i18n. The field names are in English, because the labels are based on these and must go through i18n. This way everything can be translated.

You can print the whole form with an echo of $oForm (goes through __toString()), but you have more control over the layout when you use specific widgetrender functions, like I do with renderRow(). This method takes the helptext as an argument, with is also translated.

The submit button is no widget, so we place it ourselves the old-fashioned way ... no helper, that is so Symfony 1.0.

CSRF token

That one is new. It is there, but we never defined it. It is created within sfForm and only since beta4 when indicated in settings.yml:

#Form security secret (CSRF protection)
    csrf_secret:             hierjeeigenc0d3     # Unique secret to enable CSRF protection or false to disable
 

You can choose your own code, on which the hash inside the CSRF value is based.

The form functionally is ready, but we want more control over the layout. I am a supporter of the tableless HTML design and the standard formatter of sfForm uses ... tables. Well, we can do better.

The form controller showed the coupling with my own formatter:

/*
 * Set decorator
 */
$oDecorator = new sfWidgetFormSchemaFormatterDiv($this->getWidgetSchema());
$this->getWidgetSchema()->addFormFormatter('div', $oDecorator);
$this->getWidgetSchema()->setFormFormatterName('div');
 

I will now go into this part.

I have a class sfWidgetFormSchemaFormatterDiv in sfWidgetFormSchemaFormatterDiv.class.php made in the application-level lib/ directory so that all modules of can use it.

This takes care of the HTML layout of the form elements.

class sfWidgetFormSchemaFormatterDiv extends sfWidgetFormSchemaFormatter {
    protected
        $rowFormat = '%error%%field%<br />%help%<br />',
        $helpFormat = '<span class="help">%help%</span>',
        $errorRowFormat = '<div>%errors%</div>',
        $errorListFormatInARow = '%errors%',
        $errorRowFormatInARow = '<div class="formError">&darr;&nbsp;%error%&nbsp;&darr;</div>',
        $namedErrorRowFormatInARow = '%name%: %error%<br />',
        $decoratorFormat = '<div id="formContainer">%content%</div>';
}
 

A good article is available that describes this system.

For people that wonder why the label (%label% placeholder) is not used: $rowFormat sets the layout of the renderRow() method and since I want to render the label separately (i18n), it must not be rendered a second time by renderRow().

Conclusion

Hopefully the above can be a good help for your own form in Symfony 1.1. The documentation is quite scarce at the moment, so each bit of help will be welcome.

If the English is somewhat bad, I did a automatic translation of my original Dutch version of the article and tuned that a bit. The reason? I am lazy. ;-)

If you find errors in the above, it is because of copying my code probably. Please mention it in the comments.

Good luck!

by Jordi Backx on 2008-05-10, tagged 11  csrf  decorator  form  formatter  i18n  login  sfform  symfony  validator  widget