<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet title="XSL formatting" type="text/xsl" href="http://prendreuncafe.com/blog/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>Prendre un Café - Tag - php</title>
  <link>http://prendreuncafe.com/blog/</link>
  <atom:link href="http://prendreuncafe.com/blog/feed/tag/php/rss2" rel="self" type="application/rss+xml"/>
  <description></description>
  <language>fr</language>
  <pubDate>Fri, 02 Mar 2012 11:41:49 +0100</pubDate>
  <copyright>Contenus sous licence Creative Commons BY-SA</copyright>
  <docs>http://blogs.law.harvard.edu/tech/rss</docs>
  <generator>Dotclear</generator>
  
    
  <item>
    <title>Faire part de naissance</title>
    <link>http://prendreuncafe.com/blog/post/2010/04/20/Faire-part-de-naissance</link>
    <guid isPermaLink="false">urn:md5:c99f7a9d25815299d85aabf98db08e02</guid>
    <pubDate>Tue, 20 Apr 2010 21:12:00 +0200</pubDate>
    <dc:creator>NiKo</dc:creator>
        <category>Divers</category>
        <category>akei</category><category>conseil</category><category>dev</category><category>django</category><category>formation</category><category>php</category><category>python</category><category>symfony</category>    
    <description>    &lt;p&gt;Ceux qui me suivent &lt;a href=&quot;http://twitter.com/n1k0&quot;&gt;sur twitter&lt;/a&gt; en ont eu la primeur, mais j&amp;#8217;officialise sur ce blog&amp;#160;: &lt;a href=&quot;http://www.akei.com&quot;&gt;Akei&lt;/a&gt;, ma société, est née.&lt;/p&gt;


&lt;p&gt;&lt;a href=&quot;http://www.akei.com/&quot;&gt;&lt;img src=&quot;http://prendreuncafe.com/blog/public/images/Thematiques/Akei_Logo250.png&quot; alt=&quot;Akei&quot; style=&quot;display:block; margin:0 auto;&quot; title=&quot;Akei, avr. 2010&quot; /&gt;&lt;/a&gt;&lt;/p&gt;


&lt;p&gt;Akei met en avant une offre de services axée sur le &lt;a href=&quot;http://www.akei.com/fr/services/consulting&quot;&gt;conseil&lt;/a&gt;, la &lt;a href=&quot;http://www.akei.com/fr/services/training&quot;&gt;formation&lt;/a&gt; et le &lt;a href=&quot;http://www.akei.com/fr/services/development&quot;&gt;développement Web&lt;/a&gt;, autour de technologies variées comme &lt;a href=&quot;http://www.akei.com/fr/services/symfony&quot;&gt;PHP, Symfony&lt;/a&gt;, &lt;a href=&quot;http://www.akei.com/fr/services/django&quot;&gt;Python, Django&lt;/a&gt;&amp;#160;; mais plus que tout sur la &lt;a href=&quot;http://www.akei.com/fr/philosophy&quot;&gt;qualité&lt;/a&gt;.&lt;/p&gt;


&lt;p&gt;Je ne m&amp;#8217;étalerai pas sur les longueurs et turpitudes administratives proprement &lt;a href=&quot;http://www.pole-emploi.fr/&quot;&gt;kafkaïennes&lt;/a&gt; qu&amp;#8217;il m&amp;#8217;a fallu affronter pour accoucher du bébé (et qui durent encore, soit dit en passant), mais je veux juste profiter de l&amp;#8217;instant pour vous inciter à &lt;a href=&quot;http://www.akei.com&quot;&gt;regarder ce site&lt;/a&gt;, à en lire les contenus, et à &lt;a href=&quot;http://www.akei.com/fr/contact&quot;&gt;prendre contact&lt;/a&gt; si vous vous reconnaissez dans la vision présentée, pour éventuellement - pourquoi pas&amp;#160;? - travailler ensemble sur vos projets Web.&lt;/p&gt;&lt;hr/&gt;&lt;p style=&quot;margin:.5em 0;padding:.5em;border:1px solid #333;background:#eee;color:#222&quot;&gt;&lt;small&gt;Ce billet intitulé &lt;a href=&quot;http://prendreuncafe.com/blog/post/2010/04/20/Faire-part-de-naissance&quot;&gt;Faire part de naissance&lt;/a&gt; a été rédigé par &lt;a href=&quot;http://prendreuncafe.com/cv&quot;&gt;Nicolas Perriault&lt;/a&gt; et publié sur le blog &lt;a href=&quot;http://prendreuncafe.com/blog/&quot;&gt;Prendre un Café&lt;/a&gt; sous licence &lt;a href=&quot;http://creativecommons.org/licenses/by-nc-sa/2.5/&quot;&gt;Creative Commons BY-NC-SA&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;</description>
    
    
    
      </item>
    
  <item>
    <title>User Dependent Forms with Symfony</title>
    <link>http://prendreuncafe.com/blog/post/2010/02/17/User-Dependant-Forms-with-Symfony</link>
    <guid isPermaLink="false">urn:md5:afa96fedd9e7bc61404cdf6e257005b0</guid>
    <pubDate>Wed, 17 Feb 2010 09:04:00 +0100</pubDate>
    <dc:creator>NiKo</dc:creator>
        <category>Dev</category>
        <category>best practices</category><category>credentials</category><category>forms</category><category>php</category><category>security</category><category>session</category><category>symfony</category>    
    <description>    &lt;p&gt;While sadly not being at &lt;a href=&quot;http://www.symfony-live.com/&quot;&gt;Symfony Live 2010&lt;/a&gt; with pals, I&amp;#8217;m drowning my sorrow into tech tutorials writing.&lt;/p&gt;


&lt;p&gt;Sometimes you want to design forms and contextualize them regarding the current user session. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Manage the display of some fields regarding the user authentication status and credentials;&lt;/li&gt;
&lt;li&gt;Filter some queries used to get the values available in a &lt;code&gt;&amp;lt;select/&amp;gt;&lt;/code&gt; tag;&lt;/li&gt;
&lt;li&gt;Allow multiple steps form validation with step state persistence (wizzard);&lt;/li&gt;
&lt;li&gt;etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So how can we achieve this without using an evilish &lt;code&gt;sfContext::getInstance()-&amp;gt;getUser()&lt;/code&gt; call in the &lt;code&gt;configure()&lt;/code&gt; method of the form?&lt;/p&gt;


&lt;p&gt;Symfony &lt;del&gt;1.2&lt;/del&gt; &lt;strong&gt;1.3 and 1.4&lt;/strong&gt; introduced the generation of a project-wide &lt;code&gt;BaseForm&lt;/code&gt; class we can use here to setup some convenient methods to allow user setters and getters:&lt;/p&gt;

&lt;pre&gt; php
&amp;lt;?php
class BaseForm extends sfFormSymfony
{
  static protected $user = null;

  static public function getUser()
  { 
    return self::$user;
  }

  static public function getValidUser()
  {
    if (!self::$user instanceof sfBasicSecurityUser)
    {
      throw new RuntimeException('No valid user instance available');
    }
    
    return self::$user;
  }

  static public function setUser(sfBasicSecurityUser $user)
  {
    self::$user = $user;
  }
}
&lt;/pre&gt;


&lt;p&gt;Okay, we now can set a user instance as a static property of all available forms within the project, nice. Wait, how the hell will we set the instance, and when?&lt;/p&gt;


&lt;p&gt;Symfony dispatch a very useful &lt;code&gt;context.load_factories&lt;/code&gt; event when all the factories - including the &lt;code&gt;user&lt;/code&gt; one - are instanciated and available in the context. So we can listen to this event and set the user instance to the forms when it&amp;#8217;s ready. We&amp;#8217;ll do this in the &lt;code&gt;ProjectConfiguration.class.php&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt; php
&amp;lt;?php
class ProjectConfiguration extends sfProjectConfiguration
{
  public function setup()
  {
    // ...
    
    $this-&amp;gt;dispatcher-&amp;gt;connect('context.load_factories', array($this, 'listenToLoadFactoriesEvent'));
  }

  public function listenToLoadFactoriesEvent(sfEvent $event)
  {
    BaseForm::setUser($event-&amp;gt;getSubject()-&amp;gt;getUser());
  }
}
&lt;/pre&gt;


&lt;p&gt;So now we&amp;#8217;ve set a static user instance in every form at project configuration time, we can use it in any form.&lt;/p&gt;


&lt;p&gt;For example, imagine a form where only authenticated users having an &lt;code&gt;admin&lt;/code&gt; credential can see, fill and submit an hypothetic &lt;code&gt;is_published&lt;/code&gt; field:&lt;/p&gt;

&lt;pre&gt; php
class myForm extends BaseForm
{
  public function configure()
  {
    // will throw an exception if no user is available
    $user = self::getValidUser();
    
    $this-&amp;gt;setWidgets(array(
      'title' =&amp;gt; new sfWidgetFormInputText(),
      'body' =&amp;gt; new sfWidgetFormTextarea(),
    ));
    
    $this-&amp;gt;setValidators(array(
      'title' =&amp;gt; new sfValidatorString(array('min_length' =&amp;gt; 5)),
      'body' =&amp;gt; new sfValidatorString(array('min_length' =&amp;gt; 20)),
    ));
    
    if ($user-&amp;gt;isAuthenticated() &amp;amp;&amp;amp; $user-&amp;gt;hasCredential('admin'))
    {
      $this-&amp;gt;widgetSchema['is_published'] = new sfWidgetFormInputCheckbox();
      $this-&amp;gt;validatorSchema['is_published'] = new sfValidatorBoolean(array(
        'required' =&amp;gt; false,
      ));
    }
    
    // ...
  }
}
&lt;/pre&gt;


&lt;p&gt;Here a basic user won&amp;#8217;t see and won&amp;#8217;t be able to submit any value for the &lt;code&gt;is_published&lt;/code&gt; field. Note that all those changes won&amp;#8217;t ever affect the controllers or the model, and the forms will still be easily testable because the forms and the user instance are not tightly coupled.&lt;/p&gt;


&lt;p&gt;As a side note, you can also use the form options to eventually pass a user instance and fallback on the static instance in case it&amp;#8217;s not available:&lt;/p&gt;

&lt;pre&gt; php
class myForm extends BaseForm
{
  public function configure()
  {
    // will throw an exception if no user is available
    $user = $this-&amp;gt;getOption('user', self::getValidUser());
    
    // ...
  }
}
&lt;/pre&gt;


&lt;p&gt;In a controller, you would use it this way:&lt;/p&gt;

&lt;pre&gt; php
&amp;lt;?php 
class fooActions extends sfActions
{
  public function executeBar(sfWebRequest $request)
  {
    $this-&amp;gt;form = new myForm(array('user' =&amp;gt; $this-&amp;gt;getUser()));
    
    // ...
  }
}
&lt;/pre&gt;


&lt;p&gt;A typical unit test suite of the form would be:&lt;/p&gt;

&lt;pre&gt; php
$t = new lime_test(2, new lime_output_color());

$user = new sfBasicSecurityUser(new sfEventDispatcher(), new sfSessionTestStorage(array(
  'session_path' =&amp;gt; sys_get_temp_dir(),
)));

$form = new myForm(array('user' =&amp;gt; $user));
$t-&amp;gt;is(count($form-&amp;gt;getWidgetSchema(), 2, '-&amp;gt;configure() displays 2 fields when user is not authenticated'));
$user-&amp;gt;setAuthenticated(true);
$user-&amp;gt;addCredential('admin');
$t-&amp;gt;is(count($form-&amp;gt;getWidgetSchema(), 3, '-&amp;gt;configure() displays 3 fields when user is authenticated and is an admin'));
// ... I'll let you complete the suite by yourself
&lt;/pre&gt;


&lt;p&gt;Now imagine we want to filter the choices offered by a one to many relationship field of a Doctrine form regarding user credentials:&lt;/p&gt;

&lt;pre&gt; php
class ArticleForm extends BaseArticleForm
{
  public function configure()
  {
    // will throw an exception if no user is available
    $user = $this-&amp;gt;getOption('user', self::getValidUser());
    
    // ...
    
    // Category choices
    $categoryQuery = $this-&amp;gt;getUserCategoryQuery($user);
    $this-&amp;gt;widgetSchema['category_id'] = new sfWidgetFormDoctrineChoice(array(
      'model' =&amp;gt; 'Category',
      'query' =&amp;gt; $categoryQuery,
    ));
    $this-&amp;gt;validatorSchema['community_id'] = new sfValidatorDoctrineChoice(array(
      'model' =&amp;gt; 'Category',
      'query' =&amp;gt; $categoryQuery,
    ));
  }
  
  /**
   * Please note that this method would naturally better fit in the model, in the 
   * ArticleTable class; We put this here for conciseness
   */
  protected function getUserCategoryQuery(sfBasicSecurityUser $user)
  {
    $query = Doctrine:getTable('Category')-&amp;gt;createQuery('c');
    
    // If user is not an admin, only allow choice of published categories
    if (!$user-&amp;gt;hasCredential('admin'))
    {
      $query-&amp;gt;where('c.is_published = 1');
    }
    
    return $query;
  }
}
&lt;/pre&gt;


&lt;p&gt;There are many more cool things you can achieve by offering user session access to a form, while keeping all the stuff easy to test.&lt;/p&gt;


&lt;p&gt;As usual, if you have a better way, feel free to tell us about it in the comments.&lt;/p&gt;&lt;hr/&gt;&lt;p style=&quot;margin:.5em 0;padding:.5em;border:1px solid #333;background:#eee;color:#222&quot;&gt;&lt;small&gt;Ce billet intitulé &lt;a href=&quot;http://prendreuncafe.com/blog/post/2010/02/17/User-Dependant-Forms-with-Symfony&quot;&gt;User Dependent Forms with Symfony&lt;/a&gt; a été rédigé par &lt;a href=&quot;http://prendreuncafe.com/cv&quot;&gt;Nicolas Perriault&lt;/a&gt; et publié sur le blog &lt;a href=&quot;http://prendreuncafe.com/blog/&quot;&gt;Prendre un Café&lt;/a&gt; sous licence &lt;a href=&quot;http://creativecommons.org/licenses/by-nc-sa/2.5/&quot;&gt;Creative Commons BY-NC-SA&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;</description>
    
    
    
      </item>
    
  <item>
    <title>Embedding Relations in Forms with Symfony 1.3 and Doctrine</title>
    <link>http://prendreuncafe.com/blog/post/2009/11/29/Embedding-Relations-in-Forms-with-Symfony-1.3-and-Doctrine</link>
    <guid isPermaLink="false">urn:md5:c4b0662b7bed7faf0b568b417597c70e</guid>
    <pubDate>Sun, 29 Nov 2009 18:38:00 +0100</pubDate>
    <dc:creator>NiKo</dc:creator>
        <category>Dev</category>
        <category>doctrine</category><category>forms</category><category>php</category><category>symfony</category>    
    <description>    &lt;p&gt;Symfony 1.3 and 1.4 have been &lt;a href=&quot;http://www.symfony-project.org/blog/2009/11/24/final-release-candidates-for-symfony-1-3-and-1-4&quot; hreflang=&quot;en&quot;&gt;released some days ago in RC2&lt;/a&gt; and you should really take a look at it, because they &lt;a href=&quot;http://www.symfony-project.org/tutorial/1_4/en/whats-new&quot; hreflang=&quot;en&quot;&gt;improve&lt;/a&gt; a lot the way you work with the framework, especially the forms one. It&amp;#8217;s been a couple of days I started to implement a new bookmarks management feature on a project I&amp;#8217;m working on currently, and the new model relationship embedding feature of 1.3&amp;#8217;s forms framework just saved me a lot of days.&lt;/p&gt;


&lt;p&gt;Here&amp;#8217;s a quick and dirty example on how to setup a willingly simplistic bookmarks management system using it&lt;/p&gt;


&lt;h3&gt;The schema&lt;/h3&gt;


&lt;p&gt;Here&amp;#8217;s the naive Doctrine schema I&amp;#8217;ll use for this example:&lt;/p&gt;

&lt;pre&gt; yaml
# in config/doctrine/schema.yml
User:
  columns:
    id:
      type: integer(4)
      primary: true
      autoincrement: true
    name:
      type: string(255)
      notnull: true
  relations:
    Bookmarks:
      type: many
      class: Bookmark
      local: id
      foreign: user_id
      onDelete: CASCADE

Bookmark:
  columns:
    id:
      type: integer(4)
      primary: true
      autoincrement: true
    name:
      type: string(255)
      notnull: true
    url:
      type: string(255)
      notnull: true
    user_id:
      type: integer(4)
      notnull: true
  relations:
    User:
      type: one
      local: user_id
      foreign: id
&lt;/pre&gt;


&lt;p&gt;As you can see, it&amp;#8217;s really &lt;em&gt;naive&lt;/em&gt;. Anyway.&lt;/p&gt;


&lt;h3&gt;Some fixtures&lt;/h3&gt;


&lt;p&gt;What would be a Doctrine schema without fixtures? Quite nothing I guess, so here we go:&lt;/p&gt;

&lt;pre&gt; yaml
# in data/fixtures/fixtures.yml
User:
  niko:
    name: niko

Bookmark:
  niko_bookmark1:
    User: niko
    name: Slashdot
    url: http://slashdot.org/
  niko_bookmark2:
    User: niko
    name: Delicious
    url: http://delicious.com/
  niko_bookmark3:
    User: niko
    name: Digg
    url: http://digg.com/
&lt;/pre&gt;


&lt;h3&gt;Building stuff&lt;/h3&gt;


&lt;p&gt;You guessed it, after having suited the &lt;code&gt;config/databases.yml&lt;/code&gt; file to set up a database of our choice, we can now build the database, all the classes we need and load the previously defined fixtures:&lt;/p&gt;

&lt;pre&gt;
$ ./symfony doctrine:build --all --and-load
&lt;/pre&gt;


&lt;h3&gt;A basic user&amp;#8217;s bookmarks management form&lt;/h3&gt;


&lt;p&gt;What we want do do now is to manage one &lt;code&gt;User&lt;/code&gt;&amp;#8217;s bookmarks in a single form. Let&amp;#8217;s do it now in a new &lt;code&gt;UserBookmarksForm.class.php&lt;/code&gt; file where we&amp;#8217;ll build a form extending the autogenerated &lt;code&gt;UserForm&lt;/code&gt; one:&lt;/p&gt;

&lt;pre&gt; php
&amp;lt;?php
// lib/form/doctrine/UserBookmarksForm.class.php
class UserBookmarksForm extends UserForm
{
  public function configure()
  {
    // We don't want to edit the User object
    unset($this['name']);

    // Existing bookmark forms
    $this-&amp;gt;embedRelation('Bookmarks');
    
    $this-&amp;gt;widgetSchema-&amp;gt;setNameFormat('user_bookmarks[%s]');
  }
}
&lt;/pre&gt;


&lt;p&gt;All the magic is done by calling the &lt;code&gt;embedRelation()&lt;/code&gt; method here: all &lt;code&gt;BookmarkForm&lt;/code&gt; instances will be embedded and managed automatically in and by our form.&lt;/p&gt;


&lt;p&gt;Let&amp;#8217;s use a simple controller to manage the form&lt;sup&gt;[&lt;a href=&quot;http://prendreuncafe.com/blog/post/2009/11/29/Embedding-Relations-in-Forms-with-Symfony-1.3-and-Doctrine#pnote-1129-1&quot; id=&quot;rev-pnote-1129-1&quot;&gt;1&lt;/a&gt;]&lt;/sup&gt;:&lt;/p&gt;

&lt;pre&gt; php
&amp;lt;?php
class testActions extends sfActions
{
  public function executeBookmarks(sfWebRequest $request)
  {
    $this-&amp;gt;form = new UserBookmarksForm($user = Doctrine::getTable('User')-&amp;gt;findOneByName('niko'));
    if ($request-&amp;gt;isMethod('post') &amp;amp;&amp;amp; $this-&amp;gt;form-&amp;gt;bindAndSave($request-&amp;gt;getParameter('user_bookmarks')))
    {
      $this-&amp;gt;getUser()-&amp;gt;setFlash('notice', 'Bookmarks list updated');
      $this-&amp;gt;redirect('test/index');
    }
  }
}
&lt;/pre&gt;


&lt;p&gt;And a basic template:&lt;/p&gt;

&lt;pre&gt; php
&amp;lt;form action=&amp;quot;.&amp;quot; method=&amp;quot;post&amp;quot;&amp;gt;
  &amp;lt;?php echo $form-&amp;gt;renderHiddenFields() ?&amp;gt;
  &amp;lt;?php echo $form-&amp;gt;renderGlobalErrors() ?&amp;gt;
  &amp;lt;table&amp;gt;
    &amp;lt;?php echo $form ?&amp;gt;
    &amp;lt;tr&amp;gt;
      &amp;lt;td&amp;gt;&amp;lt;/td&amp;gt;
      &amp;lt;td&amp;gt;&amp;lt;input type=&amp;quot;submit&amp;quot;/&amp;gt;&amp;lt;/td&amp;gt;
    &amp;lt;/tr&amp;gt;
  &amp;lt;/table&amp;gt;
&amp;lt;/form&amp;gt;
&lt;/pre&gt;


&lt;p&gt;You should see something like this:&lt;/p&gt;


&lt;p&gt;&lt;img src=&quot;http://prendreuncafe.com/blog/public/images/symfony/embeddedForms/simple.png&quot; alt=&quot;simple.png&quot; style=&quot;display:block; margin:0 auto;&quot; title=&quot;simple.png, nov. 2009&quot; /&gt;&lt;/p&gt;


&lt;p&gt;The form is functional, you can edit several bookmarks at one. Neat? But wait, we can&amp;#8217;t add a new one!&lt;/p&gt;


&lt;h3&gt;Embedding a bookmark creation form&lt;/h3&gt;


&lt;p&gt;Let&amp;#8217;s enhance our form by embedding an empty &lt;code&gt;BookmarkForm&lt;/code&gt; form instance in order to add a new bookmark:&lt;/p&gt;

&lt;pre&gt; php
&amp;lt;?php
// lib/form/doctrine/UserBookmarksForm.class.php
class UserBookmarksForm extends UserForm
{
  public function configure()
  {
    unset($this['name']);
    
    // Bookmark creation form
    $newBookmarkForm = new BookmarkForm();
    $newBookmarkForm-&amp;gt;setDefault('user_id', $this-&amp;gt;object-&amp;gt;id);
    $this-&amp;gt;embedForm('new', $newBookmarkForm);
    
    // Existing bookmark forms
    $this-&amp;gt;embedRelation('Bookmarks');
    
    $this-&amp;gt;widgetSchema-&amp;gt;setNameFormat('user_bookmarks[%s]');
  }
  
  protected function doBind(array $values)
  {
    if ('' === trim($values['new']['name']) &amp;amp;&amp;amp; '' === trim($values['new']['url']))
    {
      unset($values['new'], $this['new']);
    }
    
    parent::doBind($values);
  }
}
&lt;/pre&gt;


&lt;p&gt;Here I just override the &lt;code&gt;doBind()&lt;/code&gt; method to remove the bookmark creation embedded form if nothing has been submitted by the user in its fields; that&amp;#8217;s probably because he just wants to edit existing bookmarks&lt;sup&gt;[&lt;a href=&quot;http://prendreuncafe.com/blog/post/2009/11/29/Embedding-Relations-in-Forms-with-Symfony-1.3-and-Doctrine#pnote-1129-2&quot; id=&quot;rev-pnote-1129-2&quot;&gt;2&lt;/a&gt;]&lt;/sup&gt;.&lt;/p&gt;


&lt;p&gt;By refreshing the page you should now be able to see the new form:&lt;/p&gt;


&lt;p&gt;&lt;img src=&quot;http://prendreuncafe.com/blog/public/images/symfony/embeddedForms/withAdd.png&quot; alt=&quot;withAdd.png&quot; style=&quot;display:block; margin:0 auto;&quot; title=&quot;withAdd.png, nov. 2009&quot; /&gt;&lt;/p&gt;


&lt;p&gt;So now you&amp;#8217;re able to add a new &lt;code&gt;Bookmark&lt;/code&gt; and/or edit existing instances within the same simple form. Neat? Wait, we can&amp;#8217;t delete existing bookmarks!&lt;/p&gt;


&lt;h3&gt;Deleting existing bookmarks&lt;/h3&gt;


&lt;p&gt;So you would like to have a checkbox next to every existing bookmark embedded form to schedule its deletion on saving the form? Wow, that&amp;#8217;s getting tricky, I like that.&lt;/p&gt;


&lt;p&gt;The Symfony forms framework has evolved a lot, but what we&amp;#8217;re trying to achieve here will involve some fine-tunning. Don&amp;#8217;t be too much afraid seeing the code &lt;img src=&quot;/blog/themes/battlestar/smilies/wink.gif&quot; alt=&quot;;)&quot; class=&quot;smiley&quot; /&gt;&lt;/p&gt;


&lt;p&gt;First, we&amp;#8217;ll have to add the checkbox widget to the &lt;code&gt;BookmarkForm&lt;/code&gt; form, but only if an existing &lt;code&gt;Bookmark&lt;/code&gt; object has been bound to it:&lt;/p&gt;

&lt;pre&gt; php
&amp;lt;?php
// lib/form/doctrine/BookmarkForm.class.php
class BookmarkForm extends BaseBookmarkForm
{
  public function configure()
  {
    $this-&amp;gt;widgetSchema['user_id'] = new sfWidgetFormInputHidden();
    $this-&amp;gt;validatorSchema['url'] = new sfValidatorAnd(array(
      new sfValidatorString(array('max_length' =&amp;gt; 255)),
      new sfValidatorUrl(),
    ));
    
    if ($this-&amp;gt;object-&amp;gt;exists())
    {
      $this-&amp;gt;widgetSchema['delete'] = new sfWidgetFormInputCheckbox();
      $this-&amp;gt;validatorSchema['delete'] = new sfValidatorPass();
    }
  }
}
&lt;/pre&gt;


&lt;p&gt;Refreshing the form page should display something like that:&lt;/p&gt;


&lt;p&gt;&lt;img src=&quot;http://prendreuncafe.com/blog/public/images/symfony/embeddedForms/withAddAndDelete.png&quot; alt=&quot;withAddAndDelete.png&quot; style=&quot;display:block; margin:0 auto;&quot; title=&quot;withAddAndDelete.png, nov. 2009&quot; /&gt;&lt;/p&gt;


&lt;p&gt;The &lt;code&gt;UserBookmarksForm&lt;/code&gt; now holds this code:&lt;/p&gt;

&lt;pre&gt; php
&amp;lt;?php
// lib/form/doctrine/UserBookmarksForm.class.php
class UserBookmarksForm extends UserForm
{
  /**
   * Bookmarks scheduled for deletion
   * @var array
   */
  protected $scheduledForDeletion = array();  
  
  /**
   * Configures the form
   *
   */
  public function configure()
  {
    unset($this['name']);
    
    // Bookmark creation form
    $newBookmarkForm = new BookmarkForm();
    $newBookmarkForm-&amp;gt;setDefault('user_id', $this-&amp;gt;object-&amp;gt;id);
    $this-&amp;gt;embedForm('new', $newBookmarkForm);
    
    // Existing bookmark forms
    $this-&amp;gt;embedRelation('Bookmarks');
    
    $this-&amp;gt;widgetSchema-&amp;gt;setNameFormat('user_bookmarks[%s]');
  }
  
  /**
   * Here we just drop the bookmark embedded creation form if no value has been 
   * provided for it (it somewhat simulates a non-required embedded form)
   *
   * @see sfForm::doBind()
   */
  protected function doBind(array $values)
  {
    if ('' === trim($values['new']['name']) &amp;amp;&amp;amp; '' === trim($values['new']['url']))
    {
      unset($values['new'], $this['new']);
    }
    
    if (isset($values['Bookmarks']))
    {
      foreach ($values['Bookmarks'] as $i =&amp;gt; $bookmarkValues)
      {
        if (isset($bookmarkValues['delete']) &amp;amp;&amp;amp; $bookmarkValues['id'])
        {
          $this-&amp;gt;scheduledForDeletion[$i] = $bookmarkValues['id'];
        }
      }
    }
    
    parent::doBind($values);
  }
  
  /**
   * Updates object with provided values, dealing with evantual relation deletion
   *
   * @see sfFormDoctrine::doUpdateObject()
   */
  protected function doUpdateObject($values)
  {
    if (count($this-&amp;gt;scheduledForDeletion))
    {
      foreach ($this-&amp;gt;scheduledForDeletion as $index =&amp;gt; $id)
      {
        unset($values['Bookmarks'][$index]);
        unset($this-&amp;gt;object['Bookmarks'][$index]);
        Doctrine::getTable('Bookmark')-&amp;gt;findOneById($id)-&amp;gt;delete();
      }
    }

    $this-&amp;gt;getObject()-&amp;gt;fromArray($values);
  }
  
  /**
   * Saves embedded form objects.
   *
   * @param mixed $con   An optional connection object
   * @param array $forms An array of forms
   */
  public function saveEmbeddedForms($con = null, $forms = null)
  {
    if (null === $con)
    {
      $con = $this-&amp;gt;getConnection();
    }

    if (null === $forms)
    {
      $forms = $this-&amp;gt;embeddedForms;
    }
    
    foreach ($forms as $form)
    {
      if ($form instanceof sfFormObject)
      {
        if (!in_array($form-&amp;gt;getObject()-&amp;gt;getId(), $this-&amp;gt;scheduledForDeletion))
        {
          $form-&amp;gt;saveEmbeddedForms($con);
          $form-&amp;gt;getObject()-&amp;gt;save($con);
        }
      }
      else
      {
        $this-&amp;gt;saveEmbeddedForms($con, $form-&amp;gt;getEmbeddedForms());
      }
    }
  }
}
&lt;/pre&gt;


&lt;p&gt;The code should be self-explanatory&amp;#8230; or not. But anyway, you should now be able to add a new bookmark and delete existing ones as well, in a single form and process, and we&amp;#8217;re still using the very same controller from the beginning!&lt;/p&gt;


&lt;p&gt;Okay, let&amp;#8217;s admit it, we&amp;#8217;re reaching the limits of Symfony and probably those of my knowledge of it as well, but&amp;#8230; IT WORKS.&lt;/p&gt;


&lt;h3&gt;But, what about the templating?&lt;/h3&gt;


&lt;p&gt;As of now we&amp;#8217;ve used the convenient &lt;code&gt;&amp;lt;?php echo $form ?&amp;gt;&lt;/code&gt; trick, but we now want to have full fine-grained control over the way our embedded form fields will look like.&lt;/p&gt;


&lt;p&gt;Let&amp;#8217;s update the form template accordingly:&lt;/p&gt;

&lt;pre&gt; php
&amp;lt;form action=&amp;quot;.&amp;quot; method=&amp;quot;post&amp;quot;&amp;gt;
  &amp;lt;?php echo $form-&amp;gt;renderHiddenFields() ?&amp;gt;
  &amp;lt;?php echo $form-&amp;gt;renderGlobalErrors() ?&amp;gt;
  &amp;lt;!-- Embedded new bookmark form --&amp;gt;
  &amp;lt;fieldset&amp;gt;
    &amp;lt;legend&amp;gt;&amp;lt;?php echo __('Create a new bookmark') ?&amp;gt;&amp;lt;/legend&amp;gt;
    &amp;lt;?php echo $form['new']-&amp;gt;renderError() ?&amp;gt;
    &amp;lt;div class=&amp;quot;form-row&amp;quot;&amp;gt;
      &amp;lt;?php echo $form['new']['name']-&amp;gt;renderLabel() ?&amp;gt;
      &amp;lt;div class=&amp;quot;form-field&amp;quot;&amp;gt;
        &amp;lt;?php echo $form['new']['name']-&amp;gt;render() ?&amp;gt;
        &amp;lt;?php echo $form['new']['name']-&amp;gt;renderError() ?&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;div class=&amp;quot;form-row&amp;quot;&amp;gt;
      &amp;lt;?php echo $form['new']['url']-&amp;gt;renderLabel() ?&amp;gt;
      &amp;lt;div class=&amp;quot;form-field&amp;quot;&amp;gt;
        &amp;lt;?php echo $form['new']['url']-&amp;gt;render() ?&amp;gt;
        &amp;lt;?php echo $form['new']['url']-&amp;gt;renderError() ?&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/fieldset&amp;gt;
  &amp;lt;!-- /Embedded new bookmark form --&amp;gt;
  &amp;lt;!-- Embedded existing bookmark forms --&amp;gt;
  &amp;lt;?php foreach ($form['Bookmarks'] as $i =&amp;gt; $eForm): ?&amp;gt;
    &amp;lt;fieldset&amp;gt;
      &amp;lt;legend&amp;gt;&amp;lt;?php echo sprintf(__('Edit bookmark #%d'), $i+1) ?&amp;gt;&amp;lt;/legend&amp;gt;
      &amp;lt;?php echo $eForm-&amp;gt;renderError() ?&amp;gt;
      &amp;lt;div class=&amp;quot;form-row&amp;quot;&amp;gt;
        &amp;lt;?php echo $eForm['name']-&amp;gt;renderLabel() ?&amp;gt;
        &amp;lt;div class=&amp;quot;form-field&amp;quot;&amp;gt;
          &amp;lt;?php echo $eForm['name']-&amp;gt;render() ?&amp;gt;
          &amp;lt;?php echo $eForm['name']-&amp;gt;renderError() ?&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
      &amp;lt;div class=&amp;quot;form-row&amp;quot;&amp;gt;
        &amp;lt;?php echo $eForm['url']-&amp;gt;renderLabel() ?&amp;gt;
        &amp;lt;div class=&amp;quot;form-field&amp;quot;&amp;gt;
          &amp;lt;?php echo $eForm['url']-&amp;gt;render() ?&amp;gt;
          &amp;lt;?php echo $eForm['url']-&amp;gt;renderError() ?&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
      &amp;lt;div class=&amp;quot;form-row&amp;quot;&amp;gt;
        &amp;lt;div class=&amp;quot;form-field&amp;quot;&amp;gt;
          &amp;lt;?php echo $eForm['delete']-&amp;gt;render() ?&amp;gt;
          &amp;lt;?php echo $eForm['delete']-&amp;gt;renderError() ?&amp;gt;
          &amp;lt;?php echo $eForm['delete']-&amp;gt;renderLabel(__('Delete this bookmark')) ?&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/fieldset&amp;gt;
  &amp;lt;?php endforeach; ?&amp;gt;
  &amp;lt;!-- /Embedded existing bookmark forms --&amp;gt;
  &amp;lt;input type=&amp;quot;submit&amp;quot; value=&amp;quot;&amp;lt;?php echo __('Update your bookmarks') ?&amp;gt;&amp;quot;/&amp;gt;
&amp;lt;/form&amp;gt;
&lt;/pre&gt;


&lt;p&gt;Now you can really control the semantics of the HTML code of the form, field by field.&lt;/p&gt;


&lt;p&gt;As always, if you have better solutions in mind, feel free to post them in the comments.&lt;/p&gt;
&lt;div class=&quot;footnotes&quot;&gt;&lt;h4&gt;Notes&lt;/h4&gt;
&lt;p&gt;[&lt;a href=&quot;http://prendreuncafe.com/blog/post/2009/11/29/Embedding-Relations-in-Forms-with-Symfony-1.3-and-Doctrine#rev-pnote-1129-1&quot; id=&quot;pnote-1129-1&quot;&gt;1&lt;/a&gt;] I&amp;#8217;ll leave you to create an app, module and route, okay?&lt;/p&gt;
&lt;p&gt;[&lt;a href=&quot;http://prendreuncafe.com/blog/post/2009/11/29/Embedding-Relations-in-Forms-with-Symfony-1.3-and-Doctrine#rev-pnote-1129-2&quot; id=&quot;pnote-1129-2&quot;&gt;2&lt;/a&gt;] Of course this can be a lot improved, but the example is kept as simple as possible to ease understanding.&lt;/p&gt;&lt;/div&gt;&lt;hr/&gt;&lt;p style=&quot;margin:.5em 0;padding:.5em;border:1px solid #333;background:#eee;color:#222&quot;&gt;&lt;small&gt;Ce billet intitulé &lt;a href=&quot;http://prendreuncafe.com/blog/post/2009/11/29/Embedding-Relations-in-Forms-with-Symfony-1.3-and-Doctrine&quot;&gt;Embedding Relations in Forms with Symfony 1.3 and Doctrine&lt;/a&gt; a été rédigé par &lt;a href=&quot;http://prendreuncafe.com/cv&quot;&gt;Nicolas Perriault&lt;/a&gt; et publié sur le blog &lt;a href=&quot;http://prendreuncafe.com/blog/&quot;&gt;Prendre un Café&lt;/a&gt; sous licence &lt;a href=&quot;http://creativecommons.org/licenses/by-nc-sa/2.5/&quot;&gt;Creative Commons BY-NC-SA&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;</description>
    
    
    
      </item>
    
  <item>
    <title>An Interactive PHP Shell: phpsh</title>
    <link>http://prendreuncafe.com/blog/post/2009/11/04/An-Interactive-PHP-Shell%3A-PHPSH</link>
    <guid isPermaLink="false">urn:md5:632f3830bb8da94e1293d0cedff337a5</guid>
    <pubDate>Wed, 04 Nov 2009 12:00:00 +0100</pubDate>
    <dc:creator>NiKo</dc:creator>
        <category>Dev</category>
        <category>cli</category><category>php</category><category>shell</category><category>symfony</category>    
    <description>    &lt;p&gt;Just found this nice project contributed by the Facebook team, &lt;a href=&quot;http://www.phpsh.org/&quot; hreflang=&quot;en&quot;&gt;phpsh&lt;/a&gt;. Basically, it&amp;#8217;s an interactive and advanced command line interface to &lt;a href=&quot;http://php.net/&quot;&gt;php&lt;/a&gt;, a kind of super-&lt;code&gt;php -r&lt;/code&gt; similar to what you can find in &lt;a href=&quot;http://python.org/&quot;&gt;Python&lt;/a&gt; or &lt;a href=&quot;http://www.ruby-lang.org/&quot;&gt;Ruby&lt;/a&gt;. Ironically enough, phpsh is mostly written in Python, by the way.&lt;/p&gt;


&lt;p&gt;To install phpsh, just get it from github&lt;sup&gt;[&lt;a href=&quot;http://prendreuncafe.com/blog/post/2009/11/04/An-Interactive-PHP-Shell%3A-PHPSH#pnote-1128-1&quot; id=&quot;rev-pnote-1128-1&quot;&gt;1&lt;/a&gt;]&lt;/sup&gt;:&lt;/p&gt;

&lt;pre&gt;
$ git clone git://github.com/facebook/phpsh.git
$ cd phpsh
$ python setup.py build
$ sudo python setup.py install
&lt;/pre&gt;


&lt;p&gt;To run phpsh:&lt;/p&gt;

&lt;pre&gt;
$ phpsh
Starting php
type 'h' or 'help' to see instructions &amp;amp; features
php&amp;gt; =&amp;quot;hello world&amp;quot;
hello world
php&amp;gt; =2 + 2
4
php&amp;gt; $a = 8

php&amp;gt; =$a
8
&lt;/pre&gt;


&lt;p&gt;Note that you don&amp;#8217;t have to open or close &lt;code&gt;&amp;lt;?php&lt;/code&gt; tags, you can print something just by prepending an &lt;code&gt;=&lt;/code&gt; sign to the command, and no need to type semicolum at the end of a call. Easy&lt;sup&gt;[&lt;a href=&quot;http://prendreuncafe.com/blog/post/2009/11/04/An-Interactive-PHP-Shell%3A-PHPSH#pnote-1128-2&quot; id=&quot;rev-pnote-1128-2&quot;&gt;2&lt;/a&gt;]&lt;/sup&gt;.&lt;/p&gt;


&lt;p&gt;You can access a function documentation by just prepending the &lt;code&gt;d&lt;/code&gt; keyword to its name:&lt;/p&gt;

&lt;pre&gt;

php&amp;gt; d strlen

# strlen

(PHP 4, PHP 5)

strlen -- Get string length

### Description

int strlen ( string $string )

Returns the length of the given string . 

### Parameters

string     

The [string][1] being measured for length. 

### Return Values

The length of the string on success, and 0 if the string is empty. 

### 

   [1]: #language.types.string
&lt;/pre&gt;


&lt;p&gt;You can define your own classes and functions, and run them:&lt;/p&gt;

&lt;pre&gt;
php&amp;gt; function foo($a){echo $a.'!';}
php&amp;gt; foo('bar')
bar!
&lt;/pre&gt;


&lt;p&gt;Same goes for classes:&lt;/p&gt;

&lt;pre&gt;
php&amp;gt; class Foo {public function bar($a){echo $a.'!';}}
php&amp;gt; $f = new Foo()
php&amp;gt; $f-&amp;gt;bar('baz')
baz!
&lt;/pre&gt;


&lt;p&gt;You can even execute shell command from there:&lt;/p&gt;

&lt;pre&gt;
php&amp;gt; ! ls -la
&lt;/pre&gt;


&lt;p&gt;Of course, you can &lt;del&gt;import&lt;/del&gt; include and require files. As a concrete example, let&amp;#8217;s roughly play with the symfony API:&lt;/p&gt;

&lt;pre&gt;
php&amp;gt; ! symfony -V
symfony version 1.2.2-DEV (/Users/niko/Sites/vendor/symfony12/lib)
php&amp;gt; c /Users/niko/Sites/vendor/symfony12/lib/autoload/sfCoreAutoload.class.php
Extra includes are: ['/Users/niko/Sites/vendor/symfony12/lib/autoload/sfCoreAutoload.class.php']
php&amp;gt; sfCoreAutoload::register()
php&amp;gt; =sfYaml::load('foo: bar')
array(
  &amp;quot;foo&amp;quot; =&amp;gt; &amp;quot;bar&amp;quot;,
)
php&amp;gt; =sfYaml::dump(array('foo' =&amp;gt; array('bar' =&amp;gt; 'baz')))
&amp;quot;foo:\n  bar: baz\n&amp;quot;
&lt;/pre&gt;


&lt;p&gt;Last but not least, when enough playing, type &lt;code&gt;q&lt;/code&gt; or press &lt;code&gt;ctrl + d&lt;/code&gt; to quit phpsh.&lt;/p&gt;
&lt;div class=&quot;footnotes&quot;&gt;&lt;h4&gt;Notes&lt;/h4&gt;
&lt;p&gt;[&lt;a href=&quot;http://prendreuncafe.com/blog/post/2009/11/04/An-Interactive-PHP-Shell%3A-PHPSH#rev-pnote-1128-1&quot; id=&quot;pnote-1128-1&quot;&gt;1&lt;/a&gt;] So yeah, so you need &lt;a href=&quot;http://git-scm.com/&quot;&gt;git&lt;/a&gt;. And Python, of course.&lt;/p&gt;
&lt;p&gt;[&lt;a href=&quot;http://prendreuncafe.com/blog/post/2009/11/04/An-Interactive-PHP-Shell%3A-PHPSH#rev-pnote-1128-2&quot; id=&quot;pnote-1128-2&quot;&gt;2&lt;/a&gt;] Anybody putting &amp;quot;rather use python then&amp;quot; will have serious troubles, even if I strongly agree.&lt;/p&gt;&lt;/div&gt;&lt;hr/&gt;&lt;p style=&quot;margin:.5em 0;padding:.5em;border:1px solid #333;background:#eee;color:#222&quot;&gt;&lt;small&gt;Ce billet intitulé &lt;a href=&quot;http://prendreuncafe.com/blog/post/2009/11/04/An-Interactive-PHP-Shell%3A-PHPSH&quot;&gt;An Interactive PHP Shell: phpsh&lt;/a&gt; a été rédigé par &lt;a href=&quot;http://prendreuncafe.com/cv&quot;&gt;Nicolas Perriault&lt;/a&gt; et publié sur le blog &lt;a href=&quot;http://prendreuncafe.com/blog/&quot;&gt;Prendre un Café&lt;/a&gt; sous licence &lt;a href=&quot;http://creativecommons.org/licenses/by-nc-sa/2.5/&quot;&gt;Creative Commons BY-NC-SA&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;</description>
    
    
    
      </item>
    
  <item>
    <title>A Symfony 1.3 Gem: Open Files Listed in Exceptions with the Editor of your Choice</title>
    <link>http://prendreuncafe.com/blog/post/2009/10/12/A-Symfony-1.3-Gem%3A-Open-Files-Listed-in-Exceptions-with-the-Editor-of-your-Choice</link>
    <guid isPermaLink="false">urn:md5:6781e6a12d91dda46f4bd533e7f180ac</guid>
    <pubDate>Mon, 12 Oct 2009 18:22:00 +0200</pubDate>
    <dc:creator>NiKo</dc:creator>
        <category>Dev</category>
        <category>editor</category><category>php</category><category>symfony</category><category>textmate</category>    
    <description>    &lt;p&gt;I&amp;#8217;ve just stumbled upon a nice new feature of &lt;a href=&quot;http://www.symfony-project.org/&quot; hreflang=&quot;en&quot;&gt;Symfony&lt;/a&gt; 1.3, the ability to &lt;a href=&quot;http://www.symfony-project.org/tutorial/1_3/en/whats-new#chapter_64a8dd2fac010cece60fe956d341725f_sub_sf_file_link_format&quot; hreflang=&quot;en&quot;&gt;open any linked file within standard exception page with the text editor of your choice&lt;/a&gt;, at least if it provides an url scheme/protocol to open them. &lt;a href=&quot;http://blog.macromates.com/2007/the-textmate-url-scheme/&quot; hreflang=&quot;en&quot;&gt;Texmate provides natively this feature&lt;/a&gt;, so let&amp;#8217;s see how to configure a symfony project to allow its files to be opened directly in textmate when encountering a stack trace.&lt;/p&gt;


&lt;p&gt;In the &lt;code&gt;config/&lt;/code&gt; folder of the project, create a &lt;code&gt;settings.yml&lt;/code&gt; file with this content:&lt;/p&gt;

&lt;pre&gt; yml
dev: #yeah, we never know
  .settings:
    file_link_format: &amp;quot;txmt://open?url=file://%f&amp;amp;line=%l&amp;quot;
&lt;/pre&gt;


&lt;p&gt;That&amp;#8217;s it. Now any standard exception page (in &lt;code&gt;dev&lt;/code&gt; environment only, right?) will provide a &lt;code&gt;txmt://&lt;/code&gt; link to every PHP file listed in the stack trace. And at the correct line number. Neat.&lt;/p&gt;&lt;hr/&gt;&lt;p style=&quot;margin:.5em 0;padding:.5em;border:1px solid #333;background:#eee;color:#222&quot;&gt;&lt;small&gt;Ce billet intitulé &lt;a href=&quot;http://prendreuncafe.com/blog/post/2009/10/12/A-Symfony-1.3-Gem%3A-Open-Files-Listed-in-Exceptions-with-the-Editor-of-your-Choice&quot;&gt;A Symfony 1.3 Gem: Open Files Listed in Exceptions with the Editor of your Choice&lt;/a&gt; a été rédigé par &lt;a href=&quot;http://prendreuncafe.com/cv&quot;&gt;Nicolas Perriault&lt;/a&gt; et publié sur le blog &lt;a href=&quot;http://prendreuncafe.com/blog/&quot;&gt;Prendre un Café&lt;/a&gt; sous licence &lt;a href=&quot;http://creativecommons.org/licenses/by-nc-sa/2.5/&quot;&gt;Creative Commons BY-NC-SA&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;</description>
    
    
    
      </item>
    
  <item>
    <title>Simple Continuous Integration of a Symfony Project using Hudson</title>
    <link>http://prendreuncafe.com/blog/post/2009/10/06/Simple-Continuous-Integration-of-a-Symfony-Project-using-Hudson</link>
    <guid isPermaLink="false">urn:md5:48005890d8164cd0a43875105e74d9c0</guid>
    <pubDate>Tue, 06 Oct 2009 15:36:00 +0200</pubDate>
    <dc:creator>NiKo</dc:creator>
        <category>Dev</category>
        <category>continuous integration</category><category>hudson</category><category>php</category><category>symfony</category><category>testing</category>    
    <description>    &lt;p&gt;I love tests, I just cannot program without them anymore, especially with &lt;a href=&quot;http://www.symfony-project.org/&quot; hreflang=&quot;en&quot;&gt;Symfony&lt;/a&gt;. And as I write a lot of them on a daily basis, the full test suite can take a very long time to be executed&amp;#160;; running a &lt;code&gt;symfony test:all&lt;/code&gt; command before every granular commit suppose you have to wait sometimes more than 5, 10 minutes or more for the whole tests to be ran: hardly acceptable. &lt;a href=&quot;http://svnbook.red-bean.com/en/1.4/svn.ref.reposhooks.post-commit.html&quot; hreflang=&quot;en&quot;&gt;Post commit hooks&lt;/a&gt; can be a solution, but what if a lot of devs are working simultaneously on the same project? Added to the previously seen test execution duration, it can become a problem difficult to deal with.&lt;/p&gt;


&lt;p&gt;Here comes &lt;a href=&quot;http://en.wikipedia.org/wiki/Continuous_integration&quot; hreflang=&quot;en&quot;&gt;continuous integration&lt;/a&gt;&amp;#160;: regularly, eg. every hour, the symfony project will be built up and configured from scratch, then the full test suite will be executed and the resulting log will be aggregated. It&amp;#8217;s a very convenient way to keep an eye on the overall code quality and integrity of the project.&lt;/p&gt;


&lt;p&gt;There are several open source continuous integration software available on the market: &lt;a href=&quot;http://cruisecontrol.sourceforge.net/&quot; hreflang=&quot;en&quot;&gt;Cruise Control&lt;/a&gt; (and &lt;a href=&quot;http://phpundercontrol.org/&quot; hreflang=&quot;en&quot;&gt;phpUnderControl&lt;/a&gt;), &lt;a href=&quot;http://continuum.apache.org/&quot; hreflang=&quot;en&quot;&gt;Continuum&lt;/a&gt;, &lt;a href=&quot;http://integrityapp.com/&quot; hreflang=&quot;en&quot;&gt;Integrity&lt;/a&gt;, the uncertain but awesomely looking &lt;a href=&quot;http://sismo-project.org/&quot; hreflang=&quot;en&quot;&gt;Sismo&lt;/a&gt; which powers the &lt;a href=&quot;http://ci.symfony-project.org/&quot; hreflang=&quot;en&quot;&gt;Symfony continous integration server&lt;/a&gt;&amp;#8230; But I recently had the opportunity to test &lt;a href=&quot;https://hudson.dev.java.net&quot; hreflang=&quot;en&quot;&gt;Hudson&lt;/a&gt;, a java based one. It&amp;#8217;s very simple to install, setup and configure, and has tons of plugins. So let&amp;#8217;s see how it can be used to test a Symfony project.&lt;/p&gt;


&lt;p&gt;&lt;a href=&quot;http://prendreuncafe.com/blog/public/images/Dev/Hudson/hudson_tests_weather.png&quot;&gt;&lt;img src=&quot;http://prendreuncafe.com/blog/public/images/Dev/Hudson/.hudson_tests_weather_m.jpg&quot; alt=&quot;img hudson_tests_weather&quot; style=&quot;display:block; margin:0 auto;&quot; title=&quot;hudson_tests_weather.png, oct. 2009&quot; /&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h3&gt;Hudson Installation&lt;/h3&gt;


&lt;p&gt;That&amp;#8217;s really not the hardest part, because you just need a working &lt;a href=&quot;http://java.sun.com/javase/downloads/index_jdk5.jsp&quot; hreflang=&quot;en&quot;&gt;JRE 1.5&lt;/a&gt; installation on your machine, and to &lt;a href=&quot;http://hudson-ci.org/latest/hudson.war&quot; hreflang=&quot;en&quot;&gt;retrieve the latest version of the program&lt;/a&gt;.&lt;/p&gt;


&lt;p&gt;Then, you can launch it directly from a shell prompt:&lt;/p&gt;


&lt;pre&gt;$  java -jar /path/to/hudson.war&lt;/pre&gt;


&lt;p&gt;If you fear to break something or just want to give it a go without the hassle of firing a term, just launch the available &lt;a href=&quot;https://hudson.dev.java.net/hudson.jnlp&quot; hreflang=&quot;en&quot;&gt;Java Web Start version&lt;/a&gt;.&lt;/p&gt;


&lt;p&gt;On debian/ubuntu, it&amp;#8217;s just as easy as:&lt;/p&gt;


&lt;pre&gt;$ sudo echo deb http://hudson-ci.org/debian binary/ &amp;gt;&amp;gt; /etc/apt/sources.list
$ sudo apt-get update
$ sudo apt-get install hudson
$ sudo /etc/init.d/hudson start&lt;/pre&gt;


&lt;h3&gt;Configuration and Symfony Project Integration&lt;/h3&gt;


&lt;p&gt;Once Hudson is installed an running, head up to &lt;a href=&quot;http://localhost:8080/&quot; hreflang=&quot;en&quot;&gt;http://localhost:8080/&lt;/a&gt; and start playing with the web based user interface of Hudson. The navigation and configuration forms are quite obvious, and therefore efficient. Creating a project following the provided guidelines is easy and can be achieved within minutes:&lt;/p&gt;


&lt;p&gt;First, create a new Job, choosing the &lt;em&gt;Build a free-style software project&lt;/em&gt; option.&lt;/p&gt;


&lt;p&gt;Then, configure the subversion repository&lt;sup&gt;[&lt;a href=&quot;http://prendreuncafe.com/blog/post/2009/10/06/Simple-Continuous-Integration-of-a-Symfony-Project-using-Hudson#pnote-1125-1&quot; id=&quot;rev-pnote-1125-1&quot;&gt;1&lt;/a&gt;]&lt;/sup&gt; used by your project by entering its base URL in the &lt;em&gt;Source Code Management&lt;/em&gt; field
You can schedule builds, watch for SCM activity and even trigger builds from remote script using a neat XMLRPC interface&lt;sup&gt;[&lt;a href=&quot;http://prendreuncafe.com/blog/post/2009/10/06/Simple-Continuous-Integration-of-a-Symfony-Project-using-Hudson#pnote-1125-2&quot; id=&quot;rev-pnote-1125-2&quot;&gt;2&lt;/a&gt;]&lt;/sup&gt;.&lt;/p&gt;


&lt;p&gt;&lt;img src=&quot;http://prendreuncafe.com/blog/public/images/Dev/Hudson/hudson_svn.png&quot; alt=&quot;img hudson_svn&quot; style=&quot;display:block; margin:0 auto;&quot; title=&quot;hudson_svn.png, oct. 2009&quot; /&gt;&lt;/p&gt;


&lt;p&gt;&lt;img src=&quot;http://prendreuncafe.com/blog/public/images/Dev/Hudson/hudson_scheduling.png&quot; alt=&quot;img hudson_hudson_scheduling&quot; style=&quot;display:block; margin:0 auto;&quot; title=&quot;hudson_scheduling.png, oct. 2009&quot; /&gt;&lt;/p&gt;


&lt;p&gt;Configuring the building process is as easy as writing down some commands, like the one you&amp;#8217;d execute to setup your project on a new box:&lt;/p&gt;


&lt;p&gt;&lt;img src=&quot;http://prendreuncafe.com/blog/public/images/Dev/Hudson/hudson_build-commands.png&quot; alt=&quot;img hudson_build_commands&quot; style=&quot;display:block; margin:0 auto;&quot; title=&quot;hudson_build-commands.png, oct. 2009&quot; /&gt;&lt;/p&gt;


&lt;p&gt;As of Symfony 1.3, test result logs can be exported in a jUnit XML file, so Hudson will be able to parse them and provide some useful reports, metrics and charts:&lt;/p&gt;


&lt;p&gt;&lt;img src=&quot;http://prendreuncafe.com/blog/public/images/Dev/Hudson/.huson_test_results_m.jpg&quot; alt=&quot;huson_test_results.png&quot; style=&quot;display:block; margin:0 auto;&quot; title=&quot;huson_test_results.png, oct. 2009&quot; /&gt;&lt;/p&gt;


&lt;p&gt;&lt;img src=&quot;http://prendreuncafe.com/blog/public/images/Dev/Hudson/.hudson_tests_charts_m.jpg&quot; alt=&quot;&quot; style=&quot;display:block; margin:0 auto;&quot; /&gt;&lt;/p&gt;


&lt;p&gt;Of course, you can configure failure notifications: email, atom feed, there&amp;#8217;s even a &lt;a href=&quot;http://wiki.hudson-ci.org/display/HUDSON/Twitter+Plugin&quot; hreflang=&quot;en&quot;&gt;twitter plugin&lt;/a&gt;!&lt;/p&gt;


&lt;p&gt;As a conclusion, Hudson is a very simple but efficient continous integration tool which can easily be configured to monitor the quality of your Symfony projects&amp;#8230; at least if you write tests &lt;img src=&quot;/blog/themes/battlestar/smilies/wink.gif&quot; alt=&quot;;)&quot; class=&quot;smiley&quot; /&gt;&lt;/p&gt;
&lt;div class=&quot;footnotes&quot;&gt;&lt;h4&gt;Notes&lt;/h4&gt;
&lt;p&gt;[&lt;a href=&quot;http://prendreuncafe.com/blog/post/2009/10/06/Simple-Continuous-Integration-of-a-Symfony-Project-using-Hudson#rev-pnote-1125-1&quot; id=&quot;pnote-1125-1&quot;&gt;1&lt;/a&gt;] If you&amp;#8217;re not using Subversion, lot of plugins are available for other SCM in the huge &lt;a href=&quot;http://wiki.hudson-ci.org/display/HUDSON/Plugins#Plugins-Sourcecodemanagement&quot; hreflang=&quot;en&quot;&gt;Hudson plugins repository&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[&lt;a href=&quot;http://prendreuncafe.com/blog/post/2009/10/06/Simple-Continuous-Integration-of-a-Symfony-Project-using-Hudson#rev-pnote-1125-2&quot; id=&quot;pnote-1125-2&quot;&gt;2&lt;/a&gt;] Useful if you wish to use post-commit build hook&lt;/p&gt;&lt;/div&gt;&lt;hr/&gt;&lt;p style=&quot;margin:.5em 0;padding:.5em;border:1px solid #333;background:#eee;color:#222&quot;&gt;&lt;small&gt;Ce billet intitulé &lt;a href=&quot;http://prendreuncafe.com/blog/post/2009/10/06/Simple-Continuous-Integration-of-a-Symfony-Project-using-Hudson&quot;&gt;Simple Continuous Integration of a Symfony Project using Hudson&lt;/a&gt; a été rédigé par &lt;a href=&quot;http://prendreuncafe.com/cv&quot;&gt;Nicolas Perriault&lt;/a&gt; et publié sur le blog &lt;a href=&quot;http://prendreuncafe.com/blog/&quot;&gt;Prendre un Café&lt;/a&gt; sous licence &lt;a href=&quot;http://creativecommons.org/licenses/by-nc-sa/2.5/&quot;&gt;Creative Commons BY-NC-SA&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;</description>
    
    
    
      </item>
    
  <item>
    <title>Optimize your Doctrine Workflow with Specialized Queries</title>
    <link>http://prendreuncafe.com/blog/post/Optimize-your-Doctrine-Workflow-with-Specialized-Queries</link>
    <guid isPermaLink="false">urn:md5:07be0ff35e1f2e047f2faca25323c26e</guid>
    <pubDate>Tue, 15 Sep 2009 19:14:00 +0200</pubDate>
    <dc:creator>NiKo</dc:creator>
        <category>Dev</category>
        <category>bestpractices</category><category>doctrine</category><category>php</category><category>query</category><category>sql</category><category>symfony</category><category>tips</category>    
    <description>    &lt;p&gt;I&amp;#8217;m currently working on a big &lt;a href=&quot;http://www.symfony-project.org/&quot; hreflang=&quot;en&quot;&gt;Symfony&lt;/a&gt; project, with a lot of &lt;a href=&quot;http://www.doctrine-project.org/&quot; hreflang=&quot;en&quot;&gt;Doctrine&lt;/a&gt; models and complex queries to write. I found a way to organize all of them in an object-oriented and cleaner way than using the traditionnal &lt;code&gt;addNamedQuery()&lt;/code&gt; and &lt;code&gt;createNamedQuery()&lt;/code&gt; methods workflow&lt;sup&gt;[&lt;a href=&quot;http://prendreuncafe.com/blog/post/Optimize-your-Doctrine-Workflow-with-Specialized-Queries#pnote-1123-1&quot; id=&quot;rev-pnote-1123-1&quot;&gt;1&lt;/a&gt;]&lt;/sup&gt;.&lt;/p&gt;


&lt;p&gt;The idea is to create dedicated &lt;a href=&quot;http://www.doctrine-project.org/Doctrine_Query/1_2&quot; hreflang=&quot;en&quot;&gt;query&lt;/a&gt; classes for a given model&amp;#160;; this way, you can provide useful methods to build the business-related parts of your query.&lt;/p&gt;


&lt;p&gt;As usual, the theory is more understandable with a concrete example. Let&amp;#8217;s consider this simple Doctrine model&lt;sup&gt;[&lt;a href=&quot;http://prendreuncafe.com/blog/post/Optimize-your-Doctrine-Workflow-with-Specialized-Queries#pnote-1123-2&quot; id=&quot;rev-pnote-1123-2&quot;&gt;2&lt;/a&gt;]&lt;/sup&gt;&amp;#160;:&lt;/p&gt;


&lt;p&gt;&lt;strong&gt;Disclaimer:&lt;/strong&gt; The provided examples have been written in a hurry, so mistakes might have been not detected by my attentive proof-reading &lt;img src=&quot;/blog/themes/battlestar/smilies/wink.gif&quot; alt=&quot;;)&quot; class=&quot;smiley&quot; /&gt;&lt;/p&gt;

&lt;pre&gt; yaml
BlogAuthor:
  columns:
    id:
      type: integer(4)
      primary: true
      autoincrement: true  
    name:
      type: string(255)
  relations:
    Post:
      type: one
      class: BlogPost
      local: id
      foreign: author_id

BlogPost:
  columns:
    id:
      type: integer(4)
      primary: true
      autoincrement: true
    author_id:
      type: integer(4)
      notnull: true
    title:
      type: string(255)
    content:
      type: string(65535)
  relations:
    Author:
      type: one
      class: BlogAuthor
      local: author_id
      foreign: id
    Comments:
      type: many
      class: BlogComment
      local: id
      foreign: post_id

BlogComment:
  columns:
    id:
      type: integer(4)
      primary: true
      autoincrement: true
    post_id:
      type: integer(4)
      notnull: true
    author:
      type: string(255)
    content:
      type: string(5000)
  relations:
    Post:
      type: one
      class: BlogPost
      local: post_id
      foreign: id
&lt;/pre&gt;


&lt;p&gt;Now let&amp;#8217;s imagine a Query class dedicated to query the BlogPost table:&lt;/p&gt;

&lt;pre&gt; php
&amp;lt;?php 
class BlogPostQuery extends Doctrine_Query
{
  static public function create($conn = null, $class = null)
  {
    return parent::create($conn, 'BlogPostQuery')
      -&amp;gt;from('BlogPost p');
  }
  
  public function addPosts($fields = 'p.*')
  {
    return $this-&amp;gt;addSelect('p.*');
  }
  
  public function addComments($fields = 'c.*')
  {
    return $this
      -&amp;gt;addSelect($fields)
      -&amp;gt;leftJoin('p.Comments c')
      -&amp;gt;addGroupBy('c.id');
  }
  
  public function addAuthors($fields = 'a.*')
  {
    return $this
      -&amp;gt;addSelect($fields)
      -&amp;gt;leftJoin('p.Author a')
      -&amp;gt;addGroupBy('a.id');
  }
  
  public function addCommentsCount($alias = 'nb_comments')
  {
    return $this
      -&amp;gt;addSelect(sprintf('COUNT(c.id) as %s', $alias))
      -&amp;gt;addGroupBy('c.id');
  }
  
  public function filterByAuthorName($authorName)
  {
    return $this
      -&amp;gt;andWhere('a.name = ?', $authorName);
  }
}
&lt;/pre&gt;


&lt;p&gt;So how can we use this query object? Here are some sample uses:&lt;/p&gt;

&lt;pre&gt; php
// Retrieve all posts
$posts = BlogPostQuery::create()
  -&amp;gt;addPosts()
  -&amp;gt;fetchArray();

// Retrieve all posts with comments
$posts = BlogPostQuery::create()
  -&amp;gt;addPosts()
  -&amp;gt;addComments()
  -&amp;gt;fetchArray();

// Retrieve all posts with comments and their count per post
$posts = BlogPostQuery::create()
  -&amp;gt;addPosts()
  -&amp;gt;addComments()
  -&amp;gt;addCommentsCount('yataa')
  -&amp;gt;fetchArray();

// Retrieve all post with chuck as its author and related comments
$posts = BlogPostQuery::create()
  -&amp;gt;addAuthors()
  -&amp;gt;addPosts()
  -&amp;gt;addComments()
  -&amp;gt;filterByAuthorName('chuck')
  -&amp;gt;fetchArray();

// and so on...
&lt;/pre&gt;


&lt;p&gt;Of course, this example of use is not really relevant as our model is really simple, but when you&amp;#8217;re dealing with dozens of internationalized objects, it can help cleaning your model classes, controllers and improving the organization of your work.&lt;/p&gt;


&lt;h3&gt;Update and important precisions&lt;/h3&gt;


&lt;p&gt;Some people are having negative feedback regarding this technique, claiming it will encourage people using the custom query object directly in the controllers; that&amp;#8217;s absolutely not the case as the queries are to be used only within the model layer, for example in the &lt;code&gt;BlogPostTable&lt;/code&gt; class:&lt;/p&gt;

&lt;pre&gt; php
&amp;lt;?php
class BlogPostTable extends Doctrine_Table
{
  static public function getPostsWithCommentsByAuthor($authorName)
  {
    return BlogPostQuery::create()
      -&amp;gt;addPosts()
      -&amp;gt;addComments()
      -&amp;gt;filterByAuthorName($authorName)
      -&amp;gt;fetchArray()
    ;
  }
}
&lt;/pre&gt;


&lt;p&gt;And in a controller:&lt;/p&gt;

&lt;pre&gt; php
class blogActions extends sfActions
{
  public function executeListByAuthor(sfWebRequest $request)
  {
    $this-&amp;gt;posts = BlogPostTable::getPostsWithCommentsByAuthor($request-&amp;gt;getParameter('author'));
  }
}
&lt;/pre&gt;
&lt;div class=&quot;footnotes&quot;&gt;&lt;h4&gt;Notes&lt;/h4&gt;
&lt;p&gt;[&lt;a href=&quot;http://prendreuncafe.com/blog/post/Optimize-your-Doctrine-Workflow-with-Specialized-Queries#rev-pnote-1123-1&quot; id=&quot;pnote-1123-1&quot;&gt;1&lt;/a&gt;] &amp;#8230; or raw queries written directly within controllers, but you may know that &lt;a href=&quot;http://www.slideshare.net/nperriault/30-symfony-best-practices&quot; hreflang=&quot;en&quot;&gt;this is really bad&lt;/a&gt; &lt;img src=&quot;/blog/themes/battlestar/smilies/wink.gif&quot; alt=&quot;;)&quot; class=&quot;smiley&quot; /&gt;&lt;/p&gt;
&lt;p&gt;[&lt;a href=&quot;http://prendreuncafe.com/blog/post/Optimize-your-Doctrine-Workflow-with-Specialized-Queries#rev-pnote-1123-2&quot; id=&quot;pnote-1123-2&quot;&gt;2&lt;/a&gt;] I&amp;#8217;m using Doctrine 1.2 beta (bundled with upcoming symfony 1.3) in the provided example.&lt;/p&gt;&lt;/div&gt;&lt;hr/&gt;&lt;p style=&quot;margin:.5em 0;padding:.5em;border:1px solid #333;background:#eee;color:#222&quot;&gt;&lt;small&gt;Ce billet intitulé &lt;a href=&quot;http://prendreuncafe.com/blog/post/Optimize-your-Doctrine-Workflow-with-Specialized-Queries&quot;&gt;Optimize your Doctrine Workflow with Specialized Queries&lt;/a&gt; a été rédigé par &lt;a href=&quot;http://prendreuncafe.com/cv&quot;&gt;Nicolas Perriault&lt;/a&gt; et publié sur le blog &lt;a href=&quot;http://prendreuncafe.com/blog/&quot;&gt;Prendre un Café&lt;/a&gt; sous licence &lt;a href=&quot;http://creativecommons.org/licenses/by-nc-sa/2.5/&quot;&gt;Creative Commons BY-NC-SA&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;</description>
    
    
    
      </item>
    
  <item>
    <title>30 Symfony Best Practices, the slides from my talk at SymfonyDay Cologne '09</title>
    <link>http://prendreuncafe.com/blog/post/2009/09/04/30-Symfony-Best-Practices%2C-the-slides-from-my-talk-at-SymfonyDay-Cologne-09</link>
    <guid isPermaLink="false">urn:md5:b3db81a6e20a143a4e03c71d6b81c16f</guid>
    <pubDate>Fri, 04 Sep 2009 20:50:00 +0200</pubDate>
    <dc:creator>NiKo</dc:creator>
        <category>Dev</category>
        <category>bestpractices</category><category>php</category><category>sfdaycgn</category><category>symfony</category>    
    <description>    &lt;p&gt;Hey there, it&amp;#8217;s been a while, huh?&lt;/p&gt;


&lt;p&gt;Today I gave a talk about Symfony best practices at the &lt;a href=&quot;http://www.symfonyday.com/&quot; hreflang=&quot;en&quot;&gt;Symfony Day event in Cologne, Germany&lt;/a&gt;; you can &lt;a href=&quot;http://www.slideshare.net/nperriault/30-symfony-best-practices&quot; hreflang=&quot;en&quot;&gt;get the slides on slideshare&lt;/a&gt; or directly browse them below:&lt;/p&gt;

&lt;object type=&quot;application/x-shockwave-flash&quot; data=&quot;http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=30symfonybestpractices-090904074841-phpapp01&amp;amp;stripped_title=30-symfony-best-practices&quot; width=&quot;425&quot; height=&quot;344&quot; style=&quot;width:425px;height:344px;margin:0 auto&quot;&gt;
  &lt;param name=&quot;movie&quot; value=&quot;http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=30symfonybestpractices-090904074841-phpapp01&amp;amp;stripped_title=30-symfony-best-practices&quot; /&gt;
  &lt;param name=&quot;wmode&quot; value=&quot;transparent&quot; /&gt;
  &lt;param name=&quot;allowFullScreen&quot; value=&quot;true&quot; /&gt;
  &lt;param name=&quot;allowScriptAccess&quot; value=&quot;always&quot; /&gt;
  
&lt;/object&gt;



&lt;p&gt;The Symfony Day event has been purely awesomely incredibly well organized, many kudos and thanks to &lt;a href=&quot;http://www.interlutions.de/&quot; hreflang=&quot;de&quot;&gt;Interlutions&lt;/a&gt; and to all the attendees. You&amp;#8217;re all great people. Thanks.&lt;/p&gt;

&lt;a href=&quot;http://www.flickr.com/photos/n1k0/3886662176/&quot; title=&quot;Symfony Day '09 Cologne by Nicolas Perriault, on Flickr&quot;&gt;&lt;img src=&quot;http://farm3.static.flickr.com/2555/3886662176_8f0826e610.jpg&quot; width=&quot;500&quot; height=&quot;333&quot; alt=&quot;Symfony Day '09 Cologne&quot; /&gt;&lt;/a&gt;&lt;hr/&gt;&lt;p style=&quot;margin:.5em 0;padding:.5em;border:1px solid #333;background:#eee;color:#222&quot;&gt;&lt;small&gt;Ce billet intitulé &lt;a href=&quot;http://prendreuncafe.com/blog/post/2009/09/04/30-Symfony-Best-Practices%2C-the-slides-from-my-talk-at-SymfonyDay-Cologne-09&quot;&gt;30 Symfony Best Practices, the slides from my talk at SymfonyDay Cologne '09&lt;/a&gt; a été rédigé par &lt;a href=&quot;http://prendreuncafe.com/cv&quot;&gt;Nicolas Perriault&lt;/a&gt; et publié sur le blog &lt;a href=&quot;http://prendreuncafe.com/blog/&quot;&gt;Prendre un Café&lt;/a&gt; sous licence &lt;a href=&quot;http://creativecommons.org/licenses/by-nc-sa/2.5/&quot;&gt;Creative Commons BY-NC-SA&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;</description>
    
    
    
      </item>
    
</channel>
</rss>

