Snippets

Create an account or login to be able to add, comment and rate snippets.

Navigation

Popular Tags

action admin ajax batch cache cli criteria css custom database date debug doctrine email filter form forms generator helper i18n javascript model mysql navigation object pager pagination pake plugin propel query session set sql template test user validation validator view

Latest comments latest comments

This is a public source code repository where you can find, rate and comment code snippets published by others, or store you owns and categorize them with tags for easy retrieval.

Latest snippets latest snippets

Culture-aware templates

The following view class tries to load the template under directory name of the current culture, then fall back to the default template if such template cannot be found.

So if you access to "index" action of module "foo" and if current user culture is "zh_HK", the class will try to load: "module/foo/templates/zh_HK/indexSuccess.php"

If no such file exist, it will fall back to the default behaviour by loading "module/foo/templates/indexSuccess.php"

class sfCulturePHPView extends sfPHPView
{
  protected
    $cultureDirectory = '';
 
  public function initialize($context, $moduleName, $actionName, $viewName)
  {
    parent::initialize($context, $moduleName, $actionName, $viewName);
  }
 
  public function getDirectory()
  {
    if ($this->cultureDirectory)
      return $this->directory.'/'.$this->cultureDirectory;
    return $this->directory;
  }
 
  protected function preRenderCheck()
  {
    parent::preRenderCheck();
 
    // determine whether the culture-aware template is usable
    $currentCulture = $this->context->getUser()->getCulture();
 
    $template = $this->directory.'/'.$currentCulture.'/'.$this->template;
 
    if (is_readable($template))
      $this->cultureDirectory = $currentCulture;
    else
      $this->cultureDirectory = '';
  }
}
 
by Tamcy on 2008-05-27, tagged culture  view 

Sending application/xhtml+xml

All web browsers support the 'text/html' content type. However, since the introduction of XHTML, a new content type has been available that brings the benefits of XML, which is 'application/xhtml+xml' (more info, why do this)

Although not all browsers support 'application/xhtml+xml', fortunately, the designers of the HTTP protocol provided a good solution: the browser tells the server what content types it can accept and which it prefers. So the server is able to choose which content type to serve.

Firefox is one of the browsers that supports 'application/xhtml+xml' and with the new Firefox3 comes incremental rendering, which means that it has fixed the earlier disadvantage in Firefox2 that 'text/html' was faster to render (more info).

The most noticeable advantage of XHTML rendering is that errors are displayed by the browser, meaning that it's easier to identify and fix layout problems caused by muddled content. If your content is ill-formed, Firefox displays a yellow error page indicating where the error was located. This can be a most useful development tool.

So how can we serve 'application/xhtml+xml' content?

  1. Your content has to be written to be XHTML instead of HTML (the W3C website provides helpful information in explaining how to do this). Your pages must include the correct DOCTYPE (how to do this).

  2. Your application has to change the content-type header depending on whether the browser can accept XHTML or not. Obviously, it's useless to send 'application/xhtml+xml' to older browsers that don't support it.

This snippet helps with the second issue. I have written a filter (shown below) to change the response headers depending on what each browser is able to handle.

Create a new class in your lib folder called XHTMLFilter.class.php thus:

<?php
 
class XHTMLFilter extends sfFilter
{
  public function execute ($filterChain)
  {
    $applyIn = $this->getParameter('environments', array('dev'));
    if (!is_array($applyIn))
    {
      throw new sfException("XHTMLFilter param: not an array ".print_r($applyIn, true));
    }
 
    if (in_array( SF_ENVIRONMENT, $applyIn ))
    {
      $accept = $this->context->getRequest()->getAcceptableContentTypes();
      if (in_array('application/xhtml+xml', $accept))
      {
        $this->context->getResponse()->setContentType( 'application/xhtml+xml' );
      }
    }
 
    $filterChain->execute();
  }
}
 

Now add it to your app/config/filters.yml:

rendering: ~
web_debug: ~
security:  ~
 
XHTMLFilter:
  class: XHTMLFilter
  param:
    environments:    [ dev ]
 
cache:     ~
common:    ~
flash:     ~
execution: ~
 

There is one configuration parameter (environments) that contains an array of the environments to which the filter will apply. By default, only the 'dev' environment is included but you can add any or all of your environments here.

by Rick Beton on 2008-05-25, tagged contenttype  filter  header  xhtml  xml 

Creating SQL for multiple insert

Relating to the post http://www.symfony-project.org/forum/index.php/m/32510/?srch=insert+values+propel

I've created tools to generate SQL.

PS. Done with haste and for postgresSQL.

Can be edited to take in array of objects and form a whole SQL but I don't want to keep a large array of objects.

CREATED_AT, UPDATED_AT forced as NOW()

  /**
  * To generate sql values for insert statement
  * 
  * @param  $object           object      the object with relavant data
  * 
  * @return $valueSql         string     the object values string
  */
  public static function getObjectInsertValue($object)
  {
    if(!$object->isNew())
    {
        throw new Exception('ScToolkit::getObjectInsertValue($object) Only usable with new object.');
      return '';
    }
 
    $className = get_class($object);
    $classPeer = get_class($object->getPeer());
    $tableName = "";
    eval('$tableName = '.$classPeer.'::TABLE_NAME;');
    //For postgres insert (postgres need define Primary Key)
    $nextId = 'nextval(\''.$tableName.'_seq\')';
    $phpNameMap = array();
    eval('$phpNameMap = array_flip('.$classPeer.'::getPhpNameMap());');
 
    //For checking primary key
    $object->setPrimaryKey(1);
    $object->resetModified();
    $object->setPrimaryKey(2);
 
    $valueArray = array();
    foreach($phpNameMap as $dataKey=>$objectKey)
    {
      $isPrimaryKey = false;
      $columnName = "";
      eval('$columnName = '.$classPeer.'::'.$dataKey.';');
      if($object->isColumnModified($columnName))
      {
        $isPrimaryKey = true;
      }
      if(!$isPrimaryKey)
      {
        if($dataKey=='CREATED_AT' || $dataKey=='UPDATED_AT')
        {
          $valueArray[] = 'NOW()';
        }
        else
        {
          if(is_null($object->getByName($objectKey)))
          {
            $valueArray[] = 'NULL';
          }
          else
          {
            $data = $object->getByName($objectKey);
            $wrapper = "";
            if(is_string($data))
            {
                $wrapper = "'";
              $data = pg_escape_string($data);
            }
            $valueArray[] = $wrapper.$data.$wrapper;
          }
        }
      }
      else
      {
        //For postgres insert (postgres need define Primary Key)
        eval('$valueArray[] = "'.$nextId.'";');
      }
    }
 
    $valueStr = '('.implode(", ", $valueArray).')';
    return $valueStr;
  }
 
  /**
  * To generate sql insert into for insert statement
  * 
  * @param  $classPeer        string    the object class peer string
  * 
  * @return $intoSql          string    the object insert into string
  */
  public static function getObjectInsertInto($classPeer)
  {
    $classObject = substr($classPeer, 0, -strlen('Peer'));
    $object = null;
    eval('$object = new '.$classObject.'();');
    //For checking primary key
    $object->resetModified();
    $object->setPrimaryKey(1);
 
    $phpNameMap = array();
    eval('$phpNameMap = array_flip('.$classPeer.'::getPhpNameMap());');
 
    $intoArray = array();
    foreach($phpNameMap as $dataKey=>$objectKey)
    {
      $isPrimaryKey = false;
      $columnName = "";
      eval('$columnName = '.$classPeer.'::'.$dataKey.';');
      if($object->isColumnModified($columnName))
      {
        $isPrimaryKey = true;
      }
 
      //For postgres insert (postgres need define Primary Key)        
      //if(!$isPrimaryKey)
      {
        $intoArray[] = $dataKey;
      }
    }
 
    $intoStr = '('.implode(", ", $intoArray).')';
    return $intoStr;
  }
 

How to use.

$inserts = array();
while($creatingObjects)
{
  $object = new ObjectClass();
  $object->setTitle("title");
  $object->setSummary("summary");
  $inserts[] = Toolkit::getObjectInsertValue($object);
}
 
$sql = sprintf('INSERT INTO %s %s', 
    ObjectPeer::TABLE_NAME, Toolkit::getObjectInsertInto('ObjectPeer')) . 
    ' VALUES ' . implode(', ', $inserts);
 
$con = Propel::getConnection();
$stmt = $con->createStatement();
$rs = $stmt->executeQuery($sql, ResultSet::FETCHMODE_NUM);
 
by Yi Sheng Yap on 2008-05-13, tagged database  multiple  propel  sql 

module.css

This filter will automatically add css file with the name of current module.

It's great to have these files saved separately that to have one huge file. (And demo of CSSEdit doesn't allow saving files bigger than 2.5k chars :D)

Just create your cssAdderFilter.class.php, and add it to your filters.yml. Clear cache and you are ready to go.

<?php 
 
class cssAdderFilter extends sfFilter
{
 
    public function execute( $filterChain )
    {
        $context = $this->getContext();
        $request = $context->getRequest();
        $response = $context->getResponse();
 
        $module = $request->getParameter("module");
 
        $path_to_web = sfConfig::get("sf_web_dir");
        $path_to_css = $path_to_web . "/css/" . $module . ".css";
 
        if (file_exists($path_to_css))
        {
            $response->addStylesheet($module.".css");
        }
 
        $filterChain->execute();
    }
 
}
 
by zero0x on 2008-05-10, tagged asset  css  filter 
(3 comments)

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 

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 field 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 gedefinieerd 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.

Formatter

The form functionally 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.

Goog luck!

by /snippets/snippet/listByUser/login/ on 2008-05-10, tagged 11  csrf  decorator  form  formatter  i18n  login  sfform  symfony  validator  widget 

Latitude Longitude Validator

A Latitude Longitude validator that allows you to specify very exact constraints for the co-ordinates that are submitted.

<?php
 
/*
 * This file is part of the symfony package.
 * (c) 2004-2006 Fabien Potencier <fabien.potencier@symfony-project.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
 
/**
 * myLatLngValidator 
 * 
 * @description This class validator will validate the entered latitude and
 * longitudes submitted using a form or other method.
 * @subpackage validator
 * @author Nathan Rzepecki
 * @created 21/4/2008
 */
class myLatLngValidator extends sfValidator
{
  /**
   * Executes this validator.
   * 
   * @param mixed A file or parameter value/array
   * @param error An error message reference
   *
   * @return bool true, if this validator executes successfully, otherwise false
   */
  public function execute(&$value, &$error)
  {
//
//      echo '<pre>value is: '.$value.'</pre>';
//      echo '<pre>min_type: '.$this->getParameterHolder()->get('min_type').'</pre>';
//      echo '<pre>max type: '.$this->getParameterHolder()->get('max_type').'</pre>';
//      echo '<pre>min: '.$this->getParameterHolder()->get('min').'</pre>';
//      echo '<pre>max: '.$this->getParameterHolder()->get('max').'</pre>';
//      exit;
 
//      echo '<pre>value: '.$value.'</pre>';
//      echo '<pre>max type: '.$this->getParameterHolder()->get('max_type').'</pre>';
//      echo '<pre>min type: '.$this->getParameterHolder()->get('min_type').'</pre>';
//      echo '<pre>min: '.$this->getParameterHolder()->get('min').'</pre>';
//      echo '<pre>max: '.$this->getParameterHolder()->get('max').'</pre>';
//      echo '<pre>'..'</pre>';
 
    /*
     * If the min_type was set
     */
      if ($this->getParameterHolder()->get('min_type') == '+')
      {
         // type was positive
         $re = '/^\d{1,3}(\.(\d*)){0,1}$/';
         if (!preg_match($re, $value))
         {
           $error = $this->getParameterHolder()->get('min_type_error');
           return false;
         } // end if
      }
      elseif ($this->getParameterHolder()->get('min_type') == '-')
      {
        // type was negative
        $re = '/^-\d{1,3}(\.(\d*)){0,1}$/';
         if (!preg_match($re, $value))
         {
           $error = $this->getParameterHolder()->get('min_type_error');
           return false;
         } // end if
      }
      else
      {
        // don't care if it is plus or minus
         $re = '/^-{0,1}\d{1,3}(\.(\d*)){0,1}$/';
         if (!preg_match($re, $value))
         {
           $error = $this->getParameterHolder()->get('min_type_error');
           return false;
         } // end if
      } // end if elseif else
 
 
    /*
     * if max_type was set
     */            
      if ($this->getParameterHolder()->get('max_type') == '+')
      {
         // type was positive
         $re = '/^\d{1,3}(\.(\d*)){0,1}$/';
         if (!preg_match($re, $value))
         {
           $error = $this->getParameterHolder()->get('max_type_error');
           return false;
         } // end if
      }
      elseif ($this->getParameterHolder()->get('max_type') == '-')
      {
        // type was negative
        $re = '/^-\d{1,3}\.{0,1}\d+$/';  // negative digit
         if (!preg_match($re, $value))
         {
           $error = $this->getParameterHolder()->get('max_type_error');
           return false;
         } // end if
      }
      else
      {
        // don't care if it is plus or minus
         $re = '/^-{0,1}\d{1,3}(\.(\d*)){0,1}$/';
         if (!preg_match($re, $value))
         {
           $error = $this->getParameterHolder()->get('max_type_error');
           return false;
         } // end if
      } // end if elseif else
 
 
    /*
     * if max value was set
     */
     if ($this->getParameterHolder()->get('max'))
     {
       $re = '/^-\d{1,3}(\.(\d*)){0,1}$/';  // negative digit
       if (preg_match($re, $this->getParameterHolder()->get('max')))
       {
          // yes its a negative so remove the prefix -
          $maxValue = substr($this->getParameterHolder()->get('max'), 1);
 
          if (!preg_match($re, $value))
          {
            $error = $this->getParameterHolder()->get('max_type_error');
            return false;
          }
          else
          {
            // both the max value and the value are negative so test that the value is less than the max value
            if (!($maxValue > substr($value, <