<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet title="XSL formatting" type="text/xsl" href="http://clear-cache.fr/?feed/rss2/xslt" ?><rss version="2.0"
  xmlns:dc="http://purl.org/dc/elements/1.1/"
  xmlns:wfw="http://wellformedweb.org/CommentAPI/"
  xmlns:content="http://purl.org/rss/1.0/modules/content/"
  xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
  <title>Clear Cache</title>
  <link>http://clear-cache.fr/?</link>
  <atom:link href="http://clear-cache.fr/?feed/rss2" rel="self" type="application/rss+xml"/>
  <description></description>
  <language>en</language>
  <pubDate>Wed, 08 Sep 2010 02:48:00 +0200</pubDate>
  <copyright></copyright>
  <docs>http://blogs.law.harvard.edu/tech/rss</docs>
  <generator>Dotclear</generator>
  
    
  <item>
    <title>Little tip when you need to load your own yaml file</title>
    <link>http://clear-cache.fr/?post/2010/05/09/Little-tip-when-you-need-to-load-your-own-yaml-file</link>
    <guid isPermaLink="false">urn:md5:36ad8dd771f5b67967afcb1280b669cd</guid>
    <pubDate>Sun, 09 May 2010 16:25:00 +0200</pubDate>
    <dc:creator>eNk`</dc:creator>
        <category>Symfony</category>
        <category>symfony</category><category>yaml</category>    
    <description>&lt;p&gt;So this is just a little tip when you need to load your own yaml file.&lt;br /&gt;
To load a yaml file you can use sfYamlConfigHandler::parseYaml() ( or sfYaml::load() ). And just after you can also use sfYamlConfigHandler::replaceConstants() to replace constant(s) from Symfony such as %SF_LIB_DIR% (or any other value defined in a yaml config file) in the values you've just loaded from your custom yaml file.&lt;br /&gt;
&lt;br /&gt;
A little exemple:&lt;/p&gt;    &lt;pre class=&quot;brush: plain&quot;&gt;# config/my_config.yml
dirs:
  lib:    %SF_LIB_DIR%/my_lib
  custom: %SUPER_PATH%/is/here&lt;/pre&gt;

&lt;pre class=&quot;brush: php&quot;&gt;sfConfig::set('super_path', '/the/super/path');

$configuration = sfYamlConfigHandler::parseYaml(sfConfig::get('sf_config_dir').'/my_config.yml');
$configuration = sfYamlConfigHandler::replaceConstants($configuration);

var_dump($configuration);
/*
array(1) {
  [&amp;quot;dirs&amp;quot;]=&amp;gt;
  array(2) {
    [&amp;quot;lib&amp;quot;]=&amp;gt;
    string(53) &amp;quot;/Users/cedric/Dev/workspace/my_sf_project/lib/my_lib&amp;quot;
    [&amp;quot;custom&amp;quot;]=&amp;gt;
    string(23) &amp;quot;/the/super/path/is/here&amp;quot;
  }
}
*/&lt;/pre&gt;</description>
    
    
    
          <comments>http://clear-cache.fr/?post/2010/05/09/Little-tip-when-you-need-to-load-your-own-yaml-file#comment-form</comments>
      <wfw:comment>http://clear-cache.fr/?post/2010/05/09/Little-tip-when-you-need-to-load-your-own-yaml-file#comment-form</wfw:comment>
      <wfw:commentRss>http://clear-cache.fr/?feed/atom/comments/19</wfw:commentRss>
      </item>
    
  <item>
    <title>Custom sfValidatorFile and sfValidatedFile</title>
    <link>http://clear-cache.fr/?post/2010/04/05/Custom-sfValidatorFile-and-sfValidatedFile</link>
    <guid isPermaLink="false">urn:md5:117db78c2d288401291fec54230147e3</guid>
    <pubDate>Mon, 05 Apr 2010 10:56:00 +0200</pubDate>
    <dc:creator>eNk`</dc:creator>
        <category>Symfony</category>
        <category>file upload</category><category>forms</category><category>symfony</category><category>validator</category>    
    <description>&lt;p&gt;In this post I will show a way to customize the file upload with the forms framework. The forms framework provide the sfWidgetFormInputFile class to display a file input tag and the sfValidatorFile class which is the default class used to validate a file input field. As all validator classes sfValidatorFile check if the field value is ok according to the validation options, but instead of returning a &quot;basic&quot; value such as an integer or a string, this validator will return an instance of sfValidat&lt;strong&gt;ed&lt;/strong&gt;File as a clean value.&lt;/p&gt;    &lt;p&gt;In this exemple I will use an Image class and cutomize the ImageForm class.&lt;/p&gt;
&lt;pre&gt;
Image:
  columns:
    title: { type: string(255) }
    file:  { type: string(255) } 
&lt;/pre&gt;


&lt;p&gt;The Image form class configured with a sfWidgetFormInputFile and a sfValidadorFile:&lt;/p&gt;

&lt;pre class=&quot;brush: php&quot;&gt;class ImageForm extends BaseImageForm
{
    public function configure()
    {
        $this-&amp;gt;setWidget('file', new sfWidgetFormInputFile());
	
        $this-&amp;gt;setValidator('file', new sfValidadorFile(array(
            'path' =&amp;gt; '/my/super/folder';
            // ... other options ...
        ))));
    }
}&lt;/pre&gt;


&lt;p&gt;By using the previous configuration, when we will save the form the image file will automaticaly be saved in /my/super/folder.
But let's say we need to save the image in several size, how to do it ? To do this the sfValidad&lt;strong&gt;or&lt;/strong&gt;File validator provide a 'validated_file_class' option to define your own sfValidt&lt;strong&gt;ed&lt;/strong&gt;File class. So you can easly create a ImageValidatedFile class which extends from sfValidatedFile and override the sfValidatedFile::save() function to save the image as the way you want.
&lt;br /&gt;
&lt;br /&gt;
But let's go a little bit futher, it would be cool if we could pass the several size format to the ImageValidatedFile class from the form configuration function, so something like:&lt;/p&gt;

&lt;pre class=&quot;brush: php&quot;&gt;class ImageForm extends BaseImageForm
{
    public function configure()
    {
    	$this-&amp;gt;setWidget('file', new sfWidgetFormInputFile());
	
        $this-&amp;gt;setValidator('file', new sfValidatorFile(array(
            'path' =&amp;gt; '/my/super/folder',
            'resize_formats' =&amp;gt; array(
                'big' =&amp;gt; array('width' =&amp;gt; 800, 'height' =&amp;gt; 600),
                'thumb' =&amp;gt; array('width' =&amp;gt; 120, 'height' =&amp;gt; 80) ))
        ));
    }
}&lt;/pre&gt;


&lt;p&gt;As you can see in the following sfValidatorFile::doClean() function, the sfValidatorFile validator don't pass any options to the validated file class except the path option.&lt;/p&gt;

&lt;pre class=&quot;brush: php&quot;&gt;protected function doClean($value)
{
  // ...

  $class = $this-&amp;gt;getOption('validated_file_class');
	
  return new $class($value['name'], $mimeType, $value['tmp_name'], $value['size'], $this-&amp;gt;getOption('path'));
}&lt;/pre&gt;


&lt;p&gt;So to pass some options to the validated file class we can create a custom ImageValidat&lt;strong&gt;or&lt;/strong&gt;File class which will use the ImageValidat&lt;strong&gt;ed&lt;/strong&gt;File class by default. So the idea is to configure the ImageForm like this:&lt;/p&gt;

&lt;pre class=&quot;brush: php&quot;&gt;class ImageForm extends BaseImageForm
{
    public function configure()
    {
    	$this-&amp;gt;setWidget('file', new sfWidgetFormInputFile());
	
        $this-&amp;gt;setValidator('file', new ImageValidatorFile(array(
            'path' =&amp;gt; '/my/super/folder',
            // custom options I will define in ImageValidatorFile:
            'preserve_ratio' =&amp;gt; true,
            'resize_formats' =&amp;gt; array(
                'big' =&amp;gt; array('width' =&amp;gt; 800, 'height' =&amp;gt; 600),
                'thumb' =&amp;gt; array('width' =&amp;gt; 120, 'height' =&amp;gt; 80) ))
        ));
    }
}&lt;/pre&gt;


&lt;p&gt;In the ImageValidat&lt;strong&gt;or&lt;/strong&gt;File class we just need to override the configure() and doClean() functions to set the validator options and pass the custom options to the validated file class:&lt;/p&gt;

&lt;pre class=&quot;brush: php&quot;&gt;class ImageValidatorFile extends sfValidatorFile
{
    protected function configure($options = array(), $messages = array())
    {
        parent::configure($options, $messages);
    	$this-&amp;gt;setOption('validated_file_class', 'ImageValidatedFile');
        $this-&amp;gt;setOption('required', false);

        $this-&amp;gt;addOption('preserve_ratio', true);
        $this-&amp;gt;addOption('resize_formats', array()));
    }

    protected function doClean($value)
    {
        $validatedFile = parent::doClean($value);
        $validatedFile-&amp;gt;setResizeFormats($this-&amp;gt;getOption('resize_formats'));
        $validatedFile-&amp;gt;setPreserveRation($this-&amp;gt;getOption('preserve_ratio'));
        
        return $validatedFile;
    }
}&lt;/pre&gt;


&lt;p&gt;And in the ImageValidat&lt;strong&gt;ed&lt;/strong&gt;File we just need to override the save() function:&lt;/p&gt;

&lt;pre class=&quot;brush: php&quot;&gt;class ImageValidatedFile extends sfValidatedFile
{
    private $resizeFormats = array();

    private $preserveRatio = true;

    public function setResizeFileFormats($value)
    {
        $this-&amp;gt;resizeFormats = $value;
    }

    public function setPreserveRation($value)
    {
        $this-&amp;gt;preserveRatio = (boolean) $value;
    }
	
    public function save($file = null, $fileMode = 0666, $create = true, $dirMode = 0777)
    {
        if(is_null($file))
        {
            $fileName = substr($this-&amp;gt;originalName, 0, strpos($this-&amp;gt;originalName, '.'));
        	
            $file = preg_replace('/\W+/', '-', $fileName);
            $file = strtolower(trim($file, '-'));
            $file .= $this-&amp;gt;getExtension('.jpg');
        }

        if($create)
        {
            // create the folder where to save the files with $dirMode permissions.
        }

        if(!is_array($this-&amp;gt;resizeFileFormats))
        {
            $this-&amp;gt;resizeFileFormats = array();
        }

        // here I use sfImageTransformPlugin to resize the image
        $img = new sfImage($this-&amp;gt;tempName, $this-&amp;gt;type);
         
        foreach($this-&amp;gt;resizeFormats as $formatName =&amp;gt; $dimensions)
        {
            $img-&amp;gt;resize($dimensions['width'], $this-&amp;gt;preserveRatio ? null : $dimensions['height']);
            $img-&amp;gt;saveAs($this-&amp;gt;path.'/'.$formatName.'/'.$file, $this-&amp;gt;type);
        }
        
        return $file; // return the file's name
    }
}&lt;/pre&gt;</description>
    
    
    
          <comments>http://clear-cache.fr/?post/2010/04/05/Custom-sfValidatorFile-and-sfValidatedFile#comment-form</comments>
      <wfw:comment>http://clear-cache.fr/?post/2010/04/05/Custom-sfValidatorFile-and-sfValidatedFile#comment-form</wfw:comment>
      <wfw:commentRss>http://clear-cache.fr/?feed/atom/comments/18</wfw:commentRss>
      </item>
    
  <item>
    <title>Customize Doctrine model builder options</title>
    <link>http://clear-cache.fr/?post/2010/01/21/Customize-Doctrine-model-builder-options</link>
    <guid isPermaLink="false">urn:md5:92f75b150ee9aa19ff2e590a42c50c37</guid>
    <pubDate>Thu, 21 Jan 2010 14:51:00 +0100</pubDate>
    <dc:creator>eNk`</dc:creator>
        <category>Symfony</category>
        <category>Doctrine</category><category>symfony</category>    
    <description>&lt;p&gt;This is just a little tip to customize the builder options for the generated model classes (lib/model/). Basically if we define a MyClass class in the schema.yml we will have this:
&lt;br /&gt;
- MyClass &amp;gt; BaseMyClass &amp;gt; sfDoctrineRecord &amp;gt; Doctrine_Record &amp;gt; ...&lt;br /&gt;
- MyClassTable &amp;gt; Doctrine_Table &amp;gt; ...&lt;/p&gt;    &lt;p&gt;So the idea is to get somthing like:
&lt;br /&gt;
- MyClass &amp;gt; BaseMyClass &amp;gt; MyDoctrineRecord &amp;gt; sfDoctrineRecord &amp;gt; Doctrine_Record &amp;gt; ...&lt;br /&gt;
- MyClassTable &amp;gt; MyDoctrineTable &amp;gt; Doctrine_Table &amp;gt; ...
&lt;br /&gt;
This can be useful if we need to add common elements to all record or table objects in a project.
&lt;br /&gt;
&lt;br /&gt;
When you run a build model the task load the sfDoctrinePlugin default configuration. You can find this default configuration in sfDoctrinePluginConfiguration::getModelBuilderOptions().
This is the code from getModelBuilderOptions() (here I'm using sf1.3):&lt;/p&gt;

&lt;pre class=&quot;brush: php&quot;&gt;public function getModelBuilderOptions()
  {
    $options = array(
      'generateBaseClasses'  =&amp;gt; true,
      'generateTableClasses' =&amp;gt; true,
      'packagesPrefix'       =&amp;gt; 'Plugin',
      'suffix'               =&amp;gt; '.class.php',
      'baseClassesDirectory' =&amp;gt; 'base',
      'baseClassName'        =&amp;gt; 'sfDoctrineRecord',
    );

    // for BC
    $options = array_merge($options, sfConfig::get('doctrine_model_builder_options', array()));

    // filter options through the dispatcher
    $options = $this-&amp;gt;dispatcher-&amp;gt;filter(new sfEvent($this, 'doctrine.filter_model_builder_options'), $options)-&amp;gt;getReturnValue();

    return $options;
  }&lt;/pre&gt;


&lt;p&gt;As you can see $options contain the default configuration, this config is merged with sfConfig::get('doctrine_model_builder_options', array()), so we can easily customize this configuration by setting doctrine_model_builder_options in the ProjectConfiguration class like this:&lt;/p&gt;


&lt;pre class=&quot;brush: php&quot;&gt;class ProjectConfiguration extends sfProjectConfiguration
{
    public function setup()
    {
        // ...

        $this-&amp;gt;setupCorePluginsCustomConfig();
    }

    public function setupCorePluginsCustomConfig()
    {
        // custom builder options for doctrine
        sfConfig::set('doctrine_model_builder_options',
                      array('baseTableClassName' =&amp;gt; 'MyDoctrineTable',
                            'baseClassName' =&amp;gt; 'MyDoctrineRecord'));
    }
}&lt;/pre&gt;


&lt;p&gt;MyDoctrineTable and MyDoctrineRecord simply extends Doctrine_Table and sfDoctrineRecord.&lt;/p&gt;

&lt;pre class=&quot;brush: php&quot;&gt;class MyDoctrineTable extends Doctrine_Table
{
    // ...
}

class MyDoctrineRecord extends sfDoctrineRecord
{
    // ...
}&lt;/pre&gt;


&lt;p&gt;Now if we build model base model files (for record objects in lib/model/base) will be generated with 'extends MyDoctrineRecord'. For table objects, if the files don't exist they will be created with 'extends MyDoctrineTable' else you will have to update it manually.&lt;/p&gt;


&lt;p&gt;You can have a look to the &lt;a href=&quot;http://trac.doctrine-project.org/browser/branches/1.2/lib/Doctrine/Import/Builder.php&quot; hreflang=&quot;en&quot;&gt;Doctrine_Import_Builder&lt;/a&gt; class attributes to see all options.&lt;/p&gt;</description>
    
    
    
          <comments>http://clear-cache.fr/?post/2010/01/21/Customize-Doctrine-model-builder-options#comment-form</comments>
      <wfw:comment>http://clear-cache.fr/?post/2010/01/21/Customize-Doctrine-model-builder-options#comment-form</wfw:comment>
      <wfw:commentRss>http://clear-cache.fr/?feed/atom/comments/17</wfw:commentRss>
      </item>
    
  <item>
    <title>How to create a custom Doctrine behavior</title>
    <link>http://clear-cache.fr/?post/2009/11/26/How-to-create-a-custom-Doctrine-behavior</link>
    <guid isPermaLink="false">urn:md5:9c62fe221d76bb21209dec7e04cc9fd4</guid>
    <pubDate>Thu, 26 Nov 2009 15:19:00 +0100</pubDate>
    <dc:creator>eNk`</dc:creator>
        <category>Symfony</category>
        <category>behavior</category><category>Doctrine</category><category>symfony</category>    
    <description>&lt;p&gt;So all is in the tittle ^^, in this post I will speak about a way to create your own Doctrine behavior. I really want to share this because it's the first time I create a behavior and I didn't really find any post about it with some complete exemple with code.
&lt;br /&gt;
&lt;br /&gt;
Before starting:&lt;br /&gt;
Why behavior could be very useful ? I think this well explained in the Doctrine documentation &lt;a href=&quot;http://www.doctrine-project.org/documentation/manual/1_1/en/behaviors#introduction&quot;&gt;here&lt;/a&gt; ^^.
&lt;br /&gt;
&lt;br /&gt;
Why I wrote this post ? The Doctrine documentation contains very good explainations about the way of using temaples but I think it's blur about the way you can link a template and a generator in order to generate some classes on the fly.&lt;/p&gt;    &lt;p&gt;Let's go:&lt;br /&gt;
Here, the aim is to create a &quot;Commentable&quot; behavior to allow us to say that a class can have some comments and also create the corresponding comment class. In the exemple I suppose I need to add some comments on a Post class and a comment have the following fields: content, element_id, user_id and created_at. I would like to write my schema.yml like this: (and also be able to define the name of my user class to create a foreign key on the user_id field):&lt;/p&gt;

&lt;pre class=&quot;brush: plain&quot;&gt;#config/doctrine/schema.yml
Post:
  actAs:
    Timestampable: ~
    Commentable:
      userColumn: MyUserClass
  columns:
    content: { type: string }

MyUserClass:
  actAs:
    Timestampable: ~
  columns:
    name: { type: string }&lt;/pre&gt;


&lt;p&gt;Let's start to create our behavior, first of all we need to create a template class to define fields and relations that a class on which we apply the template will have. In our exemple this template will be used on the Post class. So let's create the Commentable template to define that the Post class has many comments. To do this template simply create a new class (for exemple in my_project/lib/template/) and extend the Doctrine_Template class. We will have to use two functions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;setTableDefinition(): define the table fields&lt;/li&gt;
&lt;li&gt;setUp(): define the relations and load generators.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;brush: php&quot;&gt;// lib/template/Commentable.class.php
class Commentable extends Doctrine_Template
{
    public function setTableDefinition() { }

    public function setUp()
    {
        $this-&amp;gt;hasMany($this-&amp;gt;getTable()-&amp;gt;getComponentName().'Comment as Comments', // here $this-&amp;gt;getTable()-&amp;gt;getComponentName() will return 'Post'
                       array('local' =&amp;gt; 'id',
                             'foreign' =&amp;gt; 'element_id'));
    }
}&lt;/pre&gt;


&lt;p&gt;Ok so we have a temple to say that a commentable class has many comments, and now we need a comment class (it will be PostComment in the exemple), and we will also generate files for this class. So to do that we can create a generator. The generator class will define fields and relations for the comment class (PostComment) and generate the files if needed. Let's create a CommentGenerator class (in my_project/lib/generator/) which extend the Doctrine_Record_Generator class.&lt;/p&gt;

&lt;pre class=&quot;brush: php&quot;&gt;// lib/generator/CommentGenerator.class.php
class CommentGenerator extends Doctrine_Record_Generator
{
    public function initOptions()
    {
        $builderOptions = array('suffix' =&amp;gt; '.class.php',
                                'baseClassesDirectory' =&amp;gt; 'base',
                                'generateBaseClasses' =&amp;gt; true,
                                'generateTableClasses' =&amp;gt; true,
                                'baseClassName' =&amp;gt; 'sfDoctrineRecord');

        $this-&amp;gt;setOption('builderOptions', $builderOptions);
        $this-&amp;gt;setOption('className', '%CLASS%Comment');
        $this-&amp;gt;setOption('generateFiles', true);
        $this-&amp;gt;setOption('generatePath', sfConfig::get('sf_lib_dir').DIRECTORY_SEPARATOR.'model'.DIRECTORY_SEPARATOR.'doctrine');
    }

    public function getRelationLocalKey()
    {
        return 'element_id';
    }

    public function buildRelation()
    {
        $this-&amp;gt;buildForeignRelation('Comments');
        $this-&amp;gt;buildLocalRelation('Element');
    }

    public function setTableDefinition()
    {
        $this-&amp;gt;hasColumn('id', 'integer', null, array('primary' =&amp;gt; true, 'autoincrement' =&amp;gt; true));
        $this-&amp;gt;hasColumn('content', 'text', null, array('type' =&amp;gt; 'text'));
        $this-&amp;gt;hasColumn($this-&amp;gt;getRelationLocalKey(), 'integer');
        $this-&amp;gt;hasColumn('user_id', 'integer');
        $this-&amp;gt;hasColumn('created_at', 'date');
    }

    public function setUp()
    {
        $this-&amp;gt;hasOne($this-&amp;gt;getOption('userClass'),
                      array('local' =&amp;gt; 'user_id',
                            'foreign' =&amp;gt; 'id',
                            'onDelete' =&amp;gt; 'cascade'));
    }
}&lt;/pre&gt;


&lt;p&gt;Now we will add a listener to automaticly handle the created_at field on an insertion. Of course we could use the Timesptampable behavior in the table definition of CommentGenerator, but for the exemple I prefer create a new listener. So for this listener, we just need to create a CommentListener which extend from Doctrine_Record_Listener and overrride some function such as preInsert() in our case.&lt;/p&gt;

&lt;pre class=&quot;brush: php&quot;&gt;// lib/listener/CommentListener.class.php
class CommentListener extends Doctrine_Record_Listener
{
    public function preInsert(Doctrine_Event $event)
    {
        $event-&amp;gt;getInvoker()-&amp;gt;created_at = date('Y-m-d', time());
    }
}&lt;/pre&gt;


&lt;p&gt;Add then we can add the listener to our generator class in setTableDefinition().
Here I also add a constructor to get the options from the template .&lt;/p&gt;

&lt;pre class=&quot;brush: php&quot;&gt;// lib/generator/CommentGenerator.class.php
class CommentGenerator extends Doctrine_Record_Generator
{
    public function __construct($options)
    {
        $this-&amp;gt;addOptions($options);
    }

    public function initOptions()
    {
        $builderOptions = array('suffix' =&amp;gt; '.class.php',
                                'baseClassesDirectory' =&amp;gt; 'base',
                                'generateBaseClasses' =&amp;gt; true,
                                'generateTableClasses' =&amp;gt; true,
                                'baseClassName' =&amp;gt; 'sfDoctrineRecord');

        $this-&amp;gt;setOption('builderOptions', $builderOptions);
        $this-&amp;gt;setOption('className', '%CLASS%Comment');
        $this-&amp;gt;setOption('generateFiles', true);
        $this-&amp;gt;setOption('generatePath', sfConfig::get('sf_lib_dir').DIRECTORY_SEPARATOR.'model'.DIRECTORY_SEPARATOR.'doctrine');
    }

    public function getRelationLocalKey()
    {
        return 'element_id';
    }

    public function buildRelation()
    {
        $this-&amp;gt;buildForeignRelation('Comments');
        $this-&amp;gt;buildLocalRelation('Element');
    }

    public function setTableDefinition()
    {
        $this-&amp;gt;hasColumn('id', 'integer', null, array('primary' =&amp;gt; true, 'autoincrement' =&amp;gt; true));
        $this-&amp;gt;hasColumn('content', 'text', null, array('type' =&amp;gt; 'text'));
        $this-&amp;gt;hasColumn($this-&amp;gt;getRelationLocalKey(), 'integer');
        $this-&amp;gt;hasColumn('user_id', 'integer');
        $this-&amp;gt;hasColumn('created', 'date');

        $this-&amp;gt;addListener(new CommentListener());
    }

    public function setUp()
    {
        $this-&amp;gt;hasOne($this-&amp;gt;getOption('userClass'),
                      array('local' =&amp;gt; 'user_id',
                            'foreign' =&amp;gt; 'id',
                            'onDelete' =&amp;gt; 'cascade'));
    }

    // I override this function and set the generate_once option to true.
    // The file seems to be generated each time the behavior is set up, so if you don't generate the file you don't need this, the class will be loaded in memory and nothing is write physically.
    public function generateClassFromTable(Doctrine_Table $table) 
    {
        $definition = array();
        $definition['columns'] = $table-&amp;gt;getColumns();
        $definition['tableName'] = $table-&amp;gt;getTableName();
        $definition['actAs'] = $table-&amp;gt;getTemplates();
        $definition['generate_once'] = true;

        return $this-&amp;gt;generateClass($definition);
    }

    public function addOptions(array $options)
    {
        $this-&amp;gt;_options = Doctrine_Lib::arrayDeepMerge($this-&amp;gt;_options, $options);
    }
}&lt;/pre&gt;


&lt;p&gt;So now in the Commentable template we just need to load the CommentGenerator as a template's plugin (the Doctrine_Template class also provide the loadGenerator() method).&lt;/p&gt;

&lt;pre class=&quot;brush: php&quot;&gt;// lib/template/Commentable.class.php
class Commentable extends Doctrine_Template
{
    public function __construct(array $options = array())
    {
        parent::__construct($options);

        if($this-&amp;gt;getOption('userClass', null) == null)
        {
            throw new sfException('You need to specify the userClass option for Commentable.');
        }

        $this-&amp;gt;_plugin = new CommentGenerator($this-&amp;gt;getOptions());
    }

    public function setTableDefinition() { }

    public function setUp()
    {
        $this-&amp;gt;hasMany($this-&amp;gt;getTable()-&amp;gt;getComponentName().'Comment as Comments',
                       array('local' =&amp;gt; 'id',
                             'foreign' =&amp;gt; 'element_id'));

        $this-&amp;gt;_plugin-&amp;gt;initialize($this-&amp;gt;getTable());
    }
}&lt;/pre&gt;


&lt;p&gt;Now just run a build-model and a build-sql and then look at the generated files and sql.
&lt;br /&gt;
If you choose to generate files for PostComment you can also build forms and filters, and symfony you sould create the corresponding form and form filter classes.&lt;/p&gt;</description>
    
    
    
          <comments>http://clear-cache.fr/?post/2009/11/26/How-to-create-a-custom-Doctrine-behavior#comment-form</comments>
      <wfw:comment>http://clear-cache.fr/?post/2009/11/26/How-to-create-a-custom-Doctrine-behavior#comment-form</wfw:comment>
      <wfw:commentRss>http://clear-cache.fr/?feed/atom/comments/16</wfw:commentRss>
      </item>
    
  <item>
    <title>Advanced filters with numbers for Doctrine. (fr and en)</title>
    <link>http://clear-cache.fr/?post/2009/11/11/Advanced-filters-with-numbers-for-Doctrine</link>
    <guid isPermaLink="false">urn:md5:edf5d0b9a791c78ebb2cb0b8217fe6e5</guid>
    <pubDate>Fri, 13 Nov 2009 13:07:00 +0100</pubDate>
    <dc:creator>Mathieu</dc:creator>
        <category>Symfony</category>
        <category>Doctrine</category><category>filters</category><category>symfony</category>    
    <description>&lt;p&gt;Dernièrement j'ai du filtrer les nombres de façon supérieur ou inférieure sur un champ &quot;prix&quot;, je me suis alors rendu compte que les filtres de Symfony avec Doctrine ne permettait pas cela, j'ai donc commençait à chercher une solution en me plongeant dans le code de Symfony, sans vraiment chercher sur internet je l'avoue, mais ça fait parfois du bien de voir comment marche l'outil que l'on utilise tous les jours ;) .
&lt;br /&gt;
&lt;br /&gt;
&lt;em&gt;Lately I had to filter some numbers on a price field. The aim was to filter by bigger or smaller than the price value. I realized default Doctrine filters doesn't allow to do that, so I started to search a solution by looking at the Symfony code. I admint I don't really look on the internet to find a solution, but sometimes it's good to look inside the tool we use everyday ;).&lt;/em&gt;&lt;/p&gt;    &lt;p&gt;J'ai donc découvert une façon simple qui est de faire une méthode spéciale pour le champ concerné, qui consiste donc pour un champ &quot;price&quot; de crée la fonction addPriceColumnQuery($query, $field, $value) et qui devra retourner la requête ($query), ainsi de suite... mais ici je ne vous parlerai pas de cette manière de faire, après quelque recherche avec votre ami Google vous trouverez des articles pour ça (enfin si il y a une demande un article peut être possible...)
&lt;br /&gt;
Le problème pour ceci est qu'il faudrait répéter l'opération pour tous les champs auxquels on voudrait appliquer cette modification, donc nous allons nous pencher sur une autre solution.
&lt;br /&gt;
&lt;br /&gt;
&lt;em&gt;I discovered an easy way to do it by creating a specific method for a given field. For exemple for the price field we can create an addPriceColumnQuery($query, $field, $value) which add some selection criteria to the $query and return it. But I won't speak about this manner of doing. You can find some tips about this by searching on Google. (But perhaps I could write a post about it if someone need it.)&lt;/em&gt;
&lt;br /&gt;
&lt;em&gt;If we created a method for a field we need to do the same thing for each field on which we need to apply the change. So let's see another issue.&lt;/em&gt;
&lt;br /&gt;
&lt;br /&gt;
L'idée serait de renseigner dans notre champ l'opérateur voulu suivi de sa valeur, avec la possibilité d'utiliser les opérateurs suivant: &quot;&amp;lt;&quot;, &quot;&amp;gt;&quot;, &quot;&amp;lt;=&quot;, &quot;&amp;gt;=&quot;  et dans le cas où l'on ne saisie pas d'opérateur on utilise &quot;=&quot;. On pourrai donc par exemple saisir: &amp;gt;200.
&lt;br /&gt;
&lt;br /&gt;
&lt;em&gt;The idea would be to create something to allow us to write in the field the operator with the value. We could use the following operators: &quot;&amp;lt;&quot;, &quot;&amp;gt;&quot;, &quot;&amp;lt;=&quot;, &quot;&amp;gt;=&quot; , and in the case of no operator we will use &quot;=&quot;. So for exemple we could fill the field value with: &amp;gt;200.&lt;/em&gt;
&lt;br /&gt;
&lt;br /&gt;
Donc en fouillant d'avantage on remarque que les champs sont &quot;typés&quot; (voir BaseXXXFormFilter::getFields()) par exemple le champ prix est typé &quot;Number&quot; et que pour filtrer ce champ on fait appel à la fonction addNumberQuery dans sfFormFilterDoctrine.
&lt;br /&gt;
Donc l'idée est de créer alors un nouveau type: &quot;NumberOperator&quot;, de créer la fonction addNumberOperatorQuery qui sera appelé par Symfony lors du filtrage.
&lt;br /&gt;
&lt;br /&gt;
''So if we look at BaseXXXFormFilter we can see a getFields() method. This method describe the type for each field of the filter class. For exemple my price field has a &quot;Number&quot; type and so to filter this field symfony will automaticly call the addNumberQuery() method from the sfFormFilterDoctrine class.
&lt;br /&gt;
So here the idea is to create a new type &quot;NumberOperator&quot; with an addNumberOperatorQuery() mehod and use this to filter the price field.''
&lt;br /&gt;
&lt;br /&gt;
Pour ceci on écrit notre fonction dans la classe BaseFormFilterDoctrine et on remercie Doctrine d'avoir pensé à cette classe ;)
&lt;br /&gt;
&lt;br /&gt;
&lt;em&gt;To do this we add addNumberOperatorQuery() in BaseFormFilterDoctrine and we say thanks to Doctrine to generate this class ;)&lt;/em&gt;&lt;/p&gt;
&lt;pre class=&quot;brush: php&quot;&gt;/**
 * Project filter form base class.
 */
abstract class BaseFormFilterDoctrine extends sfFormFilterDoctrine
{
  const TYPE_NUMBER_OPERATOR = 'NumberOperator';

  public function setup()
  {
  }

  public function addNumberOperatorQuery(Doctrine_Query $query, $field, $values)
  {
    $fieldName = $this-&amp;gt;getFieldName($field);
    $fields = $this-&amp;gt;getFields();
    $type = $fields[$field];

    if (is_array($values) &amp;amp;&amp;amp; isset($values['is_empty']) &amp;amp;&amp;amp; $values['is_empty'])
    {
      $query-&amp;gt;addWhere('r.' . $fieldName . ' IS NULL');
    }
    else if (is_array($values) &amp;amp;&amp;amp; isset($values['text']) &amp;amp;&amp;amp; '' != $values['text'])
    {
      if(preg_match('/^(=|&amp;lt;=|&amp;gt;=|&amp;lt;|&amp;gt;)(.+)/', trim($values[&amp;quot;text&amp;quot;]), $matches))
      {
        $query-&amp;gt;addWhere('r.' . $fieldName . ' '.$matches[1].' ?', trim($matches[2]));
      }
      else
      {
        // Call the default function
        if(!method_exists($this, $method = sprintf('add%sQuery', $type)))
        {
          throw new LogicException(sprintf('Unable to filter for the &amp;quot;%s&amp;quot; type.', $type));
        }
        $query = $this-&amp;gt;$method($query, $field, $values);
      }
    }
    return $query;
  }
}&lt;/pre&gt;


&lt;p&gt;Ensuite dans notre classe de filtre il faut modifier le type de notre champ et changer le validateur, un nouveau le cas échéant pour pouvoir aller avec notre nouvelle fonction crée auparavant.
&lt;br /&gt;
&lt;br /&gt;
&lt;em&gt;Then in our filter class we need to change the field's type and also change the validator, a new one in this case just to check that the field value is something like operator+value.&lt;/em&gt;&lt;/p&gt;
&lt;pre class=&quot;brush: php&lt;?php&quot;&gt;/**
 * Product filter form.
 */
class ProductFormFilter extends BaseContactFormFilter
{
  public function configure()
  {
    //...
    // we use the new validator
    $this-&amp;gt;validatorSchema['price'] = new sfValidatorSchemaFilter('text', new myValidatorNumberOperator(array('required' =&amp;gt; false)));
    //...
  }

  public function getFields()
  {
    $fields = parent::getFields();

    $fields['price'] = BaseFormFilterDoctrine::TYPE_NUMBER_OPERATOR; // change the field type
    return $fields;
  }
}&lt;/pre&gt;


&lt;p&gt;Et notre nouveau validateur:
&lt;br /&gt;
&lt;em&gt;And so the new validator:&lt;/em&gt;&lt;/p&gt;
&lt;pre class=&quot;brush: php&quot;&gt;class myValidatorNumberOperator extends sfValidatorNumber
{
  protected function configure($options = array(), $messages = array())
  {
    $this-&amp;gt;setMessage('invalid', '&amp;quot;%value%&amp;quot; is not a number &amp;quot;operator&amp;quot; (accepted symbols: &amp;quot;&amp;quot;, &amp;quot;=&amp;quot;, &amp;quot;&amp;lt;=&amp;quot;, &amp;quot;&amp;gt;=&amp;quot;, &amp;quot;&amp;lt;&amp;quot;, &amp;quot;&amp;gt;&amp;quot;).');
  }

  protected function doClean($value)
  {
    $compareSymbol = '';
    if(preg_match('/^(=|&amp;lt;=|&amp;gt;=|&amp;lt;|&amp;gt;)(.+)/', trim($value), $matches))
    {
      $compareSymbol = $matches[1];
      $numberValue   = $matches[2];
    }
    else
    {
      $numberValue = $value;
    }

    try {
      $cleanNumber = parent::doClean($numberValue);
    }
    catch (sfValidatorError $e) {
      throw new sfValidatorError($this, 'invalid', array('value' =&amp;gt; $value));
    }

    return $compareSymbol.$cleanNumber;
  }
}&lt;/pre&gt;


&lt;p&gt;Ensuite pour pouvoir filtrer un prix il suffit de renseigner dans notre filtre pour un prix supérieur ou égal à 200 par exemple : &quot;&amp;gt;=200&quot;.
&lt;br /&gt;
&lt;br /&gt;
&lt;em&gt;Now to filter the price filed you can use values such as:  &quot;&amp;gt;=200&quot;.&lt;/em&gt;
&lt;br /&gt;
&lt;br /&gt;
Et en faisant cet article j'ai eu une autre idée faire un filtre qui filtrerai également une valeur suivant deux critères, explications rechercher par exemple un prix supérieur à 100 et inférieur à 300 : &quot;100&amp;lt;x&amp;lt;300&quot; à vos claviers ;)
&lt;br /&gt;
&lt;br /&gt;
Traduced by eNk`&lt;/p&gt;</description>
    
    
    
          <comments>http://clear-cache.fr/?post/2009/11/11/Advanced-filters-with-numbers-for-Doctrine#comment-form</comments>
      <wfw:comment>http://clear-cache.fr/?post/2009/11/11/Advanced-filters-with-numbers-for-Doctrine#comment-form</wfw:comment>
      <wfw:commentRss>http://clear-cache.fr/?feed/atom/comments/15</wfw:commentRss>
      </item>
    
  <item>
    <title>Add new tasks in your Symfony project</title>
    <link>http://clear-cache.fr/?post/2009/09/30/Symfony-Task</link>
    <guid isPermaLink="false">urn:md5:dcf134a35656e051dd58e93a0e451b4d</guid>
    <pubDate>Fri, 16 Oct 2009 11:10:00 +0200</pubDate>
    <dc:creator>eNk`</dc:creator>
        <category>Symfony</category>
        <category>symfony</category><category>task</category>    
    <description>&lt;p&gt;So for my very first post in english I will talk about how to add a new task in symfony. Task are very useful to handle a lot of data and can be launched manually or placed in a cron job.&lt;br /&gt;
&lt;br /&gt;
I started to create task on a new project, in the past I used to create a batch folder in my symfony project. In this folder I created a symfony.inc.php to load symfony and others php files which use the symfony.inc.php to handle data. So this way of creating task work, but symfony provide the sfBaseTask class so why don't use it ? This class allow you to add task to your symfony project in addition to those provided by the framework.&lt;/p&gt;    &lt;p&gt;Let's see how does it work through a little exemple. The follwing exemple is quite stupid, but it's just to show the way you can create a task. So the aim of this task is to display a sentence given as an arguments for each user from your data base.&lt;br /&gt;
Basically you will have to create a class which extend from sfBaseTask and use the configure() method to set the name, the name space, the options and the arguments of your task. Then you will have to implement an execute() function to handle your data.&lt;/p&gt;

&lt;pre class=&quot;brush: php&quot;&gt;// myProject/lib/task/SayHelloTask.class.php

class SayHelloTask extends sfBaseTask
{
    const ALL_USERS = -1;

    public function configure()
    {
        // task's name, description and aliases
        $this-&amp;gt;name = &amp;quot;say-hello&amp;quot;;
        $this-&amp;gt;namespace = &amp;quot;mytasks&amp;quot;;
        $this-&amp;gt;briefDescription = &amp;quot;A little description of this task.&amp;quot;;
        $this-&amp;gt;detailedDescription = &amp;quot;Detailed description, blablablabla blabla blabla bla.&amp;quot;;
        $this-&amp;gt;aliases = array('mytasks-say-hello', 'sh');

        // options
        $this-&amp;gt;addOptions(array(new sfCommandOption('env', null, sfCommandOption::PARAMETER_REQUIRED, 'The environement', 'prod')));

        // arguments
        $this-&amp;gt;addArguments(array(new sfCommandArgument('message', sfCommandArgument::OPTIONAL, 'your message', 'Hello %s :)'),
                                  new sfCommandArgument('number', sfCommandArgument::OPTIONAL, 'how many users ?', self::ALL_USERS)));
    }

    public function execute($arguments = array(), $options = array())
    {
        // data base manager to open a connection
        $sfDatabaseManager = new sfDatabaseManager($this-&amp;gt;configuration);

        if(substr_count($arguments['message'], '%s') === 1)
        {
            // get users from data base
            $users = UserTable::getAll();

            $end = 0;
			$nbUsers = count($users);
			if($arguments['number'] === self::ALL_USERS)
			{
				$end = $nbUsers;
			}
			else
			{
                $nb = (int) $arguments['number'];
                $end = ($nb &amp;lt;= $nbUsers) ? $nb : $nbUsers;
			}

			$i = 0;
			while($i&amp;lt;$end)
			{
                $this-&amp;gt;log(sprintf($arguments['message'], $users[$i]-&amp;gt;getFullName()));
                $i++;
		    }
		}
		else
		{
			$this-&amp;gt;logBlock(&amp;quot;Only one '%s' in the message.&amp;quot;, 'ERROR');
		}
    }
}&lt;/pre&gt;


&lt;p&gt;To run this task you just have to execute this command line :&lt;/p&gt;

&lt;pre class=&quot;brush: plain&quot;&gt;$ php symfony mytasks:say-hello

OR

$ php symfony mytasks:say-hello &amp;quot;Hi %s :p&amp;quot;

OR

$ php symfony sh &amp;quot;Hi %s :p&amp;quot;&lt;/pre&gt;</description>
    
    
    
          <comments>http://clear-cache.fr/?post/2009/09/30/Symfony-Task#comment-form</comments>
      <wfw:comment>http://clear-cache.fr/?post/2009/09/30/Symfony-Task#comment-form</wfw:comment>
      <wfw:commentRss>http://clear-cache.fr/?feed/atom/comments/14</wfw:commentRss>
      </item>
    
  <item>
    <title>Clear Cache</title>
    <link>http://clear-cache.fr/?post/2009/07/29/Clear-Cache</link>
    <guid isPermaLink="false">urn:md5:32dfb86234995deab7fa6cb50cc5c8cf</guid>
    <pubDate>Mon, 03 Aug 2009 16:42:00 +0200</pubDate>
    <dc:creator>eNk`</dc:creator>
        <category>Symfony</category>
        <category>symfony</category>    
    <description>&lt;p&gt;Bon il fallait bien le faire, donc le voici. Le mini billet sur notre commande préférée LE &quot;symfony cc&quot;. &lt;br /&gt;
cache:clear de son nom complet et &quot;cc&quot;  pour les intimes et gros flemmard que nous sommes =]. Qui a déjà écris &quot;cache:clear&quot; en entier&amp;nbsp;? Dénoncez vos collègues, et pour la peine ils devront payer une tournée au prochain sfPot.&lt;br /&gt;&lt;/p&gt;    &lt;p&gt;Donc évidement cette commande sert à vider le cache d'un projet Symfony et offre la possibilité de vider la totalité ou juste une partie du cache.
&lt;br /&gt;
Options de la commande cache:clear&amp;nbsp;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;--app: précise le nom de l'application pour laquelle le cache doit être vidé.&lt;/li&gt;
&lt;li&gt;--type: type du cache à vider, possibilités&amp;nbsp;: template, config, i18n, routing.&lt;/li&gt;
&lt;li&gt;--env: précise l'environnement pour lequel le cache doit être vidé.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Exemple&amp;nbsp;:&lt;/p&gt;

&lt;pre class=&quot;brush: plain&quot;&gt;// Vide tout le cache
$ php symfony cache:clear

// Vide le cache HTML de l'application frontend.
$ php symfony cache:clear --app=frontend --type=template

// Vide le cache de configuration de l'application frontend pour l'environnement prod.
$ php symfony cache:clear --app=frontend --type=config --env=prod&lt;/pre&gt;


&lt;p&gt;Bon cc&amp;nbsp;!&lt;/p&gt;</description>
    
    
    
          <comments>http://clear-cache.fr/?post/2009/07/29/Clear-Cache#comment-form</comments>
      <wfw:comment>http://clear-cache.fr/?post/2009/07/29/Clear-Cache#comment-form</wfw:comment>
      <wfw:commentRss>http://clear-cache.fr/?feed/atom/comments/12</wfw:commentRss>
      </item>
    
  <item>
    <title>Les Criteria c'est trop nul :p</title>
    <link>http://clear-cache.fr/?post/2009/04/26/Les-Criteria-c-est-trop-nul-%3Ap</link>
    <guid isPermaLink="false">urn:md5:7e9eb3d4307ea59dae6461365ba3569a</guid>
    <pubDate>Thu, 09 Jul 2009 13:35:00 +0200</pubDate>
    <dc:creator>eNk`</dc:creator>
        <category>Symfony</category>
        <category>PDO</category><category>Propel</category><category>symfony</category>    
    <description>&lt;p&gt;Derrière ce titre un peu &quot;trollesque&quot; se cache en fait un petit billet dans lequel nous allons voir comment faire des requêtes avec &lt;a href=&quot;http://clear-cache.fr/?tag/Propel&quot;&gt;Propel&lt;/a&gt; 1.3 en utilisant directement &lt;a href=&quot;http://clear-cache.fr/?tag/PDO&quot;&gt;PDO&lt;/a&gt;, donc sans passer par la case Criteria/Criterion, et hydrater les objets correspondant.&lt;br /&gt;
&lt;br /&gt;
Prenons un exemple avec le schéma suivant, dans lequel nous avons des utilisateurs pouvant écrire des posts et faire parti de groupes.&lt;/p&gt;    &lt;pre class=&quot;brush: plain&quot;&gt;propel:
  user:
    _attributes: { phpName: User }
    id:          ~
    login:       { type: varchar, size: 255, required: true, index: unique }
    email:       { type: varchar, size: 255 }
    created_at:  ~
    updated_at:  ~

  post:
    _attributes: { phpName: Post }
    id:          ~
    content:     { type: longvarchar }
    user_id:     { type: integer, foreignTable: user, foreignReference: id, onDelete: cascade }
    created_at:  ~
    updated_at:  ~

  group:
    _attributes: { phpName: Group }
    id:          ~
    name:        { type: varchar, size: 255 }
    created_at:  ~
    updated_at:  ~

  user_group:
    _attributes: { phpName: UserGroup }
    group_id:    { type: integer, required: true, primaryKey: true, foreignTable: group, foreignReference: id, onDelete: cascade }
    user_id:     { type: integer, required: true, primaryKey: true, foreignTable: user, foreignReference: id, onDelete: cascade }
    created_at:  ~
    updated_at:  ~&lt;/pre&gt;


&lt;p&gt;Commençons par voir un exemple très simple avec une requête sur une seule table. Nous allons donc créer une méthode getAll() dans UserPeer pour récupérer tous les utilisateurs. Pour réaliser cela c'est très simple il suffit de récupérer la connexion, préparer un statment avec notre requête SQL, l'exécuter et hydrater les objets à l'aide de le méthode populateObjects() de la classe BaseUserPeer. Au passage toutes les classes BaseXXXPeer générées par Propel possèdent une méthode populateObjects(PDOStament $stmt) permettant d'hydrater des objets de la classe XXX.&lt;/p&gt;

&lt;pre class=&quot;brush: php&quot;&gt;/**
 * Get all users.
 *
 * @return array - An array of User objects
 */
public static function getAll()
{
    $result = null;
    
	// query
    $sql = &amp;quot;SELECT * FROM user ORDER BY user.CREATED_AT DESC&amp;quot;;
    
	// get the connexion
    $con = Propel::getConnection();
	
	// prepare a statment and execute the query
    $pdoStmt = $con-&amp;gt;prepare($sql);
    $pdoStmt-&amp;gt;execute();
	
	// hydrate objects from the resultset
    $result = UserPeer::populateObjects($pdoStmt);
    
    return $result;
}&lt;/pre&gt;


&lt;p&gt;Voyons maintenant un exemple de requête avec une jointure entre les tables user et post afin de sélectionner un utilisateur avec tous ses posts. Nous allons donc créer une méthode getUserByIdWithPost() dans laquelle, comme pour getAll(), nous allons créer un statment, l'exécuter puis hydrater le résultat. Cependant il serait pratique d'hydrater les objets User et Post en même temps et de les lier. Donc au lieu d'utiliser la méthode populateObjects() nous allons créer une méthode populateObjectsWithPost(). Cette méthode va évidement faire le même traitement que populateObjects() et va en plus hydrater les objets Post puis les relier à l'objet User.&lt;br /&gt;
Pour hydrater un objet avec Propel il suffit d'utiliser les méthodes suivantes des objets BaseXXXPeer:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;getPrimaryKeyHashFromRow(): donne la clé primaire d'un objet sous forme d'une chaine de caractères unique.&lt;/li&gt;
&lt;li&gt;getInstanceFromPool(): retourne un objet (ou null) présent dans le pool d'instance en fonction de sa clé (généré par getPrimaryKeyHashFromRow() ).&lt;/li&gt;
&lt;li&gt;addInstanceToPool(): ajoute un objet au pool d'instance.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ainsi que la méthode hydrate() des objets BaseXXX qui permet d'hydrater un objet.&lt;/p&gt;

&lt;pre class=&quot;brush: php&quot;&gt;/**
 * Get a user with all post.
 * 
 * @param int $userId - The user id.
 * @return User
 */
public static function getUserByIdWithPost($userId)
{
    $user = null;
    
	// query
    $sql = &amp;quot;SELECT * FROM user LEFT JOIN post ON user.ID = post.USER_ID WHERE user.ID = :user_id&amp;quot;;
    
	// get the connexion
    $con = Propel::getConnection();
	
	// prepare a statment and execute the query
    $pdoStmt = $con-&amp;gt;prepare($sql);
	$pdoStmt-&amp;gt;bindValue('user_id', $userId, PDO::PARAM_INT);
    $pdoStmt-&amp;gt;execute();
	
	// hydrate objects from the resultset
    $result = UserPeer::populateObjectsWithPost($pdoStmt);
    
	if(isset($result[0])) { $user = $result[0]; }
	
    return $user;
}

/**
 * Hydrate some User objects with their related Post.
 * 
 * @param PDOStatement $pdoStmt - A statment PDO.
 * @return array
 */
public static function populateObjectsWithPost(PDOStatement $pdoStmt)
{
    $results = array();
    
    // set the class once to avoid overhead in the loop
    $cls = UserPeer::getOMClass();
    $cls = substr('.'.$cls, strrpos('.'.$cls, '.') + 1);
    // populate the object(s)
    while ($row = $pdoStmt-&amp;gt;fetch(PDO::FETCH_NUM))
    {
        $key = UserPeer::getPrimaryKeyHashFromRow($row, 0);
        if (null !== ($obj = UserPeer::getInstanceFromPool($key)))
        {
            $nextColToStart = UserPeer::NUM_COLUMNS - UserPeer::NUM_LAZY_LOAD_COLUMNS;
        }
        else
        {
            $obj = new $cls();
            $nextColToStart = $obj-&amp;gt;hydrate($row);
            UserPeer::addInstanceToPool($obj, $key);
        } // if key exists

        // hydrate a post object and associate it to the user
        if(isset($row[$nextColToStart]))
        {
            $postKey = PostPeer::getPrimaryKeyHashFromRow($row, $nextColToStart);
            if(null === ($post = PostPeer::getInstanceFromPool($postKey)))
            {
                $post = new Post();
                $post-&amp;gt;hydrate($row, $nextColToStart);
                PostPeer::addInstanceToPool($post, $postKey);
            }
            $obj-&amp;gt;addPost($post);
        }

        // add the user only if not already added.
        if(in_array($obj, $results) === false) { $results[] = $obj; }
    }
		
    $pdoStmt-&amp;gt;closeCursor();

    return $results;
}&lt;/pre&gt;


&lt;p&gt;Voici maintenant un exemple avec une requête pour récupérer un utilisateur avec tous ses groupes, donc avec une relation n:m entre user et group. Rien de nouveau au niveau de la préparation et exécution du statment. Et au niveau de l'hydratation des objets il faut relier les objets Group à l'objet UserGroup correspondant qui va lui même être rattaché à un objet User.&lt;/p&gt;

&lt;pre class=&quot;brush: php&quot;&gt;/**
 * Get a user with all related groups
 * 
 * @param int $userId - The user id.
 * @return User
 */
public static function getUserByIdWithGroups($userId)
{
    $user = null;
    
	// query
    $sql = &amp;quot;SELECT user.*, group.*, user_group.*&amp;quot;;
	$sql .= &amp;quot; FROM user LEFT JOIN user_group ON user.ID = user_group.USER_ID&amp;quot;;
	$sql .= &amp;quot; LEFT JOIN group ON user_group.GROUP_ID = group.ID&amp;quot;;
    $sql .= &amp;quot; WHERE user.ID = :user_id&amp;quot;;
    
	// get the connexion
    $con = Propel::getConnection();
	
	// prepare a statment and execute the query
    $pdoStmt = $con-&amp;gt;prepare($sql);
	$pdoStmt-&amp;gt;bindValue('user_id', $userId, PDO::PARAM_INT);
    $pdoStmt-&amp;gt;execute();
	
	// hydrate objects from the resultset
    $result = UserPeer::populateObjectsWithGroups($pdoStmt);
    
	if(isset($result[0])) { $user = $result[0]; }
	
    return $user;
}


/**
 * Hydrate some User objects with their related Group.
 * 
 * @param PDOStatement $pdoStmt - A statment PDO.
 * @return array
 */
public static function populateObjectsWithUserGroups(PDOStatement $pdoStmt)
{
    $results = array();
    
    // set the class once to avoid overhead in the loop
    $cls = UserPeer::getOMClass();
    $cls = substr('.'.$cls, strrpos('.'.$cls, '.') + 1);
    // populate the object(s)
    while ($row = $pdoStmt-&amp;gt;fetch(PDO::FETCH_NUM))
    {
        $key = UserPeer::getPrimaryKeyHashFromRow($row, 0);
        if (null !== ($obj = UserPeer::getInstanceFromPool($key)))
        {
            $nextColToStart = UserPeer::NUM_COLUMNS - UserPeer::NUM_LAZY_LOAD_COLUMNS;
        }
        else
        {
            $obj = new $cls();
            $nextColToStart = $obj-&amp;gt;hydrate($row);
            UserPeer::addInstanceToPool($obj, $key);
        } // if key exists

		// hydrate a group object
        if(isset($row[$nextColToStart]))
        {
            $groupKey = GroupPeer::getPrimaryKeyHashFromRow($row, $nextColToStart);
            if(null === ($group = GroupPeer::getInstanceFromPool($groupKey)))
            {
                $group = new Group();
                $group-&amp;gt;hydrate($row, $nextColToStart);
                GroupPeer::addInstanceToPool($group, $groupKey);
            }
        }

        $nextColToStart += GroupPeer::NUM_COLUMNS - GroupPeer::NUM_LAZY_LOAD_COLUMNS;
		
        // hydrate a userGroup object
        if(isset($row[$nextColToStart]))
        {
            $userGroupKey = UserGroupPeer::getPrimaryKeyHashFromRow($row, $nextColToStart);
            if(null === ($userGroup = UserGroupPeer::getInstanceFromPool($userGroupKey)))
            {
                $userGroup = new UserGroup();
                $userGroup-&amp;gt;hydrate($row, $nextColToStart);
                UserGroupPeer::addInstanceToPool($userGroup, $userGroupKey);
            }
        }

        if(isset($userGroup, $group))
        {
            $userGroup-&amp;gt;setGroup($group);
            $obj-&amp;gt;addUserGroup($userGroup);
        }
       
        // add the user only if not already added.
        if(in_array($obj, $results) === false) { $results[] = $obj; }
    }
		
    $pdoStmt-&amp;gt;closeCursor();

    return $results;
}&lt;/pre&gt;


&lt;p&gt;Nous avons créé dans cette exemple les méthodes populateObjectsWithUserGroups() et populateObjectsWithPost() pour hydrater le résultat des requêtes. On remarque que le mécanisme d'hydratation des objets est le même pour toutes les classes générées par Propel, on peut donc factoriser du code afin de ne pas écrire des méthodes populateObjectsXXXXX() à ralonge.&lt;br /&gt;
Note: dans la méthode hydrateObject() qui va permettre d'hydrater des objets &lt;a href=&quot;http://clear-cache.fr/?tag/Propel&quot;&gt;Propel&lt;/a&gt;, j'appelle une méthode getPeerClass() sur la classe $class (dans notre exemple la variable $class peut être égale à User, Post, Group ou encore UserGroup). getPeerClass() est une méthode public static qui retourne le nom de la classe peer associée (par défaut on va retourner la constante PEER des classes BaseXXX).&lt;/p&gt;

&lt;pre class=&quot;brush: php&quot;&gt;// getPeerClass method exemple
public static function getPeerClass()
{
    return parent::PEER;
}

// class with useful methods.
class PropelTools
{
    /**
     * Hydrate a $class object from a result row of a result set and link this objet to his parent.
     * 
     * @param array $row
     * @param int $startCol
     * @param string $class
     * @param BaseObject $parentObject [optionnal]
     * @param string $addMethod [optionnal]
     * @return Object
     */
    public static function hydrateObject($row, $startCol, $class, $parentObject = null, $addMethod = null)
    {
        $myObject = null;

        // get peer class name
        $peerClass = call_user_func(array($class, 'getPeerClass'));
    
	if(isset($row[$startCol]) &amp;amp;&amp;amp; $peerClass !== false)
        {
            $myObjectKey = call_user_func(array($peerClass, 'getPrimaryKeyHashFromRow'), $row, $startCol);
            if( null === ($myObject = call_user_func(array($peerClass, 'getInstanceFromPool'), $myObjectKey)) )
            {
                $myObject = new $class();
                $myObject-&amp;gt;hydrate($row, $startCol);
                call_user_func(array($classPeer, 'addInstanceToPool'), $myObject, $myObjectKey);
            }
		
            if(isset($parentObject, $addMethod)) 
            {
                if(is_callable(array($parentObject, $addMethod), true)) { $parentObject-&amp;gt;$addMethod($myObject);  }
                else { throw new sfException(sprintf(&amp;quot;Can't call method %s() on parent object.&amp;quot;, $addMethod)); }
            }
        }

        return $myObject;
    }
}&lt;/pre&gt;


&lt;p&gt;Et voici ce que donnerai la méthode populateObjectsWithUserGroups() en utilisant la méthode hydrateObject().&lt;/p&gt;

&lt;pre class=&quot;brush: php&quot;&gt;public static function populateObjectsWithUserGroups(PDOStatement $pdoStmt)
{
    $results = array();
    
    // set the class once to avoid overhead in the loop
    $cls = UserPeer::getOMClass();
    $cls = substr('.'.$cls, strrpos('.'.$cls, '.') + 1);
    // populate the object(s)
    while ($row = $pdoStmt-&amp;gt;fetch(PDO::FETCH_NUM))
    {
        $key = UserPeer::getPrimaryKeyHashFromRow($row, 0);
        if (null !== ($obj = UserPeer::getInstanceFromPool($key)))
        {
            $nextColToStart = UserPeer::NUM_COLUMNS - UserPeer::NUM_LAZY_LOAD_COLUMNS;
        }
        else
        {
            $obj = new $cls();
            $nextColToStart = $obj-&amp;gt;hydrate($row);
            UserPeer::addInstanceToPool($obj, $key);
        } // if key exists
        
        // hydrate a userGroup and group object
        $group = PropelTools::hydrateObject($row, $nextColToStart, 'Group', $userGroup, 'setGroup');
        $nextColToStart += GroupPeer::NUM_COLUMNS - GroupPeer::NUM_LAZY_LOAD_COLUMNS;
        $userGroup = PropelTools::hydrateObject($row, $startCol, 'UserGroup', $obj, 'addUserGroup');
				
        // add the user only if not already added.
        if(in_array($obj, $results) === false) { $results[] = $obj; }
    }
		
    $pdoStmt-&amp;gt;closeCursor();

    return $results;
}&lt;/pre&gt;


&lt;p&gt;Liens utiles:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;La doc de PDO &lt;a href=&quot;http://fr.php.net/manual/fr/book.pdo.php&quot;&gt;ici&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;La doc de Propel 1.3 &lt;a href=&quot;http://propel.phpdb.org/trac/wiki/Users/Documentation/1.3&quot;&gt;ici&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
    
    
    
          <comments>http://clear-cache.fr/?post/2009/04/26/Les-Criteria-c-est-trop-nul-%3Ap#comment-form</comments>
      <wfw:comment>http://clear-cache.fr/?post/2009/04/26/Les-Criteria-c-est-trop-nul-%3Ap#comment-form</wfw:comment>
      <wfw:commentRss>http://clear-cache.fr/?feed/atom/comments/5</wfw:commentRss>
      </item>
    
  <item>
    <title>Extension du Doctrine Routing Object</title>
    <link>http://clear-cache.fr/?post/2009/05/04/Extension-du-Doctrine-Routing-Object</link>
    <guid isPermaLink="false">urn:md5:34f553aa87883bd559b81b5c39f694a7</guid>
    <pubDate>Mon, 18 May 2009 10:12:00 +0200</pubDate>
    <dc:creator>Mathieu</dc:creator>
        <category>Symfony</category>
        <category>doctrine</category>    
    <description>&lt;p&gt;Pour mon premier billet je vais vous parler du routing de symfony, pour être plus précis du routing des objets &lt;a href=&quot;http://clear-cache.fr/?tag/doctrine&quot;&gt;doctrine&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
Le problème auquel je me suis confronté était le suivant&amp;nbsp;: La classe &lt;em&gt;sfDoctrineRoute&lt;/em&gt; recherche à chaque fois, s'il existe une méthode pour récupérer le paramètre de l'objet demandé dans la règle de routage, même si le paramètre est renseigné manuellement.&lt;br /&gt;
&lt;br /&gt;
Exemple du problème :&lt;br /&gt;
Nous avons des articles composés de plusieurs pages.&lt;br /&gt;
On a auparavant créé une méthode &lt;em&gt;getRank()&lt;/em&gt;, qui retourne la première page de l'article, celle-ci est dédiée pour le routing&lt;/p&gt;    &lt;p&gt;&lt;br /&gt;
Le fichier de routing&lt;br /&gt;&lt;/p&gt;
&lt;pre&gt;
article_show:
  url:     /article/:id/page/:rank.html
  class:   sfDoctrineRoute
  options: { model: Article, type: object }
  param:   { module: article, action: show }
  requirements:
    id:      \d+
    rank:    \d+
    sf_method: [get]
&lt;/pre&gt;

&lt;p&gt;Lien généré ($article est une instance de Article)&amp;nbsp;:&lt;/p&gt;
&lt;pre&gt;
url_for('article_show', $article)
Résultat attendu : /article/1/page/1.html
Résultat obtenu  : /article/1/page/1.html
&lt;/pre&gt;
&lt;pre&gt;
url_for('article_show', array('sf_subject' =&amp;gt; $article, 'rank' =&amp;gt; 2))
Résultat attendu : /article/1/page/2.html
Résultat obtenu  : /article/1/page/1.html
&lt;/pre&gt;

&lt;p&gt;On remarque donc que la règle de routing n'a que faire de notre paramètre rank, il sera toujours alimenté par la méthode getRank() de l'objet Article.&lt;br /&gt;
&lt;br /&gt;
Pour réglé ce problème auquel je n'ai pas trouvé de solution sur la Mailing List, j'ai donc décidé d'étendre la classe sfDoctrineRoute, pour que lorsque l'on fourni un paramètre manuellement celui-ci ne soit pas écrasé, par celui récupéré par la méthode de l'objet.&lt;br /&gt;
&lt;br /&gt;&lt;/p&gt;
&lt;pre class=&quot;brush: php&quot;&gt;/**
 * myDoctrineRoute
 */
class myDoctrineRoute extends sfDoctrineRoute
{
  protected function convertObjectToArray($object)
  {
    if (is_array($object))
    {
      if (!isset($object['sf_subject']))
      {
        return $object;
      }

      $parameters = $object;
      $object = $parameters['sf_subject'];
      unset($parameters['sf_subject']);
    }
    else
    {
      $parameters = array();
    }

    if(isset($this-&amp;gt;options['erase']) &amp;amp;&amp;amp; $this-&amp;gt;options['erase'] === false)
    {
      $response = array_merge($parameters, $this-&amp;gt;doConvertObjectToArray($object));
    }
    else
    {
      // We check the parameters value and unset them if they are null
      // We define an array of null values and we check if exist one to do the foreach, to unset the null values
      $nullValues = array(null, 'null');
      $findNull   = true;
      $iFindNull  = 0;
      while($iFindNull &amp;lt; count($nullValues) &amp;amp;&amp;amp; $findNull === false)
      {
        $findNull = in_array($nullValues[$iFindNull], $parameters);
        $iFindNull++;
      }

      if($findNull)
      {
        foreach($parameters as $parameterKey =&amp;gt; $parameterValue)
        {
          if(in_array($parameterValue, $nullValues))
          {
            unset($parameters[$parameterKey]);
          }
        }
      }
      $response = array_merge($this-&amp;gt;myDoConvertObjectToArray($object, $parameters), $parameters);
    }

    return $response;
  }

  protected function myDoConvertObjectToArray($object, $defaultParameters = array())
  {
    if (isset($this-&amp;gt;options['convert']) || method_exists($object, 'toParams'))
    {
      return parent::doConvertObjectToArray($object);
    }

    $className = $this-&amp;gt;options['model'];

    $parameters = array();

    foreach ($this-&amp;gt;getRealVariables() as $variable)
    {
      if(!in_array($variable, array_keys($defaultParameters)))
      {
        try {
          $parameters[$variable] = $object-&amp;gt;$variable;
        } catch (Exception $e) {
          try {
            $method = 'get'.sfInflector::camelize($variable);
            $parameters[$variable] = $object-&amp;gt;$method;
          } catch (Exception $e) {}
        }
      }
    }
    return $parameters;
  }
}&lt;/pre&gt;

&lt;p&gt;&lt;br /&gt;
&lt;br /&gt;
Après cela il faut donc changé notre de règle de routing, avec en bonus un paramètre pour définir le comportement de votre règle, si celle-ci doit écrasé les paramètres renseignés manuellement ou non, à l'aide de l'option erase qui par défaut est à true:&lt;br /&gt;
&lt;br /&gt;&lt;/p&gt;
&lt;pre&gt;
article_show:
  url:     /article/:id/page/:rank.html
  class:   myDoctrineRoute
  options: { model: Story, type: object, erase: true }
  param:   { module: story, action: index }
  requirements:
    id:      \d+
    rank:    \d+
    sf_method: [get]
&lt;/pre&gt;

&lt;p&gt;&lt;br /&gt;
N'hésitez pas maintenant à me faire part de vos impressions, car je pense qu'il existe déjà une solution et j'ai d'ailleurs trouvé cela étrange de ne pas la trouver.&lt;/p&gt;</description>
    
    
    
          <comments>http://clear-cache.fr/?post/2009/05/04/Extension-du-Doctrine-Routing-Object#comment-form</comments>
      <wfw:comment>http://clear-cache.fr/?post/2009/05/04/Extension-du-Doctrine-Routing-Object#comment-form</wfw:comment>
      <wfw:commentRss>http://clear-cache.fr/?feed/atom/comments/7</wfw:commentRss>
      </item>
    
  <item>
    <title>SQLSTATE[HY000]: General error: 1005</title>
    <link>http://clear-cache.fr/?post/2009/05/15/SQLSTATE%5BHY000%5D%3A-General-error%3A-1005</link>
    <guid isPermaLink="false">urn:md5:47dbeb944a862ea9f64f9d5cfd4da846</guid>
    <pubDate>Fri, 15 May 2009 10:50:00 +0200</pubDate>
    <dc:creator>Stéphane.p</dc:creator>
        <category>Symfony</category>
        <category>Doctrine</category><category>MYSQL</category><category>symfony</category>    
    <description>&lt;pre&gt;
SQLSTATE[HY000]: General error: 1005 Can't create table './ma_table/#sql-6d79_2523.frm' (errno: 150). Failing Query: ALTER TABLE table2 ADD FOREIGN KEY (table1) 
REFERENCES table1(id) ON DELETE SET NULL
&lt;/pre&gt;


&lt;p&gt;Voilà une erreur qui m'est apparue plusieurs fois, assez embêtante lorsqu'on ne connait pas la solution.&lt;br /&gt;&lt;/p&gt;    &lt;p&gt;Il s'agit bien d'une erreur &lt;strong&gt;&lt;a href=&quot;http://clear-cache.fr/?tag/MYSQL&quot;&gt;MYSQL&lt;/a&gt;&lt;/strong&gt; et pour la résoudre nous devrons donc passer par le/les schema.yml. &lt;br /&gt;
&lt;ins&gt;&lt;strong&gt;Exemple:&lt;/strong&gt;&lt;/ins&gt;&lt;br /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;config/&lt;/li&gt;
&lt;li&gt;doctrine/&lt;/li&gt;
&lt;li&gt;schema.yml&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;brush: plain&quot;&gt;Table1:
  columns:
    name:    { type: string(255), notblank: true }

Table2:
  columns:
    table_one_id: { type: integer(11) }
  relations:
    Table1: { local: table_one_id, foreign: id, onDelete: SET NULL }&lt;/pre&gt;


&lt;p&gt;Dans ce cas nous aurons l'erreur précédemment citée. La syntaxe est correcte, donc le problème ne devrait pas être. &lt;br /&gt;
Cependant, &lt;em&gt;table_one_id&lt;/em&gt; étant un &lt;em&gt;integer(11)&lt;/em&gt;, et &lt;em&gt;table1.id&lt;/em&gt; (généré automatiquement) étant un &lt;em&gt;bigint(20),&lt;/em&gt; c'est ceci qui provoque cette erreur &lt;ins&gt;mysql&lt;/ins&gt;. &lt;br /&gt;&lt;/p&gt;


&lt;p&gt;Pour ma part, tous les &lt;em&gt;integer&lt;/em&gt; étant des &lt;em&gt;foreigns&lt;/em&gt; sont renseignés comme ceci:&lt;/p&gt;

&lt;pre class=&quot;brush: plain&quot;&gt;table_one_id: { type: integer }&lt;/pre&gt;


&lt;p&gt;Vous aurez ce genre de problème lorsque vous téléchargerez des plugins &lt;a href=&quot;http://clear-cache.fr/?tag/symfony&quot;&gt;symfony&lt;/a&gt;, je l'ai eu il me semble sur &lt;em&gt;'swDoctrineAssetsLibraryPlugin&lt;/em&gt;'.&lt;/p&gt;</description>
    
    
    
          <comments>http://clear-cache.fr/?post/2009/05/15/SQLSTATE%5BHY000%5D%3A-General-error%3A-1005#comment-form</comments>
      <wfw:comment>http://clear-cache.fr/?post/2009/05/15/SQLSTATE%5BHY000%5D%3A-General-error%3A-1005#comment-form</wfw:comment>
      <wfw:commentRss>http://clear-cache.fr/?feed/atom/comments/9</wfw:commentRss>
      </item>
    
  <item>
    <title>Custom validator sous symfony 1.2</title>
    <link>http://clear-cache.fr/?post/2009/04/26/Custom-validator-sous-symfony-1.2</link>
    <guid isPermaLink="false">urn:md5:84f6059c277075c6f0ef2e21cbb50994</guid>
    <pubDate>Thu, 07 May 2009 09:25:00 +0200</pubDate>
    <dc:creator>eNk`</dc:creator>
        <category>Symfony</category>
        <category>formulaires symfony</category><category>sfValidator</category><category>symfony</category>    
    <description>&lt;p&gt;Dans ce billet nous allons voir comment créer un sfValidator custom utilisable avec le framework de formulaire.&lt;br /&gt;
&lt;br /&gt;
Dans cet exemple nous allons créer un validateur permettant de vérifier si un pseudo est déjà existant dans la base de données. Ce validateur pourra par exemple être utilisé sur un formulaire d'inscription afin de forcer les utilisateurs à choisir un pseudo qui n'existe pas encore.&lt;br /&gt;
&lt;br /&gt;
Commençons par créer une classe PseudoValidator qui hérite de sfValidatorBase, dans laquelle nous allons redéfinir les méthodes configure() et doClean(). La méthode configure() va permettre de définir les options et les messages de notre validateur. La méthode doClean() va quand à elle être utilisée pour valider la valeur du champ.Vous pouvez par exemple créer cette classe dans le répertoire _my_project_/lib/validator.&lt;br /&gt;&lt;/p&gt;    &lt;pre class=&quot;brush: php&quot;&gt;class PseudoValidator extends sfValidatorBase
{
    public function configure($options = array(), $messages = array()) 
    {
        // @todo ajout d'options et de messages
    }

    public function doClean($value)
    {
        // @todo validation de la valeur du champs $value
    }
}&lt;/pre&gt;

&lt;p&gt;Ajoutons maintenant le code nécessaire dans nos deux méthodes afin de valider le champ et de définir le message qui sera affiché en cas d'échec de la validation.&lt;br /&gt;
Dans un premier temps nous allons définir un message &quot;invalid&quot; dans la méthode configure(). Ensuite dans la méthode doClean() nous allons utiliser une méthode permettant de vérifier si le pseudo existe ou non en base. Dans le cas où le pseudo existe, la valeur du champ n'est donc pas valide, et nous allons lancer une exception de type sfValidatorError avec le message &quot;invalid&quot;. Si le pseudo n'existe pas en base il n'y a rien de spécial faire, la méthode doClean() retourne la valeur.&lt;/p&gt;
&lt;pre class=&quot;brush: php&quot;&gt;class PseudoValidator extends sfValidatorBase
{
    public function configure($options = array(), $messages = array()) 
    {
        $this-&amp;gt;setMessage('invalid', 'Le pseudo &amp;quot;%pseudo%&amp;quot; est déjà utilisé.');
    }

    public function doClean($value)
    {
        // Dans une classe de votre modèle, créez une méthode permettant de savoir si le pseudo $value existe en base.
        // Pour l'exemple je suppose que checkPseudo() retourne un booléen.
        $exist = YourClass::checkPseudo($value);

        if($exist) 
        {
            throw new sfValidatorError($this, 'invalid', array('pseudo' =&amp;gt; $value));
        }
        
        return $value;
    }
}&lt;/pre&gt;

&lt;p&gt;Pour aller un peu plus loin dans l'exemple nous allons ajouter deux options à notre validateur, &quot;trim&quot; et &quot;min_length&quot;, cette dernière sera une option obligatoire.&lt;/p&gt;
&lt;pre class=&quot;brush: php&quot;&gt;class PseudoValidator extends sfValidatorBase
{
    public function configure($options = array(), $messages = array()) 
    {
    	$this-&amp;gt;addOption('trim');
    	$this-&amp;gt;addRequiredOption('min_length');
    	
    	$this-&amp;gt;addMessage('min_length_msg', 'Au moins %min% caractères.'); // message par défaut
        $this-&amp;gt;setMessage('invalid', 'Le pseudo &amp;quot;%pseudo%&amp;quot; est déjà utilisé.');
    }

    public function doClean($value)
    {
    	if($this-&amp;gt;hasOption('trim') &amp;amp;&amp;amp; $this-&amp;gt;getOption('trim'))
    	{
    		$value = trim($value);
    	}
    	
    	if($this-&amp;gt;hasOption('min_length') &amp;amp;&amp;amp; $this-&amp;gt;getOption('min_length') &amp;gt; strlen($value))
        {
            throw new sfValidatorError($this, 'min_length_msg', array('min' =&amp;gt; $this-&amp;gt;getOption('min_length')));	
        }
    	
        $exist = YourClass::checkPseudo($value);

        if($exist) 
        {
            throw new sfValidatorError($this, 'invalid', array('pseudo' =&amp;gt; $value));
        }
        
        return $value;
    }
}&lt;/pre&gt;

&lt;p&gt;Pour utiliser notre validateur, rien de plus simple, un petit symfony cc pour réinitialiser l'autoloading et il suffit d'associer le validateur au champ.&lt;/p&gt;
&lt;pre class=&quot;brush: php&quot;&gt;$myFormObject-&amp;gt;setValidator('my_field', new PseudoValidator(array('min_length' =&amp;gt; 4, 'trim' =&amp;gt; true), array('min_length_msg' =&amp;gt; &amp;quot;Le pseudo doit contenir au moins %min% caractères.&amp;quot;)));&lt;/pre&gt;</description>
    
    
    
          <comments>http://clear-cache.fr/?post/2009/04/26/Custom-validator-sous-symfony-1.2#comment-form</comments>
      <wfw:comment>http://clear-cache.fr/?post/2009/04/26/Custom-validator-sous-symfony-1.2#comment-form</wfw:comment>
      <wfw:commentRss>http://clear-cache.fr/?feed/atom/comments/4</wfw:commentRss>
      </item>
    
  <item>
    <title>Formulaires symfony: Traitement sur la valeur d'un champ à la sauvegarde</title>
    <link>http://clear-cache.fr/?post/2009/04/26/Formulaires%3A-Traitement-sur-la-valeur-d-un-champ-%C3%A0-la-sauvegarde</link>
    <guid isPermaLink="false">urn:md5:46ab187e0abde0847f3cc3837cd9beac</guid>
    <pubDate>Tue, 28 Apr 2009 21:43:00 +0200</pubDate>
    <dc:creator>eNk`</dc:creator>
        <category>Symfony</category>
        <category>Doctrine</category><category>formulaires symfony</category><category>Propel</category><category>symfony</category>    
    <description>&lt;p&gt;Voici une petite astuce permettant d'effectuer un traitement sur la valeur d'un champ lors de la sauvegarde d'un formulaire &lt;a href=&quot;http://clear-cache.fr/?tag/Doctrine&quot;&gt;Doctrine&lt;/a&gt; ou &lt;a href=&quot;http://clear-cache.fr/?tag/Propel&quot;&gt;Propel&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
Imaginons par exemple une classe User avec deux attributs login et password de type string, password que nous allons sauvegarder en MD5 dans notre table user.&lt;br /&gt;
&lt;br /&gt;
Pour enregistrer un utilisateur en base nous allons évidemment utiliser la classe de formulaire correspondant à notre classe User. Il serait donc pratique que lorsque l'on appelle la méthode save() de notre objet UserForm le mot de passe saisi par l'utilisateur dans le champ password soit automatiquement converti en MD5.&lt;br /&gt;&lt;/p&gt;    &lt;p&gt;Pour commencer regardons la hiérarchie de classe de formulaires générée par symfony:&lt;br /&gt;
Propel: UserForm &amp;lt; BaseUserForm &amp;lt; BaseFormPropel &amp;lt; sfFormPropel&lt;br /&gt;
Doctrine: UserForm &amp;lt; BaseUserForm &amp;lt; BaseFormDoctrine &amp;lt; sfFormDoctrine&lt;br /&gt;
&lt;br /&gt;
Ce sont les classes sfFormDoctrine et sfFormPropel qui vont nous intéresser car elles possèdent une méthode processValues() qui est appelée au moment de la mise à jour de l'objet User correspondant au formulaire. Elle a pour but de nettoyer les valeurs avant la mise à jour de l'objet. C'est donc grâce à cette méthode que nous allons pouvoir faire un traitement sur les valeurs saisis des champs et dans notre cas mettre le password en MD5. En effet pour chacun des champs du formulaire processValues() va appeler, si elle existe, une méthode updateXXXColumn() où XXX est le nom PHP d'un champ du formulaire, afin d'effectuer un traitement particulier sur la valeur de ce champ.&lt;br /&gt;
&lt;br /&gt;
Donc pour convertir le texte saisi dans le champ password en MD5 au moment de la sauvegarde du formulaire, il suffit de définir une méthode updatePasswordColumn() dans la classe UserForm.&lt;/p&gt;

&lt;pre class=&quot;brush: php&quot;&gt;public function updatePasswordColumn($value)
{
	if(!empty($value)) { $value = md5($value); }

	return $value;
}&lt;/pre&gt;


&lt;p&gt;NOTE: la méthode processValues() retourne un tableau contenant les valeurs nettoyées pour chaque champ, si une méthode updateXXXColumn() retourne false alors processValues() va supprimer le champ XXX du tableau qu'elle retourne.&lt;/p&gt;</description>
    
    
    
          <comments>http://clear-cache.fr/?post/2009/04/26/Formulaires%3A-Traitement-sur-la-valeur-d-un-champ-%C3%A0-la-sauvegarde#comment-form</comments>
      <wfw:comment>http://clear-cache.fr/?post/2009/04/26/Formulaires%3A-Traitement-sur-la-valeur-d-un-champ-%C3%A0-la-sauvegarde#comment-form</wfw:comment>
      <wfw:commentRss>http://clear-cache.fr/?feed/atom/comments/3</wfw:commentRss>
      </item>
    
</channel>
</rss>