Code snippets for symfony 1.x

Navigation

Popular Tags

action admin ajax batch cache cli criteria css culture custom database date debug doctrine email filter form forms generator helper i18n javascript model navigation object pager pagination pake propel query routing session sql symfony template test user validation validator view widget
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

AutoSet the title of the page

It's so long to set the title page per page and i forgot it frequently. With this filter it's the end of no titled pages.

Copy into lib/filter/sfAutoMetaFilter.class.php :

<?php
/**
 * Manage MetaTags 
 * @author Cédric Lombardot <cedric.lombardot@spyrit.net>
 */
 
class sfAutoMetaFilter extends sfFilter{
    public function execute ($filterChain)
      {
        $filterChain->execute();
        /*
         * Recherche le h1 pour mettre le title
         */
 
 
        $html=$this->getContext()->getResponse()->getContent(); 
        $title=$this->get_title($html);
        $h1s=$this->get_h1($html);
        if($h1s[1]==0){
            $h2s=$this->get_h2($html);
            if($h2s[1]!=0){
                $this->getContext()->getResponse()->setContent($this->set_title($html,(($title)?($title.' - ' ):'').strip_tags($h2s[0][0])));
            }
        }else{
            $this->getContext()->getResponse()->setContent($this->set_title($html,(($title)?($title.' - ' ):'').strip_tags($h1s[0][0])));
        }
 
 
 
 
      }
 
      // retrieve all h1 tags
        protected function get_h1($html){
            $h1tags = preg_match_all("/(<h1.*>)(.*)(<\/h1>)/isxmU",$html,$patterns);
            $res = array();
            array_push($res,$patterns[2]);
            array_push($res,count($patterns[2]));
            return $res;
        } 
        protected function get_h2($html){
            $h2tags = preg_match_all("/(<h2.*>)(.*)(<\/h2>)/isxmU",$html,$patterns);
            $res = array();
            array_push($res,$patterns[2]);
            array_push($res,count($patterns[2]));
            return $res;
        } 
        protected function get_title($html){
            $title = preg_match("/(<title>)(.*)(<\/title>)/isxmU",$html,$patterns);
            return isset($patterns[2])?$patterns[2]:'';
        } 
        protected function set_title($html,$title){
            return preg_replace("/(<title>)(.*)(<\/title>)/isxmU",'<title>'.$title.'</title>',$html);
        } 
}
?>
 

And in your filters.yml

autometa: 
  class: sfAutoMetaFilter
 
by cedric lombardot on 2010-04-29, tagged filter  metas  title 
(2 comments)

Automatically updated 'updated_by' and 'created_by' doctrine columns

Our sf1.4 project required recording the user_id of the person who updated or created a record as well as the timestamps and was using the sfDoctrineGuard plugin.

There are several ways of achieving this but the additional requirement of having constraints on these fields on a db level, being able to load fixtures and have the whole code properly unit testable made it a bit more difficult. why-sfcontextgetinstance-is-bad

After experimenting with other methods like custom save method and the following snippet snippet 281 I came to the following code:

Using actAs Timestampable was an option for the timestamps, but I have chosen to add a new Doctrine template and listener that has the timestamp incorporated so that every update/save only triggers one listener instead of two.

config/doctrine/schema.yml:

Address:
  columns:
    id:
      type: integer(4)
      primary: true
      autoincrement: true
    address_1:
      type: varchar(50)
    address_2:
      type: varchar(50)
    postal_code:
      type: varchar(6)
  relations:
    userCreate:
      class: sfGuardUser
      local: creater_id
      foreign: id
      foreignType: one
      onDelete: RESTRICT
      onUpdate: CASCADE
    userUpdate:
      class: sfGuardUser
      local: updater_id
      foreign: id
      foreignType: one
      onDelete: RESTRICT
      onUpdate: CASCADE
  actAs:
    Auditable:
  options:
    type: INNODB
    collate: utf8_unicode_ci
    charset: utf8
 

The relations part of the schema is only needed if you want to maintain data integrity.

The names of the creater and updater fields are defaulted to 'creater_id' and 'updater_id' but can be changed in the schema as per normal.

The template looks as follows and copies the logic from the timestampable template:

lib/doctrine/Doctrine/Template/Auditable.php:

<?php
class Doctrine_Template_Auditable extends Doctrine_Template
{
    /**
     * Array of userid options
     *
     * @var string
     */
    protected $_options = array(
        'creater' =>  array(
            'name'          => 'creater_id',
            'alias'         => null,
            'type'          => 'int',
            'disabled'      => false,
            'expression'    => false,
            'options'       =>  array('notnull' => true)
        ),
        'updater' =>  array(
            'name'          => 'updater_id',
            'alias'         => null,
            'type'          => 'int',
            'disabled'      => false,
            'expression'    => false,
            'onInsert'      => true,
            'options'       =>  array('notnull' => true)
        ),
        'created' =>  array(
            'name'          =>  'created_at',
            'alias'         =>  null,
            'type'          =>  'timestamp',
            'format'        =>  'Y-m-d H:i:s',
            'disabled'      =>  false,
            'expression'    =>  false,
            'options'       =>  array('notnull' => true)
        ),
        'updated' =>  array(
            'name'          =>  'updated_at',
            'alias'         =>  null,
            'type'          =>  'timestamp',
            'format'        =>  'Y-m-d H:i:s',
            'disabled'      =>  false,
            'expression'    =>  false,
            'onInsert'      =>  true,
            'options'       =>  array('notnull' => true)
        )
    );
 
 
    /**
     * setTableDefinition
     *
     * @return void
     */
    public function setTableDefinition()
    {
        if(!$this->_options['creater']['disabled']) {
            $name = $this->_options['creater']['name'];
            if ($this->_options['creater']['alias']) {
                $name .= ' as ' . $this->_options['creater']['alias'];
            }
            $this->hasColumn(
                $name,
                $this->_options['creater']['type'],
                null,
                $this->_options['creater']['options']
            );
        }
        if(!$this->_options['updater']['disabled']) {
            $name = $this->_options['updater']['name'];
            if ($this->_options['updater']['alias']) {
                $name .= ' as ' . $this->_options['updater']['alias'];
            }
            $this->hasColumn(
                $name,
                $this->_options['updater']['type'],
                null,
                $this->_options['updater']['options']
            );
        }
        if ( ! $this->_options['created']['disabled']) {
            $name = $this->_options['created']['name'];
            if ($this->_options['created']['alias']) {
                $name .= ' as ' . $this->_options['created']['alias'];
            }
            $this->hasColumn(
                $name,
                $this->_options['created']['type'],
                null,
                $this->_options['created']['options']
            );
        }
        if ( ! $this->_options['updated']['disabled']) {
            $name = $this->_options['updated']['name'];
            if ($this->_options['updated']['alias']) {
                $name .= ' as ' . $this->_options['updated']['alias'];
            }
            $this->hasColumn(
                $name,
                $this->_options['updated']['type'],
                null,
                $this->_options['updated']['options']);
        }
 
        $this->addListener(new Doctrine_Template_Listener_Auditable($this->_options));
    }
}
 

The listener that makes sure the fields are set before saving looks like this:

lib/doctrine/Doctrine/Template/Listener/Auditable.php:

<?php
class Doctrine_Template_Listener_Auditable extends Doctrine_Record_Listener
{
    /**
     * Array of userid options
     *
     * @var string
     */
    protected $_options = array();
 
    /**
     * __construct
     *
     * @param string $options.
     * @return void
     */
    public function __construct(array $options)
    {
        $this->_options = $options;
    }
 
    /**
     * preInsert
     *
     * @param Doctrine_Event $event.
     * @return void
     */
    public function preInsert(Doctrine_Event $event)
    {
        if(!$this->_options['creater']['disabled']) {
            $createrName = $this->_options['creater']['name'];
            $event->getInvoker()->$createrName = $this->getUserId($event);
        }
 
        if(!$this->_options['updater']['disabled'] && $this->_options['updater']['onInsert']) {
            $updaterName = $this->_options['updater']['name'];
            $event->getInvoker()->$updaterName = $this->getUserId($event);
        }
 
        if ( ! $this->_options['created']['disabled']) {
            $createdName = $event->getInvoker()->getTable()->getFieldName($this->_options['created']['name']);
            $modified = $event->getInvoker()->getModified();
            if ( ! isset($modified[$createdName])) {
                $event->getInvoker()->$createdName = $this->getTimestamp('created', $event->getInvoker()->getTable()->getConnection());
            }
        }
 
        if ( ! $this->_options['updated']['disabled'] && $this->_options['updated']['onInsert']) {
            $updatedName = $event->getInvoker()->getTable()->getFieldName($this->_options['updated']['name']);
            $modified = $event->getInvoker()->getModified();
            if ( ! isset($modified[$updatedName])) {
                $event->getInvoker()->$updatedName = $this->getTimestamp('updated', $event->getInvoker()->getTable()->getConnection());
            }
        }
    }
 
    /**
     * preUpdate
     *
     * @param Doctrine_Event $event.
     * @return void
     */
    public function preUpdate(Doctrine_Event $event)
    {
        if( ! $this->_options['updater']['disabled']) {
            $updaterName = $this->_options['updater']['name'];
            $event->getInvoker()->$updaterName = $this->getUserId($event);
        }
 
        if ( ! $this->_options['updated']['disabled']) {
            $updatedName = $event->getInvoker()->getTable()->getFieldName($this->_options['updated']['name']);
            $modified = $event->getInvoker()->getModified();
            if ( ! isset($modified[$updatedName])) {
                $event->getInvoker()->$updatedName = $this->getTimestamp('updated', $event->getInvoker()->getTable()->getConnection());
            }
        }
    }
 
    /**
     * getUserId
     *
     * Gets the userid
     *
     * @param Doctrine_Event $event.
     * @return int
     */
    public function getUserId(Doctrine_Event $event)
    {
      if (!$event->getInvoker()->getSystemUser())
      {
        throw new LogicException('The user must be set before saving');
      }
      return $event->getInvoker()->getSystemUser();
    }
 
    /**
     * Set the updated field for dql update queries
     *
     * @param Doctrine_Event $event
     * @return void
     */
    public function preDqlUpdate(Doctrine_Event $event)
    {
        if ( ! $this->_options['updated']['disabled']) {
            $params = $event->getParams();
            $updatedName = $event->getInvoker()->getTable()->getFieldName($this->_options['updated']['name']);
            $field = $params['alias'] . '.' . $updatedName;
            $query = $event->getQuery();
 
            if ( ! $query->contains($field)) {
                $query->set($field, '?', $this->getTimestamp('updated', $event->getInvoker()->getTable()->getConnection()));
            }
        }
    }
 
    /**
     * Gets the timestamp in the correct format based on the way the behavior is configured
     *
     * @param string $type
     * @return void
     */
    public function getTimestamp($type, $conn = null)
    {
        $options = $this->_options[$type];
 
        if ($options['expression'] !== false && is_string($options['expression'])) {
            return new Doctrine_Expression($options['expression'], $conn);
        } else {
            if ($options['type'] == 'date') {
                return date($options['format'], time());
            } else if ($options['type'] == 'timestamp') {
                return date($options['format'], time());
            } else {
                return time();
            }
        }
    }
}
 

As you can see, the user_id is retrieved from the object that is saved via the getSystemUser() method.

To not have to duplicate this method on each of the model classes, I made my base classes extend a custom class. This can to be done by adding a configureDoctrine function to the ProjectConfiguration class

confid/ProjectConfiguration.class.php:

public function configureDoctrine(Doctrine_Manager $manager)
  {
    // use a custom base class that has the system user methods used for updating
    // the creater and updater.
    $options = array('baseClassName' => 'MyDoctrineRecord');
    sfConfig::set('doctrine_model_builder_options', $options);
 
    // user id that is used when no logged in user is present. This is required
    // to be able to load fixtures.
    sfConfig::set('default_updater_id',1);
  }
 

After making these changes and regenerating the sql and model, all your base classes will now extend MyDoctrineRecord which is automatically created if not present already.

The created class is empty and we still need to add the custom functions to it:

lib/model/doctrine/MyDoctrineRecord.class:

<?php
 
abstract class MyDoctrineRecord extends sfDoctrineRecord
{
  protected $systemUserId = null;
 
  public function preSave($event)
  {
    if (!$this->getSystemUser())
    {
      if(!sfContext::hasInstance() || get_called_class()=='sfGuardUser')
      {
        $userId = sfConfig::get('default_updater_id');
        $this->setSystemUser($userId);
      }
      else
      {
        $this->setSystemUser(sfContext::getInstance()->getUser()->getGuardUser()->getId());
      }
    }
  }
 
  public function setSystemUser($userId)
  {
    $this->systemUserId = $userId;
  }
 
  public function getSystemUser()
  {
    return $this->systemUserId;
  }
}
?>
 

The above code allows for insertion of a userId for testing perposes. It will use the default_updater_id that is set in the projectConfiguration. This default is required for the field restrictions to play nice. Without this, it would try and save null which is not accepted by our database because it is not a valid user.

When a user is logged in, the userId of that user will be used.

note: If you add actAs: Auditable to the sfDoctrineGuard schema you even can track who made changes to the permissions tables.

2010-08-04: Updated the MyDoctrineRecord.class code to see if it was called by sfGuardUser. Not sure what goes wrong but I am sure it has to do with the fact that you can't get the logged in user before you are completely logged in.

2011-06-14: Updated the MyDoctrineRecord.class and replaced the save() function with preSave() as the system user was not being set in certain instances.

by Marcel Berteler on 2010-04-16, tagged created  doctrine  sfdoctrineguard  template  updated  userid  username 
(2 comments)

yet another read-only field method

(Sorry for not formatting the code, but the code tag didn't work...)

by 4Him on 2010-03-24, tagged field  form  readonly 
(1 comment)

Google analytics code by filter

This simple filter automatically adds google analytics code for all pages. It's no longer required to modify all the layouts, just put google code to app.yml and this filter does everything for you.

Create a lib/filters/gaFilter.class.php file:

<?php
class gaFilter extends sfFilter
{
  public function execute($filterChain)
  {
    // Nothing to do before the action
    $filterChain->execute();
    // Find google code and check if current module is not disabled
    if(($gaCode = sfConfig::get("app_ga_code",false)) !== false
    && !in_array($this->getContext()->getModuleName(),sfConfig::get("app_ga_disabled_modules",array()))) {
      //Decorate the response with the tracker code
      $response = $this->getContext()->getResponse();
      $response->setContent(str_ireplace('</body>', $gaCode.'</body>',$response->getContent())); 
    }
   }
}
 

Add the filter to filters.yml :

# insert your own filters here
 
# Google analytics filter
ga_filter:
  class: gaFilter
 

Now you are able to insert GA code into your page. Simply copy the whole code and add it as ga_code option into your app.yml file:

all:
  ...
  ga:
    code: >
      <script type="text/javascript">
      try {
      var pageTracker = _gat._getTracker("...");
      pageTracker._trackPageview();
      } catch(err) {}</script>
 

It works on fact, that you probably will not have a </body> tag in other place except at the end of your page and you will not use it in other content type than text/html.

Optionally you may disable GA filter in specific modules, such as administration is. To do this, add this option to your app.yml:

all:
  ...
  ga:
    disabled_modules: [administration]
    code: >
      ...
 
by Radek Petráň on 2010-03-07, tagged analytics  filter  google 

Add inline javascript to the response

Allows you to define a script block in a template, and let it be included by the include_javascripts() helper after all js files, so that you can put it right before the body tag , to conform to the Yahoo performance best practice (see http://developer.yahoo.com/performance/rules.html#js_bottom).

First put this class in your /lib/response directory:

<?php
/*
*  LfmWebResponse
* @package    forcemolle
* @subpackage response
* @author     Jules Bernable <julius@laforcemolle.org>
*
*/
class LfmWebResponse extends sfWebResponse
{
  protected
    $inline_scripts = array();
 
  public function initialize(sfEventDispatcher $dispatcher, $options = array())
  {
    parent::initialize($dispatcher, $options);
 
    $this->inline_scripts = array_combine($this->positions, array_fill(0, count($this->positions), array()));
  }
 
  /**
   * Adds a script block to the current web response.
   *
   * @param string $script    The JavaScript code
   * @param string $id        A unique identifier for the script
   * @param string $position  Position
   */
  public function addInlineScript($script='', $position=sfWebResponse::LAST)
  {
    $this->validatePosition($position);
 
    $this->inline_scripts[$position][] = $script;
  }
 
  /**
   * Retrieves inline scripts from the current web response.
   *
   * By default, the position is sfWebResponse::ALL,
   * and the method returns all inline scripts ordered by position.
   *
   * @param  string $position  The position
   *
   * @return array An array of inline scripts
   */
  public function getInlineScripts($position = self::ALL)
  {
    if (self::ALL === $position)
    {
      $inline_scripts = array();
      foreach ($this->getPositions() as $position)
      {
        foreach ($this->inline_scripts[$position] as $script)
        {
          $inline_scripts[] = $script;
        }
      }
      return $inline_scripts;
    }
    else if (self::RAW === $position)
    {
      return $this->inline_scripts;
    }
 
    $this->validatePosition($position);
 
    return $this->inline_scripts[$position];
  }
 
}
 

Update your $sf_app_dir/config/factories.yml accordingly:

all:
  response:
    class: LfmWebResponse
 

Then you need to overwrite the symfony AssetHelper. First copy $sf_symfony_lib_dir/helper/AssetHelper.php to $sf_app_dir/lib/helper/AssetHelper.php (or to $sf_lib_dir/helper/AssetHelper.php if you want it enabled globally). Then replace the get_javascripts() function (line 492) by the following:

function get_javascripts()
{
  $response = sfContext::getInstance()->getResponse();
  sfConfig::set('symfony.asset.javascripts_included', true);
 
  $html = '';
  foreach ($response->getJavascripts() as $file => $options)
  {
    $html .= javascript_include_tag($file, $options);
  }
 
  use_helper('JavascriptBase');
  foreach ($response->getInlineScripts() as $script)
  {
    $html .= content_tag('script', javascript_cdata_section($script), array('type' => 'text/javascript'));
  }
 
  return $html;
}
 

Et voilà ! You can now add a script in a template this way:

<?php $sf_response->addInlineScript(<<<EOF
// Mootools FTW!
window.addEvent('domready', function()
{
  var foo = alert('BAR!');
});
EOF
);
?>
 

Your script block is now added after all js files added by include_javascripts() !

Please note that this is a basic implementation. You can not 'override' a script nor remove it afterwards.

by Jules Bernable on 2010-03-07, tagged javascript  performance  response 
(3 comments)

Thumbnails creation in view (like sorl in django)

function thumbnail($path, $maxWidth, $maxHeight, $params=null)
{
    $path = ($path{0} == '/' ? '' : '/') . $path;
 
    $maxWidth = (int) $maxWidth;
    $maxHeight = (int) $maxHeight;
 
    $fileName = @end(explode('/', $path));
 
    $thumbsDir = sfConfig::get('sf_web_dir').'/images/thumbs';
    if(!file_exists($thumbsDir)) {
        mkdir($thumbsDir);
        chmod($thumbsDir, 0777);
    }
 
    $thumbName = $fileName.'-'.$maxWidth.'x'.$maxHeight.'.png';
    $thumbPath = '/images/thumbs/'.$thumbName;
 
    $sourcePath = sfConfig::get('sf_web_dir').$path;
    $destPath = sfConfig::get('sf_web_dir').$thumbPath;
 
    if(!file_exists($destPath)) {
        $t = new sfThumbnail($maxWidth, $maxHeight);
        $t->loadFile($sourcePath);
        $t->save($destPath, 'image/png'); 
    }
 
    $path = $thumbPath;
    return image_tag($path, $params);
 
}
 

Usage similar to image_tag, directory web/images/thumbs with chmod 0777 need to be created. Depending on sfThumbnailPlugin

example:

echo thumbnail('bigPhoto.png', 100, 100, 'alt=myBigPhoto');
 
by Adam Zieliński on 2010-03-06, tagged thumbnail 

CPF e CNPJ Validator

<?php
class sfValidatorCpfCnpj extends sfValidatorBase {
 
  /**
   * Configures the current validator
   *
   * Available options (required):
   *
   *  * tipo: CPF or CNPJ
   *
   * @param array $options   An array of options
   * @param array $messages  An array of error messages
   *
   * @see sfValidatorBase
   **/
 
  protected function configure($options = array(), $messages = array())
  {
    $this->addRequiredOption('tipo');
    $this->setMessage('invalid', '%tipo% %value% não é válido.');
  }
 
  protected function validaCPF($cpf) {
        $cpf = str_replace(array('.','-'),'',$cpf);
 
        $soma = 0;
 
        if (strlen($cpf) <> 11)
        return false;
 
        // Verifica 1º digito
        for ($i = 0; $i < 9; $i++):
            $soma += (($i+1) * $cpf[$i]);
        endfor;
 
        $d1 = ($soma % 11);
 
        if ($d1 == 10):
            $d1 = 0;
        endif;
 
        $soma = 0;
 
        // Verifica 2º digito
        for ($i = 9, $j = 0; $i > 0; $i--, $j++):
            $soma += ($i * $cpf[$j]);
        endfor;
 
        $d2 = ($soma % 11);
 
        if ($d2 == 10):
            $d2 = 0;
        endif;
 
        if ($d1 == $cpf[9] && $d2 == $cpf[10]):
            return true;
        else:
            return false;
        endif;
    }
 
    protected function validaCNPJ($cnpj) {
 
        $cnpj = str_replace(array('.','/','-'),'',$cnpj);
 
        if (strlen($cnpj) <> 14)
        return false;
 
        $soma = 0;
 
        $soma += ($cnpj[0] * 5);
        $soma += ($cnpj[1] * 4);
        $soma += ($cnpj[2] * 3);
        $soma += ($cnpj[3] * 2);
        $soma += ($cnpj[4] * 9);
        $soma += ($cnpj[5] * 8);
        $soma += ($cnpj[6] * 7);
        $soma += ($cnpj[7] * 6);
        $soma += ($cnpj[8] * 5);
        $soma += ($cnpj[9] * 4);
        $soma += ($cnpj[10] * 3);
        $soma += ($cnpj[11] * 2);
 
        $d1 = $soma % 11;
        $d1 = $d1 < 2 ? 0 : 11 - $d1;
 
        $soma = 0;
        $soma += ($cnpj[0] * 6);
        $soma += ($cnpj[1] * 5);
        $soma += ($cnpj[2] * 4);
        $soma += ($cnpj[3] * 3);
        $soma += ($cnpj[4] * 2);
        $soma += ($cnpj[5] * 9);
        $soma += ($cnpj[6] * 8);
        $soma += ($cnpj[7] * 7);
        $soma += ($cnpj[8] * 6);
        $soma += ($cnpj[9] * 5);
        $soma += ($cnpj[10] * 4);
        $soma += ($cnpj[11] * 3);
        $soma += ($cnpj[12] * 2);
 
 
        $d2 = $soma % 11;
        $d2 = $d2 < 2 ? 0 : 11 - $d2;
 
        if ($cnpj[12] == $d1 && $cnpj[13] == $d2):
            return true;
        else:
            return false;
        endif;
    }
 
  protected function doClean($value)
  {
    $tipo = $this->getOption('tipo');
 
    if($tipo == 'CPF'):
            if(!$this->validaCPF($value)):
                throw new sfValidatorError($this, 'invalid', array('tipo' => $this->getOption('tipo'), 'value' => $value));
                return false;
            endif;
    elseif($tipo == 'CNPJ'):
            if(!$this->validaCNPJ($value)):
                throw new sfValidatorError($this, 'invalid', array('tipo' => $this->getOption('tipo'), 'value' => $value));
                return false;
            endif;
    else:
        throw new sfValidatorError($this, 'invalid', array('tipo' => $this->getOption('tipo'), 'value' => $value));
        return false;
    endif;
 
    return $value;
  }  
}
 
?>
 
by Anderson da Veiga on 2010-02-27, tagged 12  13  14  cnpj  cpf  symfony  validator 

Moving the front controllers outside the web directory in Symfony 1.4

On occasions we want to move the front controllers of our application to another location and remove them from the web directory, without losing the relative directions to the directories css, js, images, etc..

For this we need to do two things.

  1. Add a little code in the class sfWebRequest to achieve this behavior in the development environment.
  2. Create a filter with the same code for the production environment.

For development environment: Replace the following line in the class sfWebRequest

568 $this->relativeUrlRoot = preg_replace('#/[^/]+\.php5?$#', '', $this->getScriptName());
 

by the following code

568 $relative = preg_replace('#/[^/]+\.php5?$#', '', $this->getScriptName());
569 $webDir = str_replace('\\', '/', sfConfig::get('sf_web_dir'));
570 $intercept = '/'.implode('/', array_intersect(explode('/',$relative), explode('/',$webDir)));       
571 $position = strpos($webDir, $intercept);
572 $this->relativeUrlRoot = substr($webDir, $position);                 
 

For production environment:: Create a filter relativeUrl.

class relativeUrlFilter extends sfFilter 
{
    public function execute($filterChain){
        $context  = $this->getContext();
        $request  = $context->getRequest();
 
        $relative = preg_replace('#/[^/]+\.php5?$#', '', $request->getScriptName());
        $webDir = str_replace('\\', '/', sfConfig::get('sf_web_dir'));
        $intercept = '/'.implode('/', array_intersect(explode('/',$relative), explode('/',$webDir)));       
        $position = strpos($webDir, $intercept);
        $request->setRelativeUrlRoot(substr($webDir, $position));                
        $filterChain->execute();
    }
}
 

Active a filter in apps/yourapp/config/filters.yml

rendering: ~
security:  ~
 
relativeUrl:                 
  class: relativeUrlFilter
 
cache:     ~
common:    ~
execution: ~
 

and that's it. Now you can put the front controllers outside the web directory, for example, in the root directory of the application myproject/index.php and myproject/myapp_dev.php, or in another location within the web directory, for example, myproject/web/frontend/index.php and myproject/web/backend/index.php.

Note: Remember that if you move these files of the web directory, you must update the following line of code according to the new location.

require_once(dirname(__FILE__).'/../config/ProjectConfiguration.class.php');
 
by Ivannis Suárez Jérez on 2010-02-23, tagged configuration 

Validator to make a field read-only

Sometimes, when you display the form for a Doctrine object, you want to make some fields read-only..

You can unset the field, and just display the value in the template. But it's better to still display an input form. You can also mess with the template, etc.

Here's a validator to achive the same goal, with no need to touch the template.

class sfValidatorReadOnlyField extends sfValidatorBase
{
  /**
   * Configures the current validator
   * 
   * Available options (required):
   *
   *  * object: The current form object
   *  * field:  The current form field
   *
   * Available error codes:
   *
   * * changed
   *
   * @param array $options   An array of options
   * @param array $messages  An array of error messages
   *
   * @see sfValidatorBase
   **/
  protected function configure($options = array(), $messages = array())
  {
    $this->addRequiredOption('object');
    $this->addRequiredOption('field');
 
    $this->addMessage('changed', 'You don\'t want to mess with me, kid!');
  }
 
  /**
   * @see sfValidatorBase
   */
  protected function doClean($value)
  {
    // Ok, we could just reset the value, but it's so more fun this way
    $object = $this->getOption('object');
    if ($value != $object->get($this->getOption('field')))
    {
      throw new sfValidatorError($this, 'changed');
    }
 
    return (string) $value;
  }
}
 

In your form class, use this code :

public function configure()
{// make some fields readonly
    $this->widgetSchema['first_name']->setAttribute('readonly', 'readonly');
    $this->widgetSchema['last_name']->setAttribute('readonly', 'readonly');
    $this->widgetSchema['email_address']->setAttribute('readonly', 'readonly');
 
    $this->validatorSchema['first_name'] = new sfValidatorReadOnlyField(array('field' => 'first_name', 'object' => $this->getObject()));
    $this->validatorSchema['last_name'] = new sfValidatorReadOnlyField(array('field' => 'last_name', 'object' => $this->getObject()));
    $this->validatorSchema['email_address'] = new sfValidatorReadOnlyField(array('field' => 'email_address', 'object' => $this->getObject()));
 
…
}
 
by Thibault Jouannic on 2010-02-15, tagged form  validator 
(1 comment)

Dump fixtures using Criteria

In case you have a populated database and you need to generate a fixtures for it, you can use symfony propel:data-dump tasks or manually call methods of sfPropelData.

But If your database contains too much data to be fetched, this methods will not work for you.

For such cases I have extended sfPropelData to use Propel Criteria objects to filter dumped data. For example, you can set limit 30 records for every model to prevent dumping all records from database.

Usage:

You can create special tasks to generate fixtures and use there class listed below

$data = new fxPropelData();
 
// Defining criterias
$c1 = new Criteria();
$c1->setLimit(30);
$c2 = clone($c1);
$c2->add('IS_DELETED',0);
 
// array with models and criterias for them
    $models = array(
      'Fruit'=> $c1,
      'Vegetable' => $c2,
    );
 
$data->dumpDataWithCriteria(sfConfig::get('sf_data_dir').'/fixtures/food.yml',$models, $options['connection']);
 

class fxPropelData

class fxPropelData extends sfPropelData {
 
  public function dumpDataWithCriteria($directoryOrFile, $models = array(), $connectionName = null) {
 
    $dumpData = $this->getDataWithCriteria($models, $connectionName);
 
    // save to file(s)
    if (!is_dir($directoryOrFile)) {
      file_put_contents($directoryOrFile, sfYaml::dump($dumpData, 3));
    }
    else {
      $i = 0;
      foreach ($models as $tableName => $criteria) {
        if (!isset($dumpData[$tableName])) {
          continue;
        }
 
        file_put_contents(sprintf("%s/%03d-%s.yml", $directoryOrFile, ++$i, $tableName), sfYaml::dump(array($tableName => $dumpData[$tableName]), 3));
      }
    }
  }
 
  protected function getDataWithCriteria($models, $connectionName = 'propel') {
    $this->loadMapBuilders();
    $this->con = Propel::getConnection($connectionName);
    $this->dbMap = Propel::getDatabaseMap($connectionName);
 
 
    $dumpData = array();
 
    foreach ($models as $model => $criteria) {
      $tableName = $model;
 
      $tableMap = $this->dbMap->getTable(constant(constant($tableName.'::PEER').'::TABLE_NAME'));
      $hasParent = false;
      $haveParents = false;
      $fixColumn = null;
      foreach ($tableMap->getColumns() as $column) {
        $col = strtolower($column->getName());
        if ($column->isForeignKey()) {
          $relatedTable = $this->dbMap->getTable($column->getRelatedTableName());
          if ($tableName === $relatedTable->getPhpName()) {
            if ($hasParent) {
              $haveParents = true;
            }
            else {
              $fixColumn = $column;
              $hasParent = true;
            }
          }
        }
      }
 
      if ($haveParents) {
        // unable to dump tables having multi-recursive references
        continue;
      }
 
      // get db info
      $resultsSets = array();
      if ($hasParent) {
        $resultsSets[] = $this->fixOrderingOfForeignKeyDataInSameTableWithCriteria($resultsSets, $criteria, $tableName, $fixColumn);
      }
      else {
        $stmt = call_user_func(array($tableName.'Peer', 'doSelectStmt'),$criteria,$this->con);
        $resultsSets[] = array_merge($stmt->fetchAll(PDO::FETCH_ASSOC),$resultsSets);
        $stmt->closeCursor();
        unset($stmt);
      }
 
 
      foreach ($resultsSets as $rows) {
        if(count($rows) > 0) {
          if (!isset($dumpData[$tableName])) $dumpData[$tableName] = array();
 
          foreach ($rows as $row) {
            $pk = $tableName;
            $values = array();
            $primaryKeys = array();
            $foreignKeys = array();
 
            foreach ($tableMap->getColumns() as $column) {
              $col = $column->getName();
              $isPrimaryKey = $column->isPrimaryKey();
 
              if (is_null($row[$col])) {
                continue;
              }
 
              if ($isPrimaryKey) {
                $value = $row[$col];
                $pk .= '_'.$value;
                $primaryKeys[$col] = $value;
              }
 
              if ($column->isForeignKey()) {
                $relatedTable = $this->dbMap->getTable($column->getRelatedTableName());
                if ($isPrimaryKey) {
                  $foreignKeys[$col] = $row[$col];
                  $primaryKeys[$col] = $relatedTable->getPhpName().'_'.$row[$col];
                }
                else {
                  $values[$col] = $relatedTable->getPhpName().'_'.$row[$col];
 
                  $values[$col] = strlen($row[$col]) ? $relatedTable->getPhpName().'_'.$row[$col] : '';
                }
              }
              elseif (!$isPrimaryKey || ($isPrimaryKey && !$tableMap->isUseIdGenerator())) {
                // We did not want auto incremented primary keys
                $values[$col] = $row[$col];
              }
            }
 
            if (count($primaryKeys) > 1 || (count($primaryKeys) > 0 && count($foreignKeys) > 0)) {
              $values = array_merge($primaryKeys, $values);
            }
 
            $dumpData[$tableName][$pk] = $values;
          }
        }
      }
    }
 
    return $dumpData;
  }
 
  protected function fixOrderingOfForeignKeyDataInSameTableWithCriteria($resultsSets, $criteria, $tableName, $column, $in = null)
  {
    is_null($in) ? $criteria->add($column->getName(),NULL) : $criteria->add($column->getName(),$in,Criteria::IN);
 
    $stmt = call_user_func(array($tableName.'Peer', 'doSelectStmt'),$criteria,$this->con);
 
    $in = array();
    while ($row = $stmt->fetch(PDO::FETCH_ASSOC))
    {
      $in[] = "'".$row[strtolower($column->getRelatedColumnName())]."'";
      $resultsSets[] = $row;
    }
 
    if ($in = implode(', ', $in))
    {
      $resultsSets = $this->fixOrderingOfForeignKeyDataInSameTableWithCriteria($resultsSets, $tableName, $column, $in);
    }
 
    return $resultsSets;
  }
 
 
}
 
by Davert on 2009-12-11, tagged creiteria  fixtures  functional  propel  test  unit