<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Miximum &#187; php</title>
	<atom:link href="http://www.miximum.fr/tag/php/feed" rel="self" type="application/rss+xml" />
	<link>http://www.miximum.fr</link>
	<description>Le blog d&#039;un ingénieur web freelance</description>
	<lastBuildDate>Wed, 16 Nov 2011 16:25:14 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.1</generator>
		<item>
		<title>Indexer Wikipédia dans Solr</title>
		<link>http://www.miximum.fr/methodes-et-outils/510-indexer-wikipedia-dans-solr</link>
		<comments>http://www.miximum.fr/methodes-et-outils/510-indexer-wikipedia-dans-solr#comments</comments>
		<pubDate>Mon, 25 Oct 2010 16:53:17 +0000</pubDate>
		<dc:creator>thibault</dc:creator>
				<category><![CDATA[Méthodes et outils]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[solr]]></category>
		<category><![CDATA[wikipedia]]></category>

		<guid isPermaLink="false">http://www.miximum.fr/?p=510</guid>
		<description><![CDATA[photo credit&#160;: Thibault Jouannic Dr&#244;le d&#8217;&#233;poque. Apr&#232;s deux jours magiques &#224; ParisWeb, et un retour chez moi dans une ambiance de guerre civile, le retour &#224; la r&#233;alit&#233; est&#8230; difficile. La reprise du quotidien apr&#232;s un tel &#233;v&#233;nement est toujours une p&#233;riode cafardog&#232;ne. Pour &#233;viter de sombrer dans la d&#233;prime la plus grise, je vous [...]]]></description>
			<content:encoded><![CDATA[<div class="postimg alignleft"><a href="http://www.flickr.com/photos/24469297@N05/5036470965/" title="En regardant le coucher de soleil" target="_blank"><img src="http://farm5.static.flickr.com/4147/5036470965_c9868f3e14_m.jpg" alt="En regardant le coucher de Soleil" border="0" /></a><br /><small><a href="http://creativecommons.org/licenses/by-nc-sa/2.0/" title="Attribution-NonCommercial-ShareAlike License" target="_blank"><img src="http://www.miximum.fr/wp-content/plugins/photo-dropper/images/cc.png" alt="Creative Commons License" border="0" width="16" height="16" align="absmiddle" /></a> <a href="http://www.photodropper.com/photos/" target="_blank">photo</a> credit&nbsp;: <a href="http://www.flickr.com/photos/24469297@N05/5036470965/" title="Thibault Jouannic" target="_blank">Thibault Jouannic</a></small></div>
<p>Dr&ocirc;le d&#8217;&eacute;poque. Apr&egrave;s <a href="http://ovineyards.com/english/parisweb-2010-my-tiny-brains-effort-to-summarize/">deux jours magiques &agrave; ParisWeb</a>, et un retour chez moi dans une ambiance de guerre civile, le retour &agrave; la r&eacute;alit&eacute; est&hellip; difficile.</p>
<p>La reprise du quotidien apr&egrave;s un tel &eacute;v&eacute;nement est toujours une p&eacute;riode cafardog&egrave;ne. Pour &eacute;viter de sombrer dans la d&eacute;prime la plus grise, je vous propose de nous fixer un objectif un tantinet ambitieux&nbsp;: et si nous indexions <a href="http://fr.wikipedia.org/">la plus grande base de connaissance au monde</a> dans le <a href="http://www.miximum.fr/tag/solr">meilleur moteur de recherche</a><span class="fine">&nbsp;</span>? (Si &ccedil;a ne vous plait pas, vous pouvez plut&ocirc;t <a href="http://twitter.com/#!/search/%23sudweb">vous abonner au tag #sudweb</a>.)</p>
<p>Chercher dans Wikip&eacute;dia gr&acirc;ce &agrave; Solr<span class="fine">&nbsp;</span>? Si, c&#8217;est possible.</p>
<h2>Des donn&eacute;es, des donn&eacute;es, des donn&eacute;es&hellip;</h2>
<p>Wikip&eacute;dia fournit r&eacute;guli&egrave;rement des <a href="http://download.wikimedia.org/frwiki/latest/">fichiers de dumps</a> permettant de r&eacute;cup&eacute;rer toutes les donn&eacute;es du site. Nous nous contenterons de r&eacute;cup&eacute;rer les articles complets (sans les r&eacute;visions, ni les commentaires), et en fran&ccedil;ais uniquement. Je vous m&acirc;che le travail, <a href="http://download.wikimedia.org/frwiki/latest/frwiki-latest-pages-articles.xml.bz2">il n&#8217;y a qu&#8217;un seul fichier &agrave; r&eacute;cup&eacute;rer</a>.</p>
<h2>Configurer le sch&eacute;ma</h2>
<p>La documentation de Solr fournit un <a href="http://wiki.apache.org/solr/DataImportHandler#Example:_Indexing_wikipedia">exemple de sch&eacute;ma pour indexer Wikipedia</a>. Nous allons l&#8217;optimiser pour prendre en compte la langue fran&ccedil;aise (je pars ici d&#8217;une install vierge de Solr).</p>
<p>Commen&ccedil;ons par ajouter un nouveau <em>fieldtype</em>&nbsp;: <strong>text_fr</strong>, dans le fichier schema.xml.</p>

<div class="wp_syntax"><div class="code"><pre class="xml" style="font-family:monospace;color: #ccc; font: 12px Consolas, Lucida Console, Monaco, monospace;"><span class="sc3"><span class="re1">&lt;fieldtype</span> <span class="re0">name</span>=<span class="st0">&quot;text_fr&quot;</span> <span class="re0">class</span>=<span class="st0">&quot;solr.TextField&quot;</span><span class="re2">&gt;</span></span>
  <span class="sc3"><span class="re1">&lt;analyzer</span> <span class="re0">type</span>=<span class="st0">&quot;index&quot;</span><span class="re2">&gt;</span></span>
    <span class="sc3"><span class="re1">&lt;tokenizer</span> <span class="re0">class</span>=<span class="st0">&quot;solr.StandardTokenizerFactory&quot;</span><span class="re2">/&gt;</span></span>
    <span class="sc3"><span class="re1">&lt;filter</span> <span class="re0">class</span>=<span class="st0">&quot;solr.SynonymFilterFactory&quot;</span> <span class="re0">synonyms</span>=<span class="st0">&quot;synonyms_fr.txt&quot;</span> <span class="re0">ignoreCase</span>=<span class="st0">&quot;true&quot;</span> <span class="re0">expand</span>=<span class="st0">&quot;true&quot;</span><span class="re2">/&gt;</span></span>
    <span class="sc3"><span class="re1">&lt;filter</span> <span class="re0">class</span>=<span class="st0">&quot;solr.StandardFilterFactory&quot;</span><span class="re2">/&gt;</span></span>
    <span class="sc3"><span class="re1">&lt;filter</span> <span class="re0">class</span>=<span class="st0">&quot;solr.StopFilterFactory&quot;</span> <span class="re0">ignoreCase</span>=<span class="st0">&quot;true&quot;</span> <span class="re0">words</span>=<span class="st0">&quot;stopwords_fr.txt&quot;</span><span class="re2">/&gt;</span></span>
    <span class="sc3"><span class="re1">&lt;filter</span> <span class="re0">class</span>=<span class="st0">&quot;solr.WordDelimiterFilterFactory&quot;</span> <span class="re0">generateWordParts</span>=<span class="st0">&quot;1&quot;</span> <span class="re0">generateNumberParts</span>=<span class="st0">&quot;1&quot;</span> <span class="re0">catenateWords</span>=<span class="st0">&quot;1&quot;</span> <span class="re0">catenateNumbers</span>=<span class="st0">&quot;1&quot;</span> <span class="re0">catenateAll</span>=<span class="st0">&quot;0&quot;</span> <span class="re0">splitOnCaseChange</span>=</span>
<span class="sc3">    <span class="re1">&lt;filter</span> <span class="re0">class</span>=<span class="st0">&quot;solr.ISOLatin1AccentFilterFactory&quot;</span><span class="re2">/&gt;</span></span>
    <span class="sc3"><span class="re1">&lt;filter</span> <span class="re0">class</span>=<span class="st0">&quot;solr.LowerCaseFilterFactory&quot;</span><span class="re2">/&gt;</span></span>
    <span class="sc3"><span class="re1">&lt;filter</span> <span class="re0">class</span>=<span class="st0">&quot;solr.SnowballPorterFilterFactory&quot;</span> <span class="re0">language</span>=<span class="st0">&quot;French&quot;</span> <span class="re0">protected</span>=<span class="st0">&quot;protwords_fr.txt&quot;</span> <span class="re2">/&gt;</span></span>
  <span class="sc3"><span class="re1">&lt;/analyzer<span class="re2">&gt;</span></span></span>
  <span class="sc3"><span class="re1">&lt;analyzer</span> <span class="re0">type</span>=<span class="st0">&quot;query&quot;</span><span class="re2">&gt;</span></span>
    <span class="sc3"><span class="re1">&lt;tokenizer</span> <span class="re0">class</span>=<span class="st0">&quot;solr.StandardTokenizerFactory&quot;</span><span class="re2">/&gt;</span></span>
    <span class="sc3"><span class="re1">&lt;filter</span> <span class="re0">class</span>=<span class="st0">&quot;solr.SynonymFilterFactory&quot;</span> <span class="re0">synonyms</span>=<span class="st0">&quot;synonyms_fr.txt&quot;</span> <span class="re0">ignoreCase</span>=<span class="st0">&quot;true&quot;</span> <span class="re0">expand</span>=<span class="st0">&quot;true&quot;</span><span class="re2">/&gt;</span></span>
    <span class="sc3"><span class="re1">&lt;filter</span> <span class="re0">class</span>=<span class="st0">&quot;solr.StandardFilterFactory&quot;</span><span class="re2">/&gt;</span></span>
    <span class="sc3"><span class="re1">&lt;filter</span> <span class="re0">class</span>=<span class="st0">&quot;solr.StopFilterFactory&quot;</span> <span class="re0">ignoreCase</span>=<span class="st0">&quot;true&quot;</span> <span class="re0">words</span>=<span class="st0">&quot;stopwords_fr.txt&quot;</span><span class="re2">/&gt;</span></span>
    <span class="sc3"><span class="re1">&lt;filter</span> <span class="re0">class</span>=<span class="st0">&quot;solr.WordDelimiterFilterFactory&quot;</span> <span class="re0">generateWordParts</span>=<span class="st0">&quot;1&quot;</span> <span class="re0">generateNumberParts</span>=<span class="st0">&quot;1&quot;</span> <span class="re0">catenateWords</span>=<span class="st0">&quot;0&quot;</span> <span class="re0">catenateNumbers</span>=<span class="st0">&quot;0&quot;</span> <span class="re0">catenateAll</span>=<span class="st0">&quot;0&quot;</span> <span class="re0">splitOnCaseChange</span>=</span>
<span class="sc3">    <span class="re1">&lt;filter</span> <span class="re0">class</span>=<span class="st0">&quot;solr.ISOLatin1AccentFilterFactory&quot;</span><span class="re2">/&gt;</span></span>
    <span class="sc3"><span class="re1">&lt;filter</span> <span class="re0">class</span>=<span class="st0">&quot;solr.LowerCaseFilterFactory&quot;</span><span class="re2">/&gt;</span></span>
    <span class="sc3"><span class="re1">&lt;filter</span> <span class="re0">class</span>=<span class="st0">&quot;solr.SnowballPorterFilterFactory&quot;</span> <span class="re0">language</span>=<span class="st0">&quot;French&quot;</span> <span class="re0">protected</span>=<span class="st0">&quot;protwords_fr.txt&quot;</span> <span class="re2">/&gt;</span></span>
  <span class="sc3"><span class="re1">&lt;/analyzer<span class="re2">&gt;</span></span></span>
<span class="sc3"><span class="re1">&lt;/fieldtype<span class="re2">&gt;</span></span></span></pre></div></div>

<p>Vous remarquerez que ce type de champ n&eacute;cessite trois fichiers pour fonctionner.</p>
<ol>
<li><strong>synonyms_fr.txt</strong>, pour g&eacute;rer les synonymes dans l&#8217;indexation. Exemple&nbsp;:
<pre>
aiki => aikido
resto => restaurant
</pre>
</li>
<li><strong>stopwords_fr.txt</strong>, contient la liste des <a href="http://svn.apache.org/repos/asf/lucene/dev/trunk/modules/analysis/common/src/resources/org/apache/lucene/analysis/snowball/french_stop.txt">mots basiques qui ne doivent pas &ecirc;tre index&eacute;s</a>&nbsp;<span class="fine">&nbsp;</span>;</li>
<li><strong>protwords_fr.txt</strong>, contient la liste des mots qui ne doivent pas &ecirc;tre retrait&eacute;s par le <a href="http://wiki.apache.org/solr/LanguageAnalysis#French"><em>stemmer</em> fran&ccedil;ais</a>&nbsp;<span class="fine">&nbsp;</span>;</li>
</ol>
<p>Ensuite, toujours dans le m&ecirc;me fichier, configurons nos <em>fields</em>&nbsp;:</p>

<div class="wp_syntax"><div class="code"><pre class="xml" style="font-family:monospace;color: #ccc; font: 12px Consolas, Lucida Console, Monaco, monospace;"><span class="sc3"><span class="re1">&lt;fields<span class="re2">&gt;</span></span></span>
  <span class="sc3"><span class="re1">&lt;field</span> <span class="re0">name</span>=<span class="st0">&quot;id&quot;</span>        <span class="re0">type</span>=<span class="st0">&quot;string&quot;</span> <span class="re0">indexed</span>=<span class="st0">&quot;true&quot;</span> <span class="re0">stored</span>=<span class="st0">&quot;true&quot;</span> <span class="re0">required</span>=<span class="st0">&quot;true&quot;</span><span class="re2">/&gt;</span></span>
&nbsp;
  <span class="sc3"><span class="re1">&lt;field</span> <span class="re0">name</span>=<span class="st0">&quot;title&quot;</span>     <span class="re0">type</span>=<span class="st0">&quot;string&quot;</span>     <span class="re0">indexed</span>=<span class="st0">&quot;false&quot;</span> <span class="re0">stored</span>=<span class="st0">&quot;true&quot;</span><span class="re2">/&gt;</span></span>
  <span class="sc3"><span class="re1">&lt;field</span> <span class="re0">name</span>=<span class="st0">&quot;search_title&quot;</span>     <span class="re0">type</span>=<span class="st0">&quot;text_fr&quot;</span>     <span class="re0">indexed</span>=<span class="st0">&quot;true&quot;</span> <span class="re0">stored</span>=<span class="st0">&quot;false&quot;</span><span class="re2">/&gt;</span></span>
  <span class="sc3"><span class="re1">&lt;field</span> <span class="re0">name</span>=<span class="st0">&quot;body&quot;</span>    <span class="re0">type</span>=<span class="st0">&quot;text_fr&quot;</span>    <span class="re0">indexed</span>=<span class="st0">&quot;true&quot;</span> <span class="re0">stored</span>=<span class="st0">&quot;true&quot;</span> <span class="re0">termVectors</span>=<span class="st0">&quot;true&quot;</span><span class="re2">/&gt;</span></span>
&nbsp;
  <span class="sc3"><span class="re1">&lt;field</span> <span class="re0">name</span>=<span class="st0">&quot;revision&quot;</span>   <span class="re0">type</span>=<span class="st0">&quot;sint&quot;</span>    <span class="re0">indexed</span>=<span class="st0">&quot;true&quot;</span> <span class="re0">stored</span>=<span class="st0">&quot;true&quot;</span><span class="re2">/&gt;</span></span>
  <span class="sc3"><span class="re1">&lt;field</span> <span class="re0">name</span>=<span class="st0">&quot;user&quot;</span>        <span class="re0">type</span>=<span class="st0">&quot;string&quot;</span>  <span class="re0">indexed</span>=<span class="st0">&quot;true&quot;</span> <span class="re0">stored</span>=<span class="st0">&quot;true&quot;</span><span class="re2">/&gt;</span></span>
  <span class="sc3"><span class="re1">&lt;field</span> <span class="re0">name</span>=<span class="st0">&quot;userId&quot;</span>     <span class="re0">type</span>=<span class="st0">&quot;int&quot;</span> <span class="re0">indexed</span>=<span class="st0">&quot;true&quot;</span> <span class="re0">stored</span>=<span class="st0">&quot;true&quot;</span><span class="re2">/&gt;</span></span>
  <span class="sc3"><span class="re1">&lt;field</span> <span class="re0">name</span>=<span class="st0">&quot;timestamp&quot;</span> <span class="re0">type</span>=<span class="st0">&quot;date&quot;</span>    <span class="re0">indexed</span>=<span class="st0">&quot;true&quot;</span> <span class="re0">stored</span>=<span class="st0">&quot;true&quot;</span><span class="re2">/&gt;</span></span>
 <span class="sc3"><span class="re1">&lt;/fields<span class="re2">&gt;</span></span></span>
&nbsp;
 <span class="sc3"><span class="re1">&lt;uniqueKey<span class="re2">&gt;</span></span></span>id<span class="sc3"><span class="re1">&lt;/uniqueKey<span class="re2">&gt;</span></span></span>
 <span class="sc3"><span class="re1">&lt;defaultSearchField<span class="re2">&gt;</span></span></span>search_title<span class="sc3"><span class="re1">&lt;/defaultSearchField<span class="re2">&gt;</span></span></span>
 <span class="sc3"><span class="re1">&lt;copyField</span> <span class="re0">source</span>=<span class="st0">&quot;title&quot;</span> <span class="re0">dest</span>=<span class="st0">&quot;search_title&quot;</span><span class="re2">/&gt;</span></span></pre></div></div>

<p>Notez que l&#8217;on stocke le corps de l&#8217;article. C&#8217;est parce que nous allons utiliser les <em>results highlighting</em>. Si vous n&#8217;avez pas besoin de cette fonctionnalit&eacute;, remplacez &laquo;&nbsp;stored=true&nbsp;&raquo; par &laquo;&nbsp;stored=false&nbsp;&raquo;.</p>
<h2>Et on importe</h2>
<p>Nous aurions pu d&eacute;cider de convertir le fichier xml de wikid&eacute;dia en sql, de l&#8217;importer dans une base, et d&#8217;utiliser un <em>sql import handler</em>. Mais pourquoi se compliquer la t&acirc;che<span class="fine">&nbsp;</span>? Solr peut importer directement du xml. Configurons notre import de donn&eacute;es dans le fichier data-config.xml&nbsp;:</p>

<div class="wp_syntax"><div class="code"><pre class="xml" style="font-family:monospace;color: #ccc; font: 12px Consolas, Lucida Console, Monaco, monospace;"><span class="sc3"><span class="re1">&lt;dataConfig<span class="re2">&gt;</span></span></span>
    <span class="sc3"><span class="re1">&lt;dataSource</span> <span class="re0">type</span>=<span class="st0">&quot;FileDataSource&quot;</span> <span class="re0">encoding</span>=<span class="st0">&quot;UTF-8&quot;</span> <span class="re2">/&gt;</span></span>
    <span class="sc3"><span class="re1">&lt;document<span class="re2">&gt;</span></span></span>
    <span class="sc3"><span class="re1">&lt;entity</span> <span class="re0">name</span>=<span class="st0">&quot;page&quot;</span></span>
<span class="sc3">        <span class="re0">processor</span>=<span class="st0">&quot;XPathEntityProcessor&quot;</span></span>
<span class="sc3">        <span class="re0">stream</span>=<span class="st0">&quot;true&quot;</span></span>
<span class="sc3">        <span class="re0">forEach</span>=<span class="st0">&quot;/mediawiki/page/&quot;</span></span>
<span class="sc3">        <span class="re0">url</span>=<span class="st0">&quot;/var/www/solrdemo/dumps/frwiki-latest-pages-articles.xml&quot;</span></span>
<span class="sc3">        <span class="re0">transformer</span>=<span class="st0">&quot;RegexTransformer,DateFormatTransformer&quot;</span></span>
<span class="sc3">        <span class="re2">&gt;</span></span>
        <span class="sc3"><span class="re1">&lt;field</span> <span class="re0">column</span>=<span class="st0">&quot;id&quot;</span>    <span class="re0">xpath</span>=<span class="st0">&quot;/mediawiki/page/id&quot;</span> <span class="re2">/&gt;</span></span>
        <span class="sc3"><span class="re1">&lt;field</span> <span class="re0">column</span>=<span class="st0">&quot;title&quot;</span>     <span class="re0">xpath</span>=<span class="st0">&quot;/mediawiki/page/title&quot;</span> <span class="re2">/&gt;</span></span>
        <span class="sc3"><span class="re1">&lt;field</span> <span class="re0">column</span>=<span class="st0">&quot;body&quot;</span>      <span class="re0">xpath</span>=<span class="st0">&quot;/mediawiki/page/revision/text&quot;</span> <span class="re2">/&gt;</span></span>
        <span class="sc3"><span class="re1">&lt;field</span> <span class="re0">column</span>=<span class="st0">&quot;revision&quot;</span>  <span class="re0">xpath</span>=<span class="st0">&quot;/mediawiki/page/revision/id&quot;</span> <span class="re2">/&gt;</span></span>
        <span class="sc3"><span class="re1">&lt;field</span> <span class="re0">column</span>=<span class="st0">&quot;user&quot;</span>      <span class="re0">xpath</span>=<span class="st0">&quot;/mediawiki/page/revision/contributor/username&quot;</span> <span class="re2">/&gt;</span></span>
        <span class="sc3"><span class="re1">&lt;field</span> <span class="re0">column</span>=<span class="st0">&quot;userId&quot;</span>    <span class="re0">xpath</span>=<span class="st0">&quot;/mediawiki/page/revision/contributor/id&quot;</span> <span class="re2">/&gt;</span></span>
        <span class="sc3"><span class="re1">&lt;field</span> <span class="re0">column</span>=<span class="st0">&quot;timestamp&quot;</span> <span class="re0">xpath</span>=<span class="st0">&quot;/mediawiki/page/revision/timestamp&quot;</span> <span class="re0">dateTimeFormat</span>=<span class="st0">&quot;yyyy-MM-dd'T'hh:mm:ss'Z'&quot;</span> <span class="re2">/&gt;</span></span>
        <span class="sc3"><span class="re1">&lt;field</span> <span class="re0">column</span>=<span class="st0">&quot;$skipDoc&quot;</span>  <span class="re0">regex</span>=<span class="st0">&quot;^(?i)#redirect.*&quot;</span> <span class="re0">replaceWith</span>=<span class="st0">&quot;true&quot;</span> <span class="re0">sourceColName</span>=<span class="st0">&quot;text&quot;</span><span class="re2">/&gt;</span></span>
       <span class="sc3"><span class="re1">&lt;/entity<span class="re2">&gt;</span></span></span>
    <span class="sc3"><span class="re1">&lt;/document<span class="re2">&gt;</span></span></span>
<span class="sc3"><span class="re1">&lt;/dataConfig<span class="re2">&gt;</span></span></span></pre></div></div>

<p>Red&eacute;marrez Solr, lancez l&#8217;indexation, attendez (une heure ou deux), et paf<span class="fine">&nbsp;</span>! 2400000 document index&eacute;s, ce qui vous en conviendrez est plus que suffisant pour s&#8217;amuser.</p>
<h2>Un peu de result highlighting</h2>
<p>&Eacute;tant donn&eacute; que nous disposons d&#8217;un sacr&eacute; paquet de texte, autant en profiter un peu, non<span class="fine">&nbsp;</span>? (oui, j&#8217;ai des loisirs de geeks). Au hasard, je vous propose de mettre en place un peu de <em>result highlighting</em><sup><a href="#footnote-1-510" id="footnote-link-1-510" title="Voir la note.">[1]</a></sup> .</p>
<p>Bon, alors pour ceux qui aiment bien avoir un retour visuel, &ccedil;a pourrait ressembler &agrave; &ccedil;a&nbsp;:</p>
<div class="postimg">
<img src="http://farm2.static.flickr.com/1149/5114959190_da8ca1d01c.jpg" alt="D&eacute;mo Result highlighting" />
</div>
<p>Pour ce faire, nous allons <a href="http://wiki.apache.org/solr/HighlightingParameters">utiliser les param&egrave;tres qui vont bien</a>, et que je vous propose ci-dessous sous forme de tableau php (et tant pis pour les pythoneux, faudra s&#8217;en contenter<sup><a href="#footnote-2-510" id="footnote-link-2-510" title="Voir la note.">[2]</a></sup> ).</p>

<div class="wp_syntax"><div class="code"><pre class="php" style="font-family:monospace;color: #ccc; font: 12px Consolas, Lucida Console, Monaco, monospace;"><span class="re0">$req</span> <span class="sy0">=</span> <a href="http://www.php.net/array"><span class="kw3">array</span></a><span class="br0">&#40;</span>
  <span class="st_h">'q'</span> <span class="sy0">=&gt;</span> <span class="st_h">'Ma recherche'</span><span class="sy0">,</span>
  <span class="st_h">'qt'</span> <span class="sy0">=&gt;</span> <span class="st_h">'dismax'</span><span class="sy0">,</span> <span class="co1">// Nous envoyons directement le contenu du formulaire à Solr, par conséquent dismax est plus adapté</span>
  <span class="st_h">'qf'</span> <span class="sy0">=&gt;</span> <span class="st_h">'title^2 body'</span><span class="sy0">,</span> <span class="co1">// On cherche dans title et body, avec une priorité plus importante pour le champ title</span>
  <span class="st_h">'hl'</span> <span class="sy0">=&gt;</span> <span class="st_h">'true'</span><span class="sy0">,</span> <span class="co1">// Activitation de l'highlighting</span>
  <span class="st_h">'hl.fl'</span> <span class="sy0">=&gt;</span> <span class="st_h">'title,body'</span><span class="sy0">,</span>  <span class="co1">// Seuls ces champs seront pris en compte pour la surbrillance</span>
  <span class="st_h">'f.body.hl.alternateField'</span> <span class="sy0">=&gt;</span> <span class="st_h">'body'</span><span class="sy0">,</span>  <span class="co1">// Si aucun snippet n'est trouvé dans le champ body, on renvoie le champ complet</span>
  <span class="st_h">'hl.fragsize'</span> <span class="sy0">=&gt;</span> <span class="nu0">150</span><span class="sy0">,</span>  <span class="co1">// Les snippets font 150 caractères…</span>
  <span class="st_h">'hl.snippets'</span> <span class="sy0">=&gt;</span> <span class="nu0">3</span><span class="sy0">,</span>  <span class="co1">// … et on renvoie 3 snippets maxi </span>
  <span class="st_h">'hl.maxAlternateFieldLength'</span> <span class="sy0">=&gt;</span> <span class="nu0">200</span><span class="sy0">,</span>  <span class="co1">// Si on renvoie le champ body comple, on limite à 200 caractères</span>
  <span class="st_h">'hl.simple.pre'</span> <span class="sy0">=&gt;</span> <span class="st_h">'&lt;strong&gt;'</span><span class="sy0">,</span>  <span class="co1">// On veut que notre surbrillance soit encadrée par des balises 'strong'</span>
  <span class="st_h">'hl.simple.post'</span> <span class="sy0">=&gt;</span> <span class="st_h">'&lt;/strong&gt;'</span><span class="sy0">,</span>
<span class="br0">&#41;</span><span class="br0">&#41;</span><span class="sy0">;</span></pre></div></div>

<p>Si vous ex&eacute;cutez une requ&ecirc;te contenant ces param&egrave;tres, vous noterez que la r&eacute;ponse est s&eacute;par&eacute;e en deux parties&nbsp;: d&#8217;un c&ocirc;t&eacute;, les r&eacute;sultats, et de l&#8217;autre, les <em>snippets</em> g&eacute;n&eacute;r&eacute;s. Vous remarquerez &eacute;galement que les snippets en question sont a peu pr&egrave;s illisibles &agrave; l&#8217;&oelig;il nu, mais &ccedil;a, c&#8217;est une autre histoire.</p>

<div class="wp_syntax"><div class="code"><pre class="xml" style="font-family:monospace;color: #ccc; font: 12px Consolas, Lucida Console, Monaco, monospace;"><span class="sc3"><span class="re1">&lt;?xml</span> <span class="re0">version</span>=<span class="st0">&quot;1.0&quot;</span> <span class="re0">encoding</span>=<span class="st0">&quot;UTF-8&quot;</span><span class="re2">?&gt;</span></span>
<span class="sc3"><span class="re1">&lt;response<span class="re2">&gt;</span></span></span>
<span class="sc3"><span class="re1">&lt;responseHeader<span class="re2">&gt;</span></span></span>
  <span class="sc3"><span class="re1">&lt;bla<span class="re2">&gt;</span></span></span>bla<span class="sc3"><span class="re1">&lt;/bla<span class="re2">&gt;</span></span></span>
<span class="sc3"><span class="re1">&lt;/responseHeader<span class="re2">&gt;</span></span></span>
<span class="sc3"><span class="re1">&lt;result</span> <span class="re0">name</span>=<span class="st0">&quot;response&quot;</span> <span class="re0">numFound</span>=<span class="st0">&quot;50&quot;</span> <span class="re0">start</span>=<span class="st0">&quot;0&quot;</span><span class="re2">&gt;</span></span>
  <span class="sc3"><span class="re1">&lt;doc<span class="re2">&gt;</span></span></span>
    <span class="sc3"><span class="re1">&lt;str</span> <span class="re0">name</span>=<span class="st0">&quot;id&quot;</span><span class="re2">&gt;</span></span>9<span class="sc3"><span class="re1">&lt;/str<span class="re2">&gt;</span></span></span>
    <span class="sc3"><span class="re1">&lt;int</span> <span class="re0">name</span>=<span class="st0">&quot;revision&quot;</span><span class="re2">&gt;</span></span>2338009<span class="sc3"><span class="re1">&lt;/int<span class="re2">&gt;</span></span></span>
    <span class="sc3"><span class="re1">&lt;date</span> <span class="re0">name</span>=<span class="st0">&quot;timestamp&quot;</span><span class="re2">&gt;</span></span>2002-10-31T09:16:01Z<span class="sc3"><span class="re1">&lt;/date<span class="re2">&gt;</span></span></span>
    <span class="sc3"><span class="re1">&lt;str</span> <span class="re0">name</span>=<span class="st0">&quot;title&quot;</span><span class="re2">&gt;</span></span>Algèbre de boole<span class="sc3"><span class="re1">&lt;/str<span class="re2">&gt;</span></span></span>
  <span class="sc3"><span class="re1">&lt;/doc<span class="re2">&gt;</span></span></span>
…
<span class="sc3"><span class="re1">&lt;/result<span class="re2">&gt;</span></span></span>
<span class="sc3"><span class="re1">&lt;lst</span> <span class="re0">name</span>=<span class="st0">&quot;highlighting&quot;</span><span class="re2">&gt;</span></span>
  <span class="sc3"><span class="re1">&lt;lst</span> <span class="re0">name</span>=<span class="st0">&quot;9&quot;</span><span class="re2">&gt;</span></span>
    <span class="sc3"><span class="re1">&lt;arr</span> <span class="re0">name</span>=<span class="st0">&quot;body&quot;</span><span class="re2">&gt;</span></span>
      <span class="sc3"><span class="re1">&lt;str<span class="re2">&gt;</span></span></span>
L' <span class="sc1">&amp;lt;</span>em<span class="sc1">&amp;gt;</span>alg<span class="sc1">&amp;amp;</span>#232;bre<span class="sc1">&amp;lt;</span>/em<span class="sc1">&amp;gt;</span> g<span class="sc1">&amp;amp;</span>#233;n<span class="sc1">&amp;amp;</span>#233;rale, ou <span class="sc1">&amp;lt;</span>em<span class="sc1">&amp;gt;</span>alg<span class="sc1">&amp;amp;</span>#232;bre<span class="sc1">&amp;lt;</span>/em<span class="sc1">&amp;gt;</span> abstraite, est la branche des math<span class="sc1">&amp;amp;</span>#233;matiques qui porte principalement sur l'<span class="sc1">&amp;amp;</span>#233;tude des structures <span class="sc1">&amp;lt;</span>em<span class="sc1">&amp;gt;</span>alg<span class="sc1">&amp;amp;</span>#233;briques<span class="sc1">&amp;lt;</span>/em<span class="sc1">&amp;gt;</span> et de leurs relations. L'appellation <span class="sc1">&amp;lt;</span>em<span class="sc1">&amp;gt;</span>alg<span class="sc1">&amp;amp;</span>#232;bre<span class="sc1">&amp;lt;</span>/em<span class="sc1">&amp;gt;</span> g<span class="sc1">&amp;amp;</span>#233;n<span class="sc1">&amp;amp;</span>#233;rale s'oppose <span class="sc1">&amp;amp;</span>#224; celle dalg<span class="sc1">&amp;amp;</span>#232;bre <span class="sc1">&amp;amp;</span>#233;l<span class="sc1">&amp;amp;</span>#233;mentaire ; cette derni<span class="sc1">&amp;amp;</span>#232;re enseigne le calcul <span class="sc1">&amp;lt;</span>em<span class="sc1">&amp;gt;</span>alg<span class="sc1">&amp;amp;</span>#233;brique<span class="sc1">&amp;lt;</span>/em<span class="sc1">&amp;gt;</span>, c'est-<span class="sc1">&amp;amp;</span>#224;-dire les r<span class="sc1">&amp;amp;</span>#232;gles de manipulation des formules et des expressions <span class="sc1">&amp;lt;</span>em<span class="sc1">&amp;gt;</span>alg<span class="sc1">&amp;amp;</span>#233;briques<span class="sc1">&amp;lt;</span>/em<span class="sc1">&amp;gt;</span>.Historiquement, les structures <span class="sc1">&amp;lt;</span>em<span class="sc1">&amp;gt;</span>alg<span class="sc1">&amp;amp;</span>#233;briques<span class="sc1">&amp;lt;</span>/em<span class="sc1">&amp;gt;</span> sont apparues dans diff<span class="sc1">&amp;amp;</span>#233;rents<span class="sc3"><span class="re1">&lt;/str<span class="re2">&gt;</span></span></span>
    <span class="sc3"><span class="re1">&lt;/arr<span class="re2">&gt;</span></span></span>
  <span class="sc3"><span class="re1">&lt;/lst<span class="re2">&gt;</span></span></span>
…</pre></div></div>

<p>Parser la r&eacute;ponse sera finalement assez simple. Allez, je vous donne le code que j&#8217;utilise (pour les curieux, <a href="http://www.twig-project.org/">c&#8217;est du twig</a>, mais ce serait pareil avec n&#8217;importe quel autre moteur de template).</p>

<div class="wp_syntax"><div class="code"><pre class="xml" style="font-family:monospace;color: #ccc; font: 12px Consolas, Lucida Console, Monaco, monospace;"><span class="sc3"><span class="re1">&lt;section</span> <span class="re0">id</span>=<span class="st0">&quot;results&quot;</span><span class="re2">&gt;</span></span>
  {% block results %}
    <span class="sc3"><span class="re1">&lt;h3<span class="re2">&gt;</span></span></span>{{ results.numFound }} résultats trouvés<span class="sc3"><span class="re1">&lt;/h3<span class="re2">&gt;</span></span></span>
    <span class="sc3"><span class="re1">&lt;ol<span class="re2">&gt;</span></span></span>
    {% for doc in results.docs %}
      {% block doc %}
        <span class="sc3"><span class="re1">&lt;li<span class="re2">&gt;</span></span></span>
          <span class="sc3"><span class="re1">&lt;a</span> <span class="re0">href</span>=<span class="st0">&quot;http://fr.wikipedia.org/wiki/{{ doc.title }}&quot;</span> <span class="re2">/&gt;</span></span>{{ doc.title }}<span class="sc3"><span class="re1">&lt;/a<span class="re2">&gt;</span></span></span>
          <span class="sc3"><span class="re1">&lt;p<span class="re2">&gt;</span></span></span>
          {% for snippet in highlighting[doc.id].body %}
            {{ snippet }}…
          {% endfor %}
          <span class="sc3"><span class="re1">&lt;/p<span class="re2">&gt;</span></span></span>
        <span class="sc3"><span class="re1">&lt;/li<span class="re2">&gt;</span></span></span>
      {% endblock %}
    {% endfor %}
    <span class="sc3"><span class="re1">&lt;/ol<span class="re2">&gt;</span></span></span>
  {% endblock %}
<span class="sc3"><span class="re1">&lt;/section<span class="re2">&gt;</span></span></span>
{% endif %}
<span class="sc3"><span class="re1">&lt;/section<span class="re2">&gt;</span></span></span></pre></div></div>

<h2>Mais&hellip; Mais&hellip; Attendez<span class="fine">&nbsp;</span>! C&#8217;est tout crade<span class="fine">&nbsp;</span>!</h2>
<p>Ok, j&#8217;avoue, si vous avez vous m&ecirc;me test&eacute; tout &ccedil;a jusqu&#8217;ici, vous avez remarqu&eacute; que le r&eacute;sultat n&#8217;est pas vraiment exploitable. Le texte que nous indexons, en effet, n&#8217;est pas du texte brut&nbsp;: il contient toutes les balises de formattage sp&eacute;cifiques du langage wiki de Wikip&eacute;dia.</p>
<p>Dans notre cas, nous n&#8217;aurons jamais besoin de ces informations aussi l&#8217;id&eacute;al serait de pouvoir filtrer ces balises d&eacute;s l&#8217;import des donn&eacute;es.</p>
<p>Si vous aussi, vous lisez la documentation de Solr en famille le soir au coin du feu<sup><a href="#footnote-3-510" id="footnote-link-3-510" title="Voir la note.">[3]</a></sup>, vous savez d&eacute;j&agrave; que <a href="http://wiki.apache.org/solr/DataImportHandler#Transformer">Solr propose des <em>transformers</em></a> (rien &agrave; voir avec les robots), qui permettent de filtrer les donn&eacute;es lors de l&#8217;import.</p>
<p>D&#8217;ailleurs, si vous regardez le code du fichier <em>data-config.xml</em> que nous avons utilis&eacute;, il utilise d&eacute;j&agrave; deux <em>transformers</em>. Tout ce que nous allons faire, c&#8217;est cr&eacute;er notre <em>transformer custom</em>. Allez hop<span class="fine">&nbsp;</span>! Un peu de java<span class="fine">&nbsp;</span>! Cr&eacute;ez un fichier WikiTransformer.java</p>

<div class="wp_syntax"><div class="code"><pre class="java" style="font-family:monospace;color: #ccc; font: 12px Consolas, Lucida Console, Monaco, monospace;"><span class="kw1">import</span> <span class="co2">java.util.*</span><span class="sy0">;</span>
<span class="kw1">import</span> <span class="co2">java.io.*</span><span class="sy0">;</span>
<span class="kw1">import</span> <span class="co2">info.bliki.wiki.filter.PlainTextConverter</span><span class="sy0">;</span>
<span class="kw1">import</span> <span class="co2">info.bliki.wiki.model.WikiModel</span><span class="sy0">;</span>
&nbsp;
<span class="kw1">public</span> <span class="kw1">class</span> WikiTransformerAlt <span class="br0">&#123;</span>
  <span class="kw1">public</span> <a href="http://www.google.com/search?hl=en&amp;q=allinurl%3Aobject+java.sun.com&amp;btnI=I%27m%20Feeling%20Lucky"><span class="kw3">Object</span></a> transformRow<span class="br0">&#40;</span>Map<span class="sy0">&lt;</span>String, Object<span class="sy0">&gt;</span> row<span class="br0">&#41;</span> <span class="br0">&#123;</span>
    <a href="http://www.google.com/search?hl=en&amp;q=allinurl%3Astring+java.sun.com&amp;btnI=I%27m%20Feeling%20Lucky"><span class="kw3">String</span></a> body <span class="sy0">=</span> <span class="br0">&#40;</span><a href="http://www.google.com/search?hl=en&amp;q=allinurl%3Astring+java.sun.com&amp;btnI=I%27m%20Feeling%20Lucky"><span class="kw3">String</span></a><span class="br0">&#41;</span>row.<span class="me1">get</span><span class="br0">&#40;</span><span class="st0">&quot;body&quot;</span><span class="br0">&#41;</span><span class="sy0">;</span>
&nbsp;
    <a href="http://www.google.com/search?hl=en&amp;q=allinurl%3Astringwriter+java.sun.com&amp;btnI=I%27m%20Feeling%20Lucky"><span class="kw3">StringWriter</span></a> writer <span class="sy0">=</span> <span class="kw1">new</span> <a href="http://www.google.com/search?hl=en&amp;q=allinurl%3Astringwriter+java.sun.com&amp;btnI=I%27m%20Feeling%20Lucky"><span class="kw3">StringWriter</span></a><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">;</span>
&nbsp;
    WikiModel wikiModel <span class="sy0">=</span> <span class="kw1">new</span> WikiModel<span class="br0">&#40;</span><span class="st0">&quot;http://www.mywiki.com/wiki/${image}&quot;</span>, <span class="st0">&quot;http://www.mywiki.com/wiki/${title}&quot;</span><span class="br0">&#41;</span><span class="sy0">;</span>
    <a href="http://www.google.com/search?hl=en&amp;q=allinurl%3Astring+java.sun.com&amp;btnI=I%27m%20Feeling%20Lucky"><span class="kw3">String</span></a> plainStr <span class="sy0">=</span> wikiModel.<span class="me1">render</span><span class="br0">&#40;</span><span class="kw1">new</span> PlainTextConverter<span class="br0">&#40;</span><span class="br0">&#41;</span>, body<span class="br0">&#41;</span><span class="sy0">;</span>
&nbsp;
    row.<span class="me1">put</span><span class="br0">&#40;</span><span class="st0">&quot;body&quot;</span>, plainStr<span class="br0">&#41;</span><span class="sy0">;</span>
&nbsp;
    <span class="kw1">return</span> row<span class="sy0">;</span>
  <span class="br0">&#125;</span>
<span class="br0">&#125;</span></pre></div></div>

<p>Deux choses &agrave; noter&nbsp;: ce transformer n&#8217;est pas optimal, le nom du champ &agrave; parser (body) est cod&eacute; en dur. Mais bon, &ccedil;a ira pour cette fois (mais ne recommencez pas). Deuxi&egrave;me chose, j&#8217;utilise la <a href="http://code.google.com/p/gwtwiki/">librairie java gwtwiki</a> pour faire le boulot de traduction wiki -> texte brut. Cette librairie est pour le moment lacunaire (ou alors je m&#8217;en sert mal), et le r&eacute;sultat ne sera pas parfait. Il faudra faire avec.</p>
<p>La compilation du fichier en .jar et son installation sortent du scope de cet article, aussi je vous laisse utiliser votre moteur de recherche favori&hellip; Bon, ok, je vous file les commandes&nbsp;:</p>

<div class="wp_syntax"><div class="code"><pre class="bash" style="font-family:monospace;color: #ccc; font: 12px Consolas, Lucida Console, Monaco, monospace;">javac <span class="re5">-cp</span> <span class="st0">&quot;/path/vers/bliki/info.bliki.wiki/bliki-core/target/*&quot;</span> WikiTransformer.java
jar <span class="re5">-cf</span> WikiTransformer.jar WikiTransformer.class
<span class="kw2">cp</span> WikiTransformer.jar path<span class="sy0">/</span>vers<span class="sy0">/</span>solr<span class="sy0">/</span>work<span class="sy0">/</span>Jetty_0_0_0_0_8983_solr.war__solr__k1kf17<span class="sy0">/</span>webapp<span class="sy0">/</span>WEB-INF<span class="sy0">/</span>lib<span class="sy0">/</span></pre></div></div>

<p>Notez le chemin de destination pas tr&eacute;s conventionnel&nbsp;: c&#8217;est le seul endroit ou l&#8217;autoload semble capter le jar. Si un expert java passe par l&agrave; et peut m&#8217;expliquer&hellip;</p>
<p>Il ne nous reste plus qu&#8217;&agrave; mettre &agrave; jour notre fichier de configuration d&#8217;import, et &agrave; relancer l&#8217;indexation&nbsp;:</p>

<div class="wp_syntax"><div class="code"><pre class="xml" style="font-family:monospace;color: #ccc; font: 12px Consolas, Lucida Console, Monaco, monospace;"><span class="sc3"><span class="re1">&lt;dataConfig<span class="re2">&gt;</span></span></span>
    <span class="sc3"><span class="re1">&lt;dataSource</span> <span class="re0">type</span>=<span class="st0">&quot;FileDataSource&quot;</span> <span class="re0">encoding</span>=<span class="st0">&quot;UTF-8&quot;</span> <span class="re2">/&gt;</span></span>
    <span class="sc3"><span class="re1">&lt;document<span class="re2">&gt;</span></span></span>
    <span class="sc3"><span class="re1">&lt;entity</span> <span class="re0">name</span>=<span class="st0">&quot;page&quot;</span></span>
<span class="sc3">        <span class="re0">processor</span>=<span class="st0">&quot;XPathEntityProcessor&quot;</span></span>
<span class="sc3">        <span class="re0">stream</span>=<span class="st0">&quot;true&quot;</span></span>
<span class="sc3">        <span class="re0">forEach</span>=<span class="st0">&quot;/mediawiki/page/&quot;</span></span>
<span class="sc3">        <span class="re0">url</span>=<span class="st0">&quot;/var/www/solrdemo/dumps/frwiki-latest-pages-articles.xml&quot;</span></span>
<span class="sc3">        <span class="re0">transformer</span>=<span class="st0">&quot;RegexTransformer,DateFormatTransformer,WikiTransformer&quot;</span></span>
<span class="sc3">        <span class="re2">&gt;</span></span>
        <span class="sc3"><span class="re1">&lt;field</span> <span class="re0">column</span>=<span class="st0">&quot;id&quot;</span>    <span class="re0">xpath</span>=<span class="st0">&quot;/mediawiki/page/id&quot;</span> <span class="re2">/&gt;</span></span>
        <span class="sc3"><span class="re1">&lt;field</span> <span class="re0">column</span>=<span class="st0">&quot;title&quot;</span>     <span class="re0">xpath</span>=<span class="st0">&quot;/mediawiki/page/title&quot;</span> <span class="re2">/&gt;</span></span>
        <span class="sc3"><span class="re1">&lt;field</span> <span class="re0">column</span>=<span class="st0">&quot;body&quot;</span>      <span class="re0">xpath</span>=<span class="st0">&quot;/mediawiki/page/revision/text&quot;</span> <span class="re2">/&gt;</span></span>
        <span class="sc3"><span class="re1">&lt;field</span> <span class="re0">column</span>=<span class="st0">&quot;revision&quot;</span>  <span class="re0">xpath</span>=<span class="st0">&quot;/mediawiki/page/revision/id&quot;</span> <span class="re2">/&gt;</span></span>
        <span class="sc3"><span class="re1">&lt;field</span> <span class="re0">column</span>=<span class="st0">&quot;user&quot;</span>      <span class="re0">xpath</span>=<span class="st0">&quot;/mediawiki/page/revision/contributor/username&quot;</span> <span class="re2">/&gt;</span></span>
        <span class="sc3"><span class="re1">&lt;field</span> <span class="re0">column</span>=<span class="st0">&quot;userId&quot;</span>    <span class="re0">xpath</span>=<span class="st0">&quot;/mediawiki/page/revision/contributor/id&quot;</span> <span class="re2">/&gt;</span></span>
        <span class="sc3"><span class="re1">&lt;field</span> <span class="re0">column</span>=<span class="st0">&quot;timestamp&quot;</span> <span class="re0">xpath</span>=<span class="st0">&quot;/mediawiki/page/revision/timestamp&quot;</span> <span class="re0">dateTimeFormat</span>=<span class="st0">&quot;yyyy-MM-dd'T'hh:mm:ss'Z'&quot;</span> <span class="re2">/&gt;</span></span>
        <span class="sc3"><span class="re1">&lt;field</span> <span class="re0">column</span>=<span class="st0">&quot;$skipDoc&quot;</span>  <span class="re0">regex</span>=<span class="st0">&quot;^(?i)#redirect.*&quot;</span> <span class="re0">replaceWith</span>=<span class="st0">&quot;true&quot;</span> <span class="re0">sourceColName</span>=<span class="st0">&quot;body&quot;</span><span class="re2">/&gt;</span></span>
       <span class="sc3"><span class="re1">&lt;/entity<span class="re2">&gt;</span></span></span>
    <span class="sc3"><span class="re1">&lt;/document<span class="re2">&gt;</span></span></span>
<span class="sc3"><span class="re1">&lt;/dataConfig<span class="re2">&gt;</span></span></span></pre></div></div>

<p>Et voil&agrave;, vous disposez de votre propre moteur de recherche Wikipedia. Les raffinements possibles sont nombreux, je laisse votre imagination vous guider. En attendant, rendez-vous au prochain SudWeb.</p>
<h4>Notes&nbsp;:</h4><ol class="footnotes"><li id="footnote-1-510"> Pour les francophones acharn&eacute;s, <em>surbrillance des r&eacute;sultats</em>, mais c&#8217;est moche   [<a href="#footnote-link-1-510">retour</a>]</li><li id="footnote-2-510">Notez comme s&#8217;ext&eacute;riorise ma haine et ma jalousie envers les gens qui ont la chance de travailler avec un vrai langage de programmation  [<a href="#footnote-link-2-510">retour</a>]</li><li id="footnote-3-510">Allez, avouez, je sais que vous le faites  [<a href="#footnote-link-3-510">retour</a>]</li></ol>]]></content:encoded>
			<wfw:commentRss>http://www.miximum.fr/methodes-et-outils/510-indexer-wikipedia-dans-solr/feed</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Comment PHP me rends fou</title>
		<link>http://www.miximum.fr/musee-des-horreurs/473-comment-php-me-rends-fou</link>
		<comments>http://www.miximum.fr/musee-des-horreurs/473-comment-php-me-rends-fou#comments</comments>
		<pubDate>Fri, 12 Mar 2010 18:07:21 +0000</pubDate>
		<dc:creator>thibault</dc:creator>
				<category><![CDATA[Musée des horreurs]]></category>
		<category><![CDATA[bug]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[symfony]]></category>
		<category><![CDATA[wtf]]></category>

		<guid isPermaLink="false">http://www.miximum.fr/?p=473</guid>
		<description><![CDATA[Chers amis d&#233;veloppeurs bonsoir. Je vous &#233;cris car je souhaite soumettre &#224; votre sagacit&#233; un probl&#232;me &#233;trange. &#192; d&#233;faut de pouvoir me venir en aide (seule ma dignit&#233; m&#8217;emp&#234;che encore de verser des larmes de frustration), peut-&#234;tre au moins cela vous fera-t-il marrer. Je suis victime d&#8217;une version particuli&#232;rement vicieuse du bug de 17h30 (avec [...]]]></description>
			<content:encoded><![CDATA[<p>Chers amis d&eacute;veloppeurs bonsoir. Je vous &eacute;cris car je souhaite soumettre &agrave; votre sagacit&eacute; un probl&egrave;me &eacute;trange. &Agrave; d&eacute;faut de pouvoir me venir en aide (seule ma dignit&eacute; m&#8217;emp&ecirc;che encore de verser des larmes de frustration), peut-&ecirc;tre au moins cela vous fera-t-il marrer. Je suis victime d&#8217;une version particuli&egrave;rement vicieuse du <a href="http://www.miximum.fr/tranche_vie/62-le-bug-de-17h30">bug de 17h30</a> (avec un combo &laquo;&nbsp;bug du vendredi de fin d&#8217;it&eacute;ration&nbsp;&raquo; compte triple).</p>
<p>Projet symfony. Je code, tout se d&eacute;roule bien, fin de semaine, je balance mon code sur le serveur de recette, et je jette un coup d&#8217;&oelig;il sur l&#8217;environnement de prod. Horreur<span class="fine">&nbsp;</span>! Page blanche<span class="fine">&nbsp;</span>! Je v&eacute;rifie la m&ecirc;me page sous l&#8217;environnement de dev. Elle s&#8217;affiche correctement.</p>
<p>Je jette un coup d&#8217;&oelig;il dans la log apache&nbsp;:</p>

<div class="wp_syntax"><div class="code"><pre class="php" style="font-family:monospace;color: #ccc; font: 12px Consolas, Lucida Console, Monaco, monospace;">child pid <span class="nu0">16813</span> <a href="http://www.php.net/exit"><span class="kw3">exit</span></a> signal Segmentation fault <span class="br0">&#40;</span><span class="nu0">11</span><span class="br0">&#41;</span></pre></div></div>

<p>Un sentiment d&#8217;immense lassitude m&#8217;envahit aussit&ocirc;t.</p>
<p>Apr&egrave;s une longue et p&eacute;nible session profilage / debugage avec xdebug, je finit par circonvenir plus ou moins l&#8217;endroit du probl&egrave;me, et parviens m&ecirc;me &agrave; le reproduire &agrave; l&#8217;envie. Vous allez voir, c&#8217;est surprenant&nbsp;:</p>

<div class="wp_syntax"><div class="code"><pre class="php" style="font-family:monospace;color: #ccc; font: 12px Consolas, Lucida Console, Monaco, monospace;"><span class="re0">$category</span> <span class="sy0">=</span> Article<span class="sy0">-&gt;</span><span class="me1">getCategory</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">;</span> <span class="co1">// objet de classe 'Category'</span>
<span class="kw1">echo</span> <span class="re0">$category</span><span class="sy0">;</span>  <span class="co1">// Affiche le titre de la catégorie</span>
<a href="http://www.php.net/printf"><span class="kw3">printf</span></a><span class="br0">&#40;</span><span class="st_h">'%s'</span><span class="sy0">,</span> <span class="re0">$category</span><span class="br0">&#41;</span><span class="sy0">;</span> <span class="co1">// Page blanche, segfault, sacrifice de chatons, etc.</span></pre></div></div>

<p>Amusant, non<span class="fine">&nbsp;</span>? Apr&egrave;s quelques recherches, il semblerait que dans le contexte d&#8217;une fonction *printf, php n&#8217;utilise pas la fonction magique __toString().</p>
<p>Je teste donc&nbsp;:</p>

<div class="wp_syntax"><div class="code"><pre class="php" style="font-family:monospace;color: #ccc; font: 12px Consolas, Lucida Console, Monaco, monospace;"><span class="kw2">&lt;?php</span>
&nbsp;
<span class="kw2">class</span> Toto
<span class="br0">&#123;</span>
  <span class="kw2">private</span> <span class="re0">$tata</span><span class="sy0">;</span>
&nbsp;
  <span class="kw2">public</span> <span class="kw2">function</span> __construct<span class="br0">&#40;</span><span class="re0">$tata</span><span class="br0">&#41;</span>
  <span class="br0">&#123;</span>
    <span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">tata</span> <span class="sy0">=</span> <span class="re0">$tata</span><span class="sy0">;</span>
  <span class="br0">&#125;</span>
&nbsp;
  <span class="kw2">public</span> <span class="kw2">function</span> __toString<span class="br0">&#40;</span><span class="br0">&#41;</span>
  <span class="br0">&#123;</span>
    <span class="kw1">return</span> <span class="br0">&#40;</span>string<span class="br0">&#41;</span> <span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">tata</span> <span class="sy0">.</span> <span class="st0">&quot;<span class="es1">\n</span>&quot;</span><span class="sy0">;</span>
  <span class="br0">&#125;</span>
<span class="br0">&#125;</span>
&nbsp;
<span class="re0">$toto</span> <span class="sy0">=</span> <span class="kw2">new</span> Toto<span class="br0">&#40;</span><span class="st_h">'tutu'</span><span class="br0">&#41;</span><span class="sy0">;</span>
<span class="kw1">echo</span> <span class="re0">$toto</span><span class="sy0">;</span>
<a href="http://www.php.net/printf"><span class="kw3">printf</span></a><span class="br0">&#40;</span><span class="st_h">'%s'</span><span class="sy0">,</span> <span class="re0">$toto</span><span class="br0">&#41;</span><span class="sy0">;</span>
&nbsp;
<span class="co1">// tutu</span>
<span class="co1">// tutu</span></pre></div></div>

<p>WTF<span class="fine">&nbsp;</span>? Mais &ccedil;a marche tr&eacute;s bien<span class="fine">&nbsp;</span>! C&#8217;est donc un probl&egrave;me sp&eacute;cifique &agrave; mon code<span class="fine">&nbsp;</span>? Je poursuis mon d&eacute;buggage, et j&#8217;arrive ici&nbsp;:</p>

<div class="wp_syntax"><div class="code"><pre class="php" style="font-family:monospace;color: #ccc; font: 12px Consolas, Lucida Console, Monaco, monospace;"><span class="re0">$category</span> <span class="sy0">=</span> <span class="re0">$article</span><span class="sy0">-&gt;</span><span class="me1">getCategory</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">;</span>
<span class="co1">// $category est de la classe sfOutputEscaperIteratorDecorator</span>
<span class="co1">// qui hérite de sfOutputEscaperObjectDecorator</span>
<span class="co1">// qui définit une fonction __toString()</span>
&nbsp;
<a href="http://www.php.net/printf"><span class="kw3">printf</span></a><span class="br0">&#40;</span><span class="st_h">'%s'</span><span class="sy0">,</span> <span class="re0">$category</span><span class="br0">&#41;</span><span class="sy0">;</span>
<span class="co1">// Appelle sfOutputEscaperObjectDecorator::__toString</span></pre></div></div>

<p>Bon, allons voir sur place ce qui ne va pas.</p>

<div class="wp_syntax"><div class="code"><pre class="php" style="font-family:monospace;color: #ccc; font: 12px Consolas, Lucida Console, Monaco, monospace;"><span class="kw2">class</span> sfOutputEscaperObjectDecorator
<span class="br0">&#123;</span>
…
  <span class="kw2">public</span> <span class="kw2">function</span> __toString<span class="br0">&#40;</span><span class="br0">&#41;</span>
  <span class="br0">&#123;</span>
    <span class="co1">// Jusqu'ici, tout va bien, mais dés l'instruction return, ça plante</span>
    <span class="kw1">return</span> <span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">escape</span><span class="br0">&#40;</span><span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">escapingMethod</span><span class="sy0">,</span> <span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">value</span><span class="sy0">-&gt;</span>__toString<span class="br0">&#40;</span><span class="br0">&#41;</span><span class="br0">&#41;</span><span class="sy0">;</span>
  <span class="br0">&#125;</span>
&nbsp;
  <span class="co1">// Je modifie donc la fonction pour obtenir ceci :</span>
  <span class="kw2">public</span> <span class="kw2">function</span> __toString<span class="br0">&#40;</span><span class="br0">&#41;</span>
  <span class="br0">&#123;</span>
    <span class="re0">$value</span> <span class="sy0">=</span> <span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">escape</span><span class="br0">&#40;</span><span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">escapingMethod</span><span class="sy0">,</span> <span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">value</span><span class="sy0">-&gt;</span>__toString<span class="br0">&#40;</span><span class="br0">&#41;</span><span class="br0">&#41;</span><span class="sy0">;</span>
    <a href="http://www.php.net/var_dump"><span class="kw3">var_dump</span></a><span class="br0">&#40;</span><a href="http://www.php.net/gettype"><span class="kw3">gettype</span></a><span class="br0">&#40;</span><span class="re0">$value</span><span class="br0">&#41;</span><span class="br0">&#41;</span><span class="sy0">;</span>
    <a href="http://www.php.net/var_dump"><span class="kw3">var_dump</span></a><span class="br0">&#40;</span><span class="re0">$value</span><span class="br0">&#41;</span><span class="sy0">;</span>
    <a href="http://www.php.net/die"><span class="kw3">die</span></a><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">;</span>
    <span class="kw1">return</span> <span class="re0">$value</span><span class="sy0">;</span>
  <span class="br0">&#125;</span>
&nbsp;
<span class="co1">// J'obtiens pour affichage :</span>
<span class="co1">// 'String'</span>
<span class="co1">// 'Titre de ma catégorie'</span>
&nbsp;
<span class="co1">// En revanche :</span>
&nbsp;
  <span class="kw2">public</span> <span class="kw2">function</span> __toString<span class="br0">&#40;</span><span class="br0">&#41;</span>
  <span class="br0">&#123;</span>
    <span class="re0">$value</span> <span class="sy0">=</span> <span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">escape</span><span class="br0">&#40;</span><span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">escapingMethod</span><span class="sy0">,</span> <span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">value</span><span class="sy0">-&gt;</span>__toString<span class="br0">&#40;</span><span class="br0">&#41;</span><span class="br0">&#41;</span><span class="sy0">;</span>
    <a href="http://www.php.net/var_dump"><span class="kw3">var_dump</span></a><span class="br0">&#40;</span><a href="http://www.php.net/gettype"><span class="kw3">gettype</span></a><span class="br0">&#40;</span><span class="re0">$value</span><span class="br0">&#41;</span><span class="br0">&#41;</span><span class="sy0">;</span>
    <a href="http://www.php.net/var_dump"><span class="kw3">var_dump</span></a><span class="br0">&#40;</span><span class="re0">$value</span><span class="br0">&#41;</span><span class="sy0">;</span>
    <span class="co1">// die();</span>
    <span class="kw1">return</span> <span class="re0">$value</span><span class="sy0">;</span>
  <span class="br0">&#125;</span>
&nbsp;
<span class="co1">// Page blanche. Continuons dans l'étrange, avec quelques modifications de la même méthode :</span>
&nbsp;
  <span class="kw2">public</span> <span class="kw2">function</span> __toString<span class="br0">&#40;</span><span class="br0">&#41;</span>
  <span class="br0">&#123;</span>
    <span class="re0">$value</span> <span class="sy0">=</span> <span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">escape</span><span class="br0">&#40;</span><span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">escapingMethod</span><span class="sy0">,</span> <span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">value</span><span class="sy0">-&gt;</span>__toString<span class="br0">&#40;</span><span class="br0">&#41;</span><span class="br0">&#41;</span><span class="sy0">;</span>
    <a href="http://www.php.net/die"><span class="kw3">die</span></a><span class="br0">&#40;</span><span class="st_h">'ici'</span><span class="br0">&#41;</span><span class="sy0">;</span> <span class="co1">// Affiche 'ici'</span>
    <span class="kw1">return</span> <span class="st_h">'toto'</span><span class="sy0">;</span>
  <span class="br0">&#125;</span>
&nbsp;
  <span class="kw2">public</span> <span class="kw2">function</span> __toString<span class="br0">&#40;</span><span class="br0">&#41;</span>
  <span class="br0">&#123;</span>
    <span class="kw1">return</span> <span class="st_h">'toto'</span><span class="sy0">;</span> <span class="co1">// Affiche 'toto'</span>
  <span class="br0">&#125;</span>
&nbsp;
  <span class="kw2">public</span> <span class="kw2">function</span> __toString<span class="br0">&#40;</span><span class="br0">&#41;</span>
  <span class="br0">&#123;</span>
    <span class="re0">$value</span> <span class="sy0">=</span> <span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">escape</span><span class="br0">&#40;</span><span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">escapingMethod</span><span class="sy0">,</span> <span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">value</span><span class="sy0">-&gt;</span>__toString<span class="br0">&#40;</span><span class="br0">&#41;</span><span class="br0">&#41;</span><span class="sy0">;</span>
    <span class="kw1">return</span> <span class="st_h">'toto'</span><span class="sy0">;</span> <span class="co1">// Page blanche</span>
  <span class="br0">&#125;</span>
<span class="br0">&#125;</span></pre></div></div>

<p>Tout &ccedil;a, bien s&ucirc;r, c&#8217;est sur l&#8217;environnement de prod. En dev, tout se passe toujours normalement.</p>
<p>J&#8217;en suis l&agrave;. Si quelqu&#8217;un a une bonne explication. En attendant, je crois que je vais aller me coucher de bonne heure. Bon week-end &agrave; tous.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.miximum.fr/musee-des-horreurs/473-comment-php-me-rends-fou/feed</wfw:commentRss>
		<slash:comments>7</slash:comments>
		</item>
		<item>
		<title>Symfony form : pick or create</title>
		<link>http://www.miximum.fr/tutos/466-symfony-form-pick-or-create</link>
		<comments>http://www.miximum.fr/tutos/466-symfony-form-pick-or-create#comments</comments>
		<pubDate>Tue, 02 Feb 2010 14:40:29 +0000</pubDate>
		<dc:creator>thibault</dc:creator>
				<category><![CDATA[Tutos]]></category>
		<category><![CDATA[doctrine]]></category>
		<category><![CDATA[forms]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[symfony]]></category>

		<guid isPermaLink="false">http://www.miximum.fr/?p=466</guid>
		<description><![CDATA[Here&#8217;s an other tutorial about the Symfony form framework. Last time, we managed to make embedded i18n forms optionals. In today&#8217;s howto, we will talk about embedded forms again. In the last tutorials, we created a form to edit news. Today we will create another form to write interviews. An interview is just an article [...]]]></description>
			<content:encoded><![CDATA[<p>Here&#8217;s an other tutorial about the Symfony form framework. Last time, <a href="http://www.miximum.fr/tutos/449-optional-translation-form-for-i18n-objects-with-symfony-and-doctrine">we managed to make embedded i18n forms optionals</a>. In today&#8217;s howto, we will talk about embedded forms again.</p>
<p>In the last tutorials, we created a form to edit news. Today we will create another form to write interviews. An interview is just an article about a celebrity. So our Interview class will inherit from our Article one, so we can reuse our last time work.</p>
<p>Here&#8217;s the complete schema we will work with&nbsp;:</p>

<div class="wp_syntax"><div class="code"><pre class="yml" style="font-family:monospace;color: #ccc; font: 12px Consolas, Lucida Console, Monaco, monospace;">Article:
  actAs:
    Timestampable: ~
    I18n:
      fields: [ title, body ]
      actAs:
        Sluggable: { fields: [ title ], uniqueBy: [ lang, title ] }
&nbsp;
  columns:
    title: { type: string(255), notnull: true }
    body: { type: clob, notnull: true }
    author: { type: string(255), notnull: false }
&nbsp;
News:
  inheritance:
    extends: Article
    type: concrete
&nbsp;
Celebrity:
  actAs:
    Sluggable:
      fields: [ firstname, lastname ]
  columns:
    firstname: { type: string(255), notnull: true }
    lastname: { type: string(255), notnull: true }
&nbsp;
Interview:
  inheritance:
    extends: Article
    type: concrete
&nbsp;
  columns:
    celebrity_id: { type: integer, notnull: true }
&nbsp;
  relations:
&nbsp;
    Celebrity:
      local: celebrity_id
      foreign: id
      type: one
      foreignType: one
      foreignAlias: Interviews</pre></div></div>

<p>And the fixtures&nbsp;:</p>

<div class="wp_syntax"><div class="code"><pre class="yml" style="font-family:monospace;color: #ccc; font: 12px Consolas, Lucida Console, Monaco, monospace;">Interview:
  i1:
    Author: 'Thibault J.'
    Celebrity:
      firstname: Thibault
      lastname: Jouannic
    Translation:
      fr:
        title: Auto-interview
        body: |
          Bla bla bla question et réponses</pre></div></div>

<p>As usual&hellip;</p>

<div class="wp_syntax"><div class="code"><pre class="bash" style="font-family:monospace;color: #ccc; font: 12px Consolas, Lucida Console, Monaco, monospace;">php symfony build <span class="re5">--all</span> <span class="re5">--and-load</span>
php symfony doctrine:generate-admin backend Interview</pre></div></div>

<h2>Before we start</h2>
<p>Before we start, let&#8217;s stop and think a few seconds. How do we want to manage our celebrity relation into our form<span class="fine">&nbsp;</span>? Take a look at the default form, without any modification&nbsp;:</p>
<div class="postimg"><a href="http://www.flickr.com/photos/24469297@N05/4324656195/" title="01_celebrity" target="_blank"><img src="http://farm3.static.flickr.com/2687/4324656195_1f90822fc6.jpg" alt="01_celebrity" border="0" /></a><br /><small><a href="http://creativecommons.org/licenses/by-sa/2.0/" title="Attribution-ShareAlike License" target="_blank"><img src="http://www.miximum.fr/wp-content/plugins/photo-dropper/images/cc.png" alt="Creative Commons License" border="0" width="16" height="16" align="absmiddle" /></a> </small></div>
<p>Quite bad, isn&#8217;t it<span class="fine">&nbsp;</span>? We could make a little better by adding a toString method.</p>

<div class="wp_syntax"><div class="code"><pre class="php" style="font-family:monospace;color: #ccc; font: 12px Consolas, Lucida Console, Monaco, monospace;"><span class="co1">// lib/model/doctrine/Celebrity.class.php</span>
&nbsp;
<span class="kw2">class</span> Celebrity <span class="kw2">extends</span> BaseCelebrity
<span class="br0">&#123;</span>
  <span class="kw2">public</span> <span class="kw2">function</span> __toString<span class="br0">&#40;</span><span class="br0">&#41;</span>
  <span class="br0">&#123;</span>
    <span class="kw1">return</span> <a href="http://www.php.net/sprintf"><span class="kw3">sprintf</span></a><span class="br0">&#40;</span><span class="st_h">'%s %s'</span><span class="sy0">,</span> <span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">getFirstname</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">,</span> <span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">getLastname</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="br0">&#41;</span><span class="sy0">;</span>
  <span class="br0">&#125;</span>
<span class="br0">&#125;</span></pre></div></div>

<div class="postimg"><a href="http://www.flickr.com/photos/24469297@N05/4325392592/" title="02_celebrity" target="_blank"><img src="http://farm5.static.flickr.com/4066/4325392592_dc60d3e13d.jpg" alt="02_celebrity" border="0" /></a><br /><small><a href="http://creativecommons.org/licenses/by-sa/2.0/" title="Attribution-ShareAlike License" target="_blank"><img src="http://www.miximum.fr/wp-content/plugins/photo-dropper/images/cc.png" alt="Creative Commons License" border="0" width="16" height="16" align="absmiddle" /></a></small></div>
<p>That&#8217;s a bit better. The problem with this form is that you cannot add a new celebrity. If you want to create a new interview, you&#8217;ll have to chose among the existing celebrities. Of course, you could create an other admin module to manage celebrities, but a good programmer is a lazy one. And that would be great if we could create a new celebrity directly in the interview form.</p>
<p>In fact, that&#8217;s quite easy, <a href="http://prendreuncafe.com/blog/post/2009/11/29/Embedding-Relations-in-Forms-with-Symfony-1.3-and-Doctrine">using the embedRelation function</a>.</p>

<div class="wp_syntax"><div class="code"><pre class="php" style="font-family:monospace;color: #ccc; font: 12px Consolas, Lucida Console, Monaco, monospace;"><span class="co1">// lib/form/doctrine/InterviewForm.class.php</span>
<span class="kw2">class</span> InterviewForm <span class="kw2">extends</span> BaseInterviewForm
<span class="br0">&#123;</span>
  <span class="co4">/**
   * @see ArticleForm
   */</span>
  <span class="kw2">public</span> <span class="kw2">function</span> configure<span class="br0">&#40;</span><span class="br0">&#41;</span>
  <span class="br0">&#123;</span>
    parent<span class="sy0">::</span><span class="me2">configure</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">;</span>
&nbsp;
    <a href="http://www.php.net/unset"><span class="kw3">unset</span></a><span class="br0">&#40;</span><span class="re0">$this</span><span class="br0">&#91;</span><span class="st_h">'celebrity_id'</span><span class="br0">&#93;</span><span class="br0">&#41;</span><span class="sy0">;</span>
    <span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">embedRelation</span><span class="br0">&#40;</span><span class="st_h">'Celebrity'</span><span class="br0">&#41;</span><span class="sy0">;</span>
  <span class="br0">&#125;</span>
<span class="br0">&#125;</span></pre></div></div>

<div class="postimg"><a href="http://www.flickr.com/photos/24469297@N05/4325392638/" title="03_celebrity" target="_blank"><img src="http://farm5.static.flickr.com/4015/4325392638_ecf4e66bb4.jpg" alt="03_celebrity" border="0" /></a><br /><small><a href="http://creativecommons.org/licenses/by-sa/2.0/" title="Attribution-ShareAlike License" target="_blank"><img src="http://www.miximum.fr/wp-content/plugins/photo-dropper/images/cc.png" alt="Creative Commons License" border="0" width="16" height="16" align="absmiddle" /></a></small></div>
<p>Here&#8217;s the result, which is quite relevant to what we wanted. But<span class="fine">&nbsp;</span>!? Wait<span class="fine">&nbsp;</span>! There is a regression. What if we want to choose among existing celebrities, the way we used to do<span class="fine">&nbsp;</span>? What we really need is a way to combine both methods actually. We need to be able to choose an existing celebrity OR to create a new one.</p>
<h2>Pick or create</h2>
<p>We will have to use an intermediate form.</p>

<div class="wp_syntax"><div class="code"><pre class="php" style="font-family:monospace;color: #ccc; font: 12px Consolas, Lucida Console, Monaco, monospace;"><span class="co1">// lib/form/doctrine/InterviewForm.class.php</span>
<span class="kw2">class</span> InterviewForm <span class="kw2">extends</span> BaseInterviewForm
<span class="br0">&#123;</span>
  <span class="co4">/**
   * @see ArticleForm
   */</span>
  <span class="kw2">public</span> <span class="kw2">function</span> configure<span class="br0">&#40;</span><span class="br0">&#41;</span>
  <span class="br0">&#123;</span>
    parent<span class="sy0">::</span><span class="me2">configure</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">;</span>
&nbsp;
    <a href="http://www.php.net/unset"><span class="kw3">unset</span></a><span class="br0">&#40;</span><span class="re0">$this</span><span class="br0">&#91;</span><span class="st_h">'celebrity_id'</span><span class="br0">&#93;</span><span class="br0">&#41;</span><span class="sy0">;</span>
&nbsp;
    <span class="re0">$celebrityId</span> <span class="sy0">=</span> <span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">getObject</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span><span class="me1">isNew</span><span class="br0">&#40;</span><span class="br0">&#41;</span> ? <span class="kw4">NULL</span> <span class="sy0">:</span> <span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">getObject</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span><span class="me1">getCelebrity</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span><span class="me1">getId</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">;</span>
    <span class="re0">$form</span> <span class="sy0">=</span> <span class="kw2">new</span> InterviewCelebrityForm<span class="br0">&#40;</span><a href="http://www.php.net/array"><span class="kw3">array</span></a><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">,</span> <a href="http://www.php.net/array"><span class="kw3">array</span></a><span class="br0">&#40;</span><span class="st_h">'celebrity_id'</span> <span class="sy0">=&gt;</span> <span class="re0">$celebrityId</span><span class="br0">&#41;</span><span class="br0">&#41;</span><span class="sy0">;</span>
    <span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">embedForm</span><span class="br0">&#40;</span><span class="st_h">'Celebrity'</span><span class="sy0">,</span> <span class="re0">$form</span><span class="br0">&#41;</span><span class="sy0">;</span>
  <span class="br0">&#125;</span>
<span class="br0">&#125;</span>
&nbsp;
<span class="co1">// lib/form/doctrine/InterviewCelebrityForm.class.php</span>
<span class="kw2">class</span> InterviewCelebrityForm <span class="kw2">extends</span> sfForm
<span class="br0">&#123;</span>
  <span class="kw2">public</span> <span class="kw2">function</span> configure<span class="br0">&#40;</span><span class="br0">&#41;</span>
  <span class="br0">&#123;</span>
    <span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">widgetSchema</span><span class="br0">&#91;</span><span class="st_h">'celebrity_id'</span><span class="br0">&#93;</span> <span class="sy0">=</span> <span class="kw2">new</span> sfWidgetFormDoctrineChoice<span class="br0">&#40;</span><a href="http://www.php.net/array"><span class="kw3">array</span></a><span class="br0">&#40;</span>
      <span class="st_h">'model'</span> <span class="sy0">=&gt;</span> <span class="st_h">'Celebrity'</span><span class="sy0">,</span>
      <span class="st_h">'add_empty'</span> <span class="sy0">=&gt;</span> <span class="kw4">true</span>
    <span class="br0">&#41;</span><span class="br0">&#41;</span><span class="sy0">;</span>
&nbsp;
    <span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">setDefault</span><span class="br0">&#40;</span><span class="st_h">'celebrity_id'</span><span class="sy0">,</span> <span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">getOption</span><span class="br0">&#40;</span><span class="st_h">'celebrity_id'</span><span class="br0">&#41;</span><span class="br0">&#41;</span><span class="sy0">;</span>
&nbsp;
    <span class="re0">$form</span> <span class="sy0">=</span> <span class="kw2">new</span> CelebrityForm<span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">;</span>
    <span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">embedForm</span><span class="br0">&#40;</span><span class="st_h">'new_celebrity'</span><span class="sy0">,</span> <span class="re0">$form</span><span class="br0">&#41;</span><span class="sy0">;</span>
&nbsp;
    <span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">widgetSchema</span><span class="sy0">-&gt;</span><span class="me1">setLabel</span><span class="br0">&#40;</span><span class="st_h">'celebrity_id'</span><span class="sy0">,</span> <span class="st_h">'Choose one…'</span><span class="br0">&#41;</span><span class="sy0">;</span>
    <span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">widgetSchema</span><span class="sy0">-&gt;</span><span class="me1">setLabel</span><span class="br0">&#40;</span><span class="st_h">'new_celebrity'</span><span class="sy0">,</span> <span class="st_h">'or create a new.'</span><span class="br0">&#41;</span><span class="sy0">;</span>
  <span class="br0">&#125;</span>
<span class="br0">&#125;</span></pre></div></div>

<div class="postimg"><a href="http://www.flickr.com/photos/24469297@N05/4325392696/" title="04_celebrity" target="_blank"><img src="http://farm3.static.flickr.com/2702/4325392696_aa940a7fb8.jpg" alt="04_celebrity" border="0" /></a><br /><small><a href="http://creativecommons.org/licenses/by-sa/2.0/" title="Attribution-ShareAlike License" target="_blank"><img src="http://www.miximum.fr/wp-content/plugins/photo-dropper/images/cc.png" alt="Creative Commons License" border="0" width="16" height="16" align="absmiddle" /></a></small></div>
<p>Try to edit the existing interview, and look at the nice result. However, if you try to save the form, you&#8217;ll have a lot of validations errors. There is still some work to do before we can get rid of all those red alerts.</p>
<div class="postimg"><a href="http://www.flickr.com/photos/24469297@N05/4324656371/" title="05_celebrity" target="_blank"><img src="http://farm5.static.flickr.com/4034/4324656371_64d6c5eb2f.jpg" alt="05_celebrity" border="0" /></a><br /><small><a href="http://creativecommons.org/licenses/by-sa/2.0/" title="Attribution-ShareAlike License" target="_blank"><img src="http://www.miximum.fr/wp-content/plugins/photo-dropper/images/cc.png" alt="Creative Commons License" border="0" width="16" height="16" align="absmiddle" /></a></small></div>
<h2>Form validation</h2>
<p>The form validation strategy is quite simple. If an existing celebrity is chosen with the drop down list, the firstname and lastname should be ignored. If the list is empty, we must validate the firstname and lastname. At last, we must throw an error if all the fields are empty.</p>

<div class="wp_syntax"><div class="code"><pre class="php" style="font-family:monospace;color: #ccc; font: 12px Consolas, Lucida Console, Monaco, monospace;"><span class="co1">// lib/form/doctrine/InterviewCelebrityForm.class.php</span>
&nbsp;
    <span class="co1">// add this  at the end of the configure() function</span>
    <span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">validatorSchema</span><span class="br0">&#91;</span><span class="st_h">'celebrity_id'</span><span class="br0">&#93;</span> <span class="sy0">=</span> <span class="kw2">new</span> sfValidatorPass<span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">;</span>
&nbsp;
    <span class="re0">$celebrityValidatorSchema</span> <span class="sy0">=</span> clone <span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">validatorSchema</span><span class="br0">&#91;</span><span class="st_h">'new_celebrity'</span><span class="br0">&#93;</span><span class="sy0">;</span>
    <span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">validatorSchema</span><span class="br0">&#91;</span><span class="st_h">'new_celebrity'</span><span class="br0">&#93;</span> <span class="sy0">=</span> <span class="kw2">new</span> sfValidatorPass<span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">;</span>
&nbsp;
    <span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">validatorSchema</span><span class="sy0">-&gt;</span><span class="me1">setPostValidator</span><span class="br0">&#40;</span>
      <span class="kw2">new</span> sfValidatorAnd<span class="br0">&#40;</span><a href="http://www.php.net/array"><span class="kw3">array</span></a><span class="br0">&#40;</span>
        <span class="kw2">new</span> sfValidatorOr<span class="br0">&#40;</span><a href="http://www.php.net/array"><span class="kw3">array</span></a><span class="br0">&#40;</span>
          <span class="kw2">new</span> sfValidatorSchemaFilter<span class="br0">&#40;</span><span class="st_h">'celebrity_id'</span><span class="sy0">,</span> <span class="kw2">new</span> sfValidatorDoctrineChoice<span class="br0">&#40;</span><a href="http://www.php.net/array"><span class="kw3">array</span></a><span class="br0">&#40;</span>
            <span class="st_h">'model'</span> <span class="sy0">=&gt;</span> <span class="st_h">'Celebrity'</span><span class="sy0">,</span>
            <span class="st_h">'required'</span> <span class="sy0">=&gt;</span> <span class="kw4">true</span>
          <span class="br0">&#41;</span><span class="br0">&#41;</span><span class="br0">&#41;</span><span class="sy0">,</span>
          <span class="kw2">new</span> sfValidatorSchemaFilter<span class="br0">&#40;</span><span class="st_h">'new_celebrity'</span><span class="sy0">,</span> <span class="re0">$celebrityValidatorSchema</span><span class="br0">&#41;</span>
        <span class="br0">&#41;</span><span class="br0">&#41;</span><span class="sy0">,</span>
        <span class="kw2">new</span> sfValidatorCallback<span class="br0">&#40;</span><a href="http://www.php.net/array"><span class="kw3">array</span></a><span class="br0">&#40;</span>
          <span class="st_h">'callback'</span> <span class="sy0">=&gt;</span> <a href="http://www.php.net/array"><span class="kw3">array</span></a><span class="br0">&#40;</span><span class="re0">$this</span><span class="sy0">,</span> <span class="st_h">'checkCelebrity'</span><span class="br0">&#41;</span>
        <span class="br0">&#41;</span><span class="br0">&#41;</span>
      <span class="br0">&#41;</span><span class="br0">&#41;</span>
    <span class="br0">&#41;</span><span class="sy0">;</span>
&nbsp;
  <span class="co1">// add this function into the class</span>
  <span class="co4">/**
   * Either who choose an existing celebrity, either we create a new one
   *
   * Unset values according to the choice we made
   **/</span>
  <span class="kw2">public</span> <span class="kw2">function</span> checkCelebrity<span class="br0">&#40;</span><span class="re0">$validator</span><span class="sy0">,</span> <span class="re0">$values</span><span class="sy0">,</span> <span class="re0">$argument</span><span class="br0">&#41;</span>
  <span class="br0">&#123;</span>
    <span class="kw1">if</span><span class="br0">&#40;</span><span class="sy0">!</span><a href="http://www.php.net/empty"><span class="kw3">empty</span></a><span class="br0">&#40;</span><span class="re0">$values</span><span class="br0">&#91;</span><span class="st_h">'celebrity_id'</span><span class="br0">&#93;</span><span class="br0">&#41;</span><span class="br0">&#41;</span>
    <span class="br0">&#123;</span>
      <a href="http://www.php.net/unset"><span class="kw3">unset</span></a><span class="br0">&#40;</span>
        <span class="re0">$this</span><span class="br0">&#91;</span><span class="st_h">'new_celebrity'</span><span class="br0">&#93;</span><span class="sy0">,</span>
        <span class="re0">$values</span><span class="br0">&#91;</span><span class="st_h">'new_celebrity'</span><span class="br0">&#93;</span>
      <span class="br0">&#41;</span><span class="sy0">;</span>
    <span class="br0">&#125;</span>
    <span class="kw1">else</span>
    <span class="br0">&#123;</span>
      <a href="http://www.php.net/unset"><span class="kw3">unset</span></a><span class="br0">&#40;</span>
        <span class="re0">$this</span><span class="br0">&#91;</span><span class="st_h">'celebrity_id'</span><span class="br0">&#93;</span><span class="sy0">,</span>
        <span class="re0">$values</span><span class="br0">&#91;</span><span class="st_h">'celebrity_id'</span><span class="br0">&#93;</span>
      <span class="br0">&#41;</span><span class="sy0">;</span>
    <span class="br0">&#125;</span>
&nbsp;
    <span class="kw1">return</span> <span class="re0">$values</span><span class="sy0">;</span>
  <span class="br0">&#125;</span></pre></div></div>

<p>Try differents combinations to submit your form. The validation should behave correctly now.</p>
<h2>Saving your form</h2>
<p>You may have noticed that our interview isn&#8217;t updated correctly after we submit the form. We will have to override the doUpdateObject method in order to update the object correctly on form validation.</p>

<div class="wp_syntax"><div class="code"><pre class="php" style="font-family:monospace;color: #ccc; font: 12px Consolas, Lucida Console, Monaco, monospace;"><span class="co1">// lib/form/doctrine/InterviewForm.class.php</span>
  <span class="kw2">public</span> <span class="kw2">function</span> doUpdateObject<span class="br0">&#40;</span><span class="re0">$values</span><span class="br0">&#41;</span>
  <span class="br0">&#123;</span>
    <span class="kw1">if</span><span class="br0">&#40;</span><span class="sy0">!</span><a href="http://www.php.net/empty"><span class="kw3">empty</span></a><span class="br0">&#40;</span><span class="re0">$values</span><span class="br0">&#91;</span><span class="st_h">'Celebrity'</span><span class="br0">&#93;</span><span class="br0">&#91;</span><span class="st_h">'celebrity_id'</span><span class="br0">&#93;</span><span class="br0">&#41;</span><span class="br0">&#41;</span>
    <span class="br0">&#123;</span>
      <span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">getObject</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span><span class="me1">setCelebrityId</span><span class="br0">&#40;</span><span class="re0">$values</span><span class="br0">&#91;</span><span class="st_h">'Celebrity'</span><span class="br0">&#93;</span><span class="br0">&#91;</span><span class="st_h">'celebrity_id'</span><span class="br0">&#93;</span><span class="br0">&#41;</span><span class="sy0">;</span>
    <span class="br0">&#125;</span>
    <span class="kw1">else</span>
    <span class="br0">&#123;</span>
      <span class="re0">$celebrity</span> <span class="sy0">=</span> <span class="kw2">new</span> Celebrity<span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">;</span>
      <span class="re0">$celebrity</span><span class="sy0">-&gt;</span><span class="me1">fromArray</span><span class="br0">&#40;</span><span class="re0">$values</span><span class="br0">&#91;</span><span class="st_h">'Celebrity'</span><span class="br0">&#93;</span><span class="br0">&#91;</span><span class="st_h">'new_celebrity'</span><span class="br0">&#93;</span><span class="br0">&#41;</span><span class="sy0">;</span>
      <span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">getObject</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span><span class="me1">Celebrity</span> <span class="sy0">=</span> <span class="re0">$celebrity</span><span class="sy0">;</span>
    <span class="br0">&#125;</span>
&nbsp;
    <a href="http://www.php.net/unset"><span class="kw3">unset</span></a><span class="br0">&#40;</span>
      <span class="re0">$values</span><span class="br0">&#91;</span><span class="st_h">'Celebrity'</span><span class="br0">&#93;</span><span class="sy0">,</span>
      <span class="re0">$this</span><span class="br0">&#91;</span><span class="st_h">'Celebrity'</span><span class="br0">&#93;</span>
    <span class="br0">&#41;</span><span class="sy0">;</span>
&nbsp;
    parent<span class="sy0">::</span><span class="me2">doUpdateObject</span><span class="br0">&#40;</span><span class="re0">$values</span><span class="br0">&#41;</span><span class="sy0">;</span>
  <span class="br0">&#125;</span></pre></div></div>

<p>That&#8217;s it<span class="fine">&nbsp;</span>! Everything should be alright now. But just to make sure, here&#8217;s a few tests.</p>
<h2>Test your forms</h2>
<p>We are reusing some code from the last day, so I just will output the new part here.</p>

<div class="wp_syntax"><div class="code"><pre class="php" style="font-family:monospace;color: #ccc; font: 12px Consolas, Lucida Console, Monaco, monospace;">// test/function/backend/interviewActions.class.php
&nbsp;
<span class="kw2">&lt;?php</span>
&nbsp;
<span class="kw1">include</span><span class="br0">&#40;</span><a href="http://www.php.net/dirname"><span class="kw3">dirname</span></a><span class="br0">&#40;</span><span class="kw4">__FILE__</span><span class="br0">&#41;</span><span class="sy0">.</span><span class="st_h">'/../../bootstrap/functional.php'</span><span class="br0">&#41;</span><span class="sy0">;</span>
&nbsp;
<span class="re0">$browser</span> <span class="sy0">=</span> <span class="kw2">new</span> sfBackendTestFunctional<span class="br0">&#40;</span><span class="kw2">new</span> sfBrowser<span class="br0">&#40;</span><span class="br0">&#41;</span><span class="br0">&#41;</span><span class="sy0">;</span>
<span class="re0">$browser</span><span class="sy0">-&gt;</span><span class="me1">loadData</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">;</span>
<span class="re0">$browser</span><span class="sy0">-&gt;</span><span class="me1">setTester</span><span class="br0">&#40;</span><span class="st_h">'doctrine'</span><span class="sy0">,</span> <span class="st_h">'sfTesterDoctrine'</span><span class="br0">&#41;</span><span class="sy0">;</span>
&nbsp;
<span class="re0">$interview</span> <span class="sy0">=</span> Doctrine<span class="sy0">::</span><span class="me2">getTable</span><span class="br0">&#40;</span><span class="st_h">'Interview'</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
  <span class="me1">createQuery</span><span class="br0">&#40;</span><span class="st_h">'i'</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
  <span class="me1">leftJoin</span><span class="br0">&#40;</span><span class="st_h">'i.Translation t'</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
  <span class="me1">andWhere</span><span class="br0">&#40;</span><span class="st_h">'t.lang = ?'</span><span class="sy0">,</span> <span class="st_h">'fr'</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
  <span class="me1">orderBy</span><span class="br0">&#40;</span><span class="st_h">'i.id'</span><span class="sy0">,</span> <span class="st_h">'asc'</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
  <span class="me1">fetchOne</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">;</span>
&nbsp;
<span class="re0">$celebrity</span> <span class="sy0">=</span> <span class="kw2">new</span> Celebrity<span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">;</span>
<span class="re0">$celebrity</span><span class="sy0">-&gt;</span><span class="me1">setFirstname</span><span class="br0">&#40;</span><span class="st_h">'toto'</span><span class="br0">&#41;</span><span class="sy0">;</span>
<span class="re0">$celebrity</span><span class="sy0">-&gt;</span><span class="me1">setLastname</span><span class="br0">&#40;</span><span class="st_h">'tutu'</span><span class="br0">&#41;</span><span class="sy0">;</span>
<span class="re0">$celebrity</span><span class="sy0">-&gt;</span><span class="me1">save</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">;</span>
&nbsp;
<span class="re0">$editUrl</span> <span class="sy0">=</span> <a href="http://www.php.net/sprintf"><span class="kw3">sprintf</span></a><span class="br0">&#40;</span><span class="st_h">'/interview/%s/edit'</span><span class="sy0">,</span> <span class="re0">$interview</span><span class="sy0">-&gt;</span><span class="me1">getId</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="br0">&#41;</span><span class="sy0">;</span>
&nbsp;
<span class="re0">$browser</span><span class="sy0">-&gt;</span>
  <span class="me1">get</span><span class="br0">&#40;</span><span class="st_h">'/interview'</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
  <span class="me1">with</span><span class="br0">&#40;</span><span class="st_h">'response'</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span><span class="me1">begin</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
    <span class="me1">isStatusCode</span><span class="br0">&#40;</span><span class="nu0">200</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
  <a href="http://www.php.net/end"><span class="kw3">end</span></a><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
&nbsp;
  <span class="me1">info</span><span class="br0">&#40;</span><span class="st_h">'1 - Interview list'</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
  <span class="me1">with</span><span class="br0">&#40;</span><span class="st_h">'response'</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span><span class="me1">begin</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
    <span class="me1">checkElement</span><span class="br0">&#40;</span><span class="st_h">'h1'</span><span class="sy0">,</span> <span class="st_h">'/Interview List/'</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
    <span class="me1">checkElement</span><span class="br0">&#40;</span><span class="st_h">'body'</span><span class="sy0">,</span> <span class="st_h">'/Thibault Jouannic/'</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
  <a href="http://www.php.net/end"><span class="kw3">end</span></a><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
&nbsp;
<span class="co1">// …</span>
&nbsp;
   info<span class="br0">&#40;</span><span class="st_h">'  2.8 - The current celebrity is already selected'</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
  <span class="me1">get</span><span class="br0">&#40;</span><span class="re0">$editUrl</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
  <span class="me1">with</span><span class="br0">&#40;</span><span class="st_h">'response'</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span><span class="me1">begin</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
    <span class="me1">checkElement</span><span class="br0">&#40;</span><span class="st_h">'#interview_Celebrity_celebrity_id option[selected=&quot;selected&quot;]'</span><span class="sy0">,</span> <a href="http://www.php.net/sprintf"><span class="kw3">sprintf</span></a><span class="br0">&#40;</span><span class="st_h">'/%s/'</span><span class="sy0">,</span> <span class="re0">$interview</span><span class="sy0">-&gt;</span><span class="me1">getCelebrity</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="br0">&#41;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
  <a href="http://www.php.net/end"><span class="kw3">end</span></a><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
&nbsp;
  <span class="me1">info</span><span class="br0">&#40;</span><span class="st_h">'  2.9 - The current celebrity can be updated'</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
  <span class="me1">get</span><span class="br0">&#40;</span><span class="re0">$editUrl</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
  <span class="me1">click</span><span class="br0">&#40;</span><span class="st_h">'Save'</span><span class="sy0">,</span> <a href="http://www.php.net/array"><span class="kw3">array</span></a><span class="br0">&#40;</span><span class="st_h">'interview'</span> <span class="sy0">=&gt;</span> <a href="http://www.php.net/array"><span class="kw3">array</span></a><span class="br0">&#40;</span>
    <span class="st_h">'Celebrity'</span> <span class="sy0">=&gt;</span> <a href="http://www.php.net/array"><span class="kw3">array</span></a><span class="br0">&#40;</span>
      <span class="st_h">'celebrity_id'</span> <span class="sy0">=&gt;</span> <span class="re0">$celebrity</span><span class="sy0">-&gt;</span><span class="me1">getId</span><span class="br0">&#40;</span><span class="br0">&#41;</span>
      <span class="br0">&#41;</span>
    <span class="br0">&#41;</span><span class="br0">&#41;</span><span class="sy0">,</span> <a href="http://www.php.net/array"><span class="kw3">array</span></a><span class="br0">&#40;</span><span class="st_h">'_with_csrf'</span> <span class="sy0">=&gt;</span> <span class="kw4">true</span><span class="br0">&#41;</span>
  <span class="br0">&#41;</span><span class="sy0">-&gt;</span>
  <span class="me1">with</span><span class="br0">&#40;</span><span class="st_h">'form'</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span><span class="me1">begin</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
    <span class="me1">hasErrors</span><span class="br0">&#40;</span><span class="nu0">0</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
  <a href="http://www.php.net/end"><span class="kw3">end</span></a><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
&nbsp;
  <span class="me1">with</span><span class="br0">&#40;</span><span class="st_h">'doctrine'</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span><span class="me1">begin</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
    <span class="me1">check</span><span class="br0">&#40;</span><span class="st_h">'Interview'</span><span class="sy0">,</span> <a href="http://www.php.net/array"><span class="kw3">array</span></a><span class="br0">&#40;</span>
      <span class="st_h">'id'</span> <span class="sy0">=&gt;</span> <span class="re0">$interview</span><span class="sy0">-&gt;</span><span class="me1">getId</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">,</span>
      <span class="st_h">'celebrity_id'</span> <span class="sy0">=&gt;</span> <span class="re0">$celebrity</span><span class="sy0">-&gt;</span><span class="me1">getId</span><span class="br0">&#40;</span><span class="br0">&#41;</span>
    <span class="br0">&#41;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
    <span class="me1">info</span><span class="br0">&#40;</span><span class="st_h">'  2.10 - Updating the celebrity does not create empty records'</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
    <span class="me1">check</span><span class="br0">&#40;</span><span class="st_h">'Celebrity'</span><span class="sy0">,</span> <a href="http://www.php.net/array"><span class="kw3">array</span></a><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">,</span> <span class="nu0">4</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
  <a href="http://www.php.net/end"><span class="kw3">end</span></a><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
&nbsp;
  <span class="me1">info</span><span class="br0">&#40;</span><span class="st_h">'  2.11 - A new celebrity can be created'</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
  <span class="me1">get</span><span class="br0">&#40;</span><span class="re0">$editUrl</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
  <span class="me1">click</span><span class="br0">&#40;</span><span class="st_h">'Save'</span><span class="sy0">,</span> <a href="http://www.php.net/array"><span class="kw3">array</span></a><span class="br0">&#40;</span><span class="st_h">'interview'</span> <span class="sy0">=&gt;</span> <a href="http://www.php.net/array"><span class="kw3">array</span></a><span class="br0">&#40;</span>
    <span class="st_h">'Celebrity'</span> <span class="sy0">=&gt;</span> <a href="http://www.php.net/array"><span class="kw3">array</span></a><span class="br0">&#40;</span>
      <span class="st_h">'celebrity_id'</span> <span class="sy0">=&gt;</span> <span class="st_h">''</span><span class="sy0">,</span>
      <span class="st_h">'NewCelebrity'</span> <span class="sy0">=&gt;</span> <a href="http://www.php.net/array"><span class="kw3">array</span></a><span class="br0">&#40;</span>
        <span class="st_h">'firstname'</span> <span class="sy0">=&gt;</span> <span class="st_h">'oncle'</span><span class="sy0">,</span>
        <span class="st_h">'lastname'</span> <span class="sy0">=&gt;</span> <span class="st_h">'picsou'</span>
      <span class="br0">&#41;</span>
    <span class="br0">&#41;</span>
  <span class="br0">&#41;</span><span class="br0">&#41;</span><span class="sy0">,</span> <a href="http://www.php.net/array"><span class="kw3">array</span></a><span class="br0">&#40;</span><span class="st_h">'_with_csrf'</span> <span class="sy0">=&gt;</span> <span class="kw4">true</span><span class="br0">&#41;</span>
  <span class="br0">&#41;</span><span class="sy0">-&gt;</span>
  <span class="me1">with</span><span class="br0">&#40;</span><span class="st_h">'doctrine'</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span><span class="me1">begin</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
    <span class="me1">check</span><span class="br0">&#40;</span><span class="st_h">'Celebrity'</span><span class="sy0">,</span> <a href="http://www.php.net/array"><span class="kw3">array</span></a><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">,</span> <span class="nu0">5</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
    <span class="me1">check</span><span class="br0">&#40;</span><span class="st_h">'Interview'</span><span class="sy0">,</span> <a href="http://www.php.net/array"><span class="kw3">array</span></a><span class="br0">&#40;</span>
      <span class="st_h">'id'</span> <span class="sy0">=&gt;</span> <span class="re0">$interview</span><span class="sy0">-&gt;</span><span class="me1">getId</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">,</span>
      <span class="st_h">'celebrity_id'</span> <span class="sy0">=&gt;</span> <span class="re0">$celebrity</span><span class="sy0">-&gt;</span><span class="me1">getId</span><span class="br0">&#40;</span><span class="br0">&#41;</span> <span class="sy0">+</span> <span class="nu0">1</span>
    <span class="br0">&#41;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
  <a href="http://www.php.net/end"><span class="kw3">end</span></a><span class="br0">&#40;</span><span class="br0">&#41;</span>
<span class="sy0">;</span></pre></div></div>

<p>That&#8217;s all folks<span class="fine">&nbsp;</span>! See you soon.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.miximum.fr/tutos/466-symfony-form-pick-or-create/feed</wfw:commentRss>
		<slash:comments>8</slash:comments>
		</item>
		<item>
		<title>Optional translation form for I18n objects with Symfony and Doctrine</title>
		<link>http://www.miximum.fr/tutos/449-optional-translation-form-for-i18n-objects-with-symfony-and-doctrine</link>
		<comments>http://www.miximum.fr/tutos/449-optional-translation-form-for-i18n-objects-with-symfony-and-doctrine#comments</comments>
		<pubDate>Fri, 29 Jan 2010 14:23:20 +0000</pubDate>
		<dc:creator>thibault</dc:creator>
				<category><![CDATA[Tutos]]></category>
		<category><![CDATA[doctrine]]></category>
		<category><![CDATA[forms]]></category>
		<category><![CDATA[I18n]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[symfony]]></category>

		<guid isPermaLink="false">http://www.miximum.fr/?p=449</guid>
		<description><![CDATA[photo credit&#160;: kevindooley There are people around here claiming that the Symfony form framework is a gift from the gods. One might say that this statement is a bit exaggerated, but it&#8217;s true that the release of the 1.3/1.4 version of the Symfony brought many improvements, and yes, the form framework can save you a [...]]]></description>
			<content:encoded><![CDATA[<div class="postimg alignleft"><a href="http://www.flickr.com/photos/12836528@N00/2201791390/" title="Scaffolding: Not just for construction workers anymore" target="_blank"><img src="http://farm3.static.flickr.com/2037/2201791390_7bc614e27c_m.jpg" alt="Scaffolding: Not just for construction workers anymore" border="0" /></a><br /><small><a href="http://creativecommons.org/licenses/by/2.0/" title="Attribution License" target="_blank"><img src="http://www.miximum.fr/wp-content/plugins/photo-dropper/images/cc.png" alt="Creative Commons License" border="0" width="16" height="16" align="absmiddle" /></a> <a href="http://www.photodropper.com/photos/" target="_blank">photo</a> credit&nbsp;: <a href="http://www.flickr.com/photos/12836528@N00/2201791390/" title="kevindooley" target="_blank">kevindooley</a></small></div>
<p>There are people around here claiming that the <a href="http://www.symfony-project.org/forms/1_4/">Symfony form framework</a> is a gift from the gods. One might say that this statement is a bit exaggerated, but it&#8217;s true that the release of the 1.3/1.4 version of the Symfony brought many improvements, and yes, the form framework can save you a lot of time.</p>
<p>In one of my current project, I had to developp some basic CMS-like features. In a back-office, an admin should be able to writes different kind of articles (news, interviews&hellip;). Hey<span class="fine">&nbsp;</span>! <a href="http://www.symfony-project.org/more-with-symfony/1_4/en/09-Doctrine-Form-Inheritance">That&#8217;s a job for inheritance<span class="fine">&nbsp;</span>!</a> But wait, there&#8217;s more. Articles can be translated in several languages. <a href="http://www.doctrine-project.org/documentation/manual/1_2/en/behaviors#core-behaviors:i18n">Easy, with the Doctrine I18n behavior</a>.</p>
<p>One last requirement&nbsp;: article translations are optional. One might publish an article in english, an other in french, and a third in both languages. This is a little more tricky, so let&#8217;s see how to do this.</p>
<h2>Some references</h2>
<p>Before we start, here are the wonderful ressources I read to get my way out&nbsp;:</p>
<p><a href="http://prendreuncafe.com/blog/post/2009/11/29/Embedding-Relations-in-Forms-with-Symfony-1.3-and-Doctrine"> http://prendreuncafe.com/blog/post/2009/11/29/Embedding-Relations-in-Forms-with-Symfony-1.3-and-Doctrine</a><br />
<a href="http://symfonyguide.wordpress.com/2009/09/28/symfony-forms-saving-process/"> http://symfonyguide.wordpress.com/2009/09/28/symfony-forms-saving-process/</a><br />
<a href="http://vousavezchoisi.com/img/formsdiagram.png"> http://vousavezchoisi.com/img/formsdiagram.png</a><br />
<a href="http://www.symfony-project.org/more-with-symfony/1_4/en/06-Advanced-Forms#chapter_06_ignoring_embedded_forms"> http://www.symfony-project.org/more-with-symfony/1_4/en/06-Advanced-Forms#chapter_06_ignoring_embedded_forms</a><br />
<a href="http://www.ze-technology.com/2009/06/05/tests-fonctionnels-avec-symfony-gerer-lidentification/"> http://www.ze-technology.com/2009/06/05/tests-fonctionnels-avec-symfony-gerer-lidentification/</a></p>
<h2>Let&#8217;s start with the schema</h2>

<div class="wp_syntax"><div class="code"><pre class="yml" style="font-family:monospace;color: #ccc; font: 12px Consolas, Lucida Console, Monaco, monospace;">Article:
  actAs:
    Timestampable: ~
    I18n:
      fields: [ title, body ]
      actAs:
        Sluggable: { fields: [ title ], uniqueBy: [ lang, title ] }
&nbsp;
  columns:
    title: { type: string(255), notnull: true }
    body: { type: clob, notnull: true }
    author: { type: string(255), notnull: false }
&nbsp;
News:
  inheritance:
    extends: Article
    type: concrete</pre></div></div>

<p>As it&#8217;s friday, I will also give you some fixtures&nbsp;:</p>

<div class="wp_syntax"><div class="code"><pre class="yml" style="font-family:monospace;color: #ccc; font: 12px Consolas, Lucida Console, Monaco, monospace;">News:
  n1:
    author: 'Fantomas'
    Translation:
      fr:
        title: 'OK, l''Ipad est sorti. Vous allez me foutre la paix maintentant ?'
        body: |
          Vous commencez serieusement à me gonfler avec vos articles et vos tweets sur
          une techno inutile et bardée de DRM.
&nbsp;
  n2:
    author: 'Garcin Fony'
    Translation:
      fr:
        title: 'Sinon, à part ça, ça va ?'
        body: |
          Allez, pour me calmer, je vais me refaire un p'tit café</pre></div></div>

<p>Let&#8217;s build the whole thing, and generate the admin in the same time.</p>

<div class="wp_syntax"><div class="code"><pre class="bash" style="font-family:monospace;color: #ccc; font: 12px Consolas, Lucida Console, Monaco, monospace;">php symfony doctrine:build <span class="re5">--all</span> <span class="re5">--and-load</span>
php symfony generate:app backend
php symfony doctrine:generate-admin backend News</pre></div></div>

<p>Check the module admin we&#8217;ve just build. Click on the first &laquo;&nbsp;&nbsp;edit&nbsp;&nbsp;&raquo; button. Wait a minute<span class="fine">&nbsp;</span>? The I18n fields are just missing, how are we gonna edit our articles<span class="fine">&nbsp;</span>? If you&#8217;re not familiar with the I18n behavior, you must know that the content is split into two tables. The new table, &laquo;&nbsp;&nbsp;article_translation&nbsp;&nbsp;&raquo;, holds all the I18n fields.</p>

<div class="wp_syntax"><div class="code"><pre class="sql" style="font-family:monospace;color: #ccc; font: 12px Consolas, Lucida Console, Monaco, monospace;">mysql<span class="sy0">&gt;</span> <span class="kw1">SELECT</span> <span class="sy0">*</span> <span class="kw1">FROM</span> news;
<span class="sy0">+</span><span class="co1">----+-------------+---------------------+---------------------+</span>
<span class="sy0">|</span> id <span class="sy0">|</span> author      <span class="sy0">|</span> created_at          <span class="sy0">|</span> updated_at          <span class="sy0">|</span>
<span class="sy0">+</span><span class="co1">----+-------------+---------------------+---------------------+</span>
<span class="sy0">|</span>  <span class="nu0">1</span> <span class="sy0">|</span> Fantomas    <span class="sy0">|</span> <span class="nu0">2010</span><span class="sy0">-</span>01<span class="sy0">-</span><span class="nu0">29</span> <span class="nu0">12</span>:<span class="nu0">14</span>:<span class="nu0">46</span> <span class="sy0">|</span> <span class="nu0">2010</span><span class="sy0">-</span>01<span class="sy0">-</span><span class="nu0">29</span> <span class="nu0">12</span>:<span class="nu0">14</span>:<span class="nu0">46</span> <span class="sy0">|</span> 
<span class="sy0">|</span>  <span class="nu0">2</span> <span class="sy0">|</span> Garcin Fony <span class="sy0">|</span> <span class="nu0">2010</span><span class="sy0">-</span>01<span class="sy0">-</span><span class="nu0">29</span> <span class="nu0">12</span>:<span class="nu0">14</span>:<span class="nu0">46</span> <span class="sy0">|</span> <span class="nu0">2010</span><span class="sy0">-</span>01<span class="sy0">-</span><span class="nu0">29</span> <span class="nu0">12</span>:<span class="nu0">14</span>:<span class="nu0">46</span> <span class="sy0">|</span> 
<span class="sy0">+</span><span class="co1">----+-------------+---------------------+---------------------+</span>
&nbsp;
mysql<span class="sy0">&gt;</span> <span class="kw1">SELECT</span> id<span class="sy0">,</span> lang<span class="sy0">,</span> title <span class="kw1">FROM</span> news_translation;
<span class="sy0">+</span><span class="co1">----+------+------------------------------------------------------------------+</span>
<span class="sy0">|</span> id <span class="sy0">|</span> lang <span class="sy0">|</span> title                                                            <span class="sy0">|</span>
<span class="sy0">+</span><span class="co1">----+------+------------------------------------------------------------------+</span>
<span class="sy0">|</span>  <span class="nu0">1</span> <span class="sy0">|</span> fr   <span class="sy0">|</span> OK<span class="sy0">,</span> l<span class="st0">'Ipad est sorti. Vous allez me foutre la paix maintentant ? | 
|  2 | fr   | Sinon, à part ça, ça va ?                                     | 
+----+------+------------------------------------------------------------------+</span></pre></div></div>

<p>So, to be able to edit our translations, you have to embed the I18n forms, using the embedI18 function. Let&#8217;s do that.</p>

<div class="wp_syntax"><div class="code"><pre class="php" style="font-family:monospace;color: #ccc; font: 12px Consolas, Lucida Console, Monaco, monospace;"><span class="co1">// lib/form/doctrine/NewsForm.class.php</span>
<span class="kw2">class</span> NewsForm <span class="kw2">extends</span> BaseNewsForm
<span class="br0">&#123;</span>
  <span class="co4">/**
   * @see ArticleForm
   */</span>
  <span class="kw2">public</span> <span class="kw2">function</span> configure<span class="br0">&#40;</span><span class="br0">&#41;</span>
  <span class="br0">&#123;</span>
    parent<span class="sy0">::</span><span class="me2">configure</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">;</span>
    <span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">embedI18n</span><span class="br0">&#40;</span><a href="http://www.php.net/array"><span class="kw3">array</span></a><span class="br0">&#40;</span><span class="st_h">'fr'</span><span class="sy0">,</span> <span class="st_h">'en'</span><span class="br0">&#41;</span><span class="br0">&#41;</span><span class="sy0">;</span>
  <span class="br0">&#125;</span>
<span class="br0">&#125;</span></pre></div></div>

<p>Reload the edit page. Now you can edit your news in french and english. Pretty cool, isn&#8217;t it<span class="fine">&nbsp;</span>?</p>
<h2>Let&#8217;s refactor this mess</h2>
<p>Since now, it&#8217;s been pretty easy. However, the code we&#8217;ve written is bad. It&#8217;s bad, for the following reasons&nbsp;:</p>
<ol>
<li>Each time we will add a new article type (e.g. interview, etc.), we will have to update it&#8217;s configure method&nbsp;<span class="fine">&nbsp;</span>;</li>
<li>Each time we will change the available languages (to add a new one, or disable an existing one), we will have to edit every forms&nbsp;<span class="fine">&nbsp;</span>;</li>
</ol>
<p>Fortunately, since Symfony 1.3, <a href="http://www.symfony-project.org/tutorial/1_4/en/whats-new#chapter_a2fae23c9403b0e9ec99806fccf6b53e_sub_form_classes_inheritance">the form inheritance follow the model structure</a>. Notice that NewsForm extends BaseNewsForm, which extends ArticleForm.</p>
<p>Let&#8217;s refactor our code&nbsp;:</p>

<div class="wp_syntax"><div class="code"><pre class="yml" style="font-family:monospace;color: #ccc; font: 12px Consolas, Lucida Console, Monaco, monospace;"># config/app.yml
all:
  cultures:
    enabled:
      fr: Français
      en: English</pre></div></div>


<div class="wp_syntax"><div class="code"><pre class="php" style="font-family:monospace;color: #ccc; font: 12px Consolas, Lucida Console, Monaco, monospace;"><span class="co1">// lib/form/doctrine/NewsForm.class.php</span>
&nbsp;
<span class="co1">// Revert the changes we added there</span>
<span class="kw2">class</span> NewsForm <span class="kw2">extends</span> BaseNewsForm
<span class="br0">&#123;</span>
  <span class="co4">/**
   * @see ArticleForm
   */</span>
  <span class="kw2">public</span> <span class="kw2">function</span> configure<span class="br0">&#40;</span><span class="br0">&#41;</span>
  <span class="br0">&#123;</span>
    parent<span class="sy0">::</span><span class="me2">configure</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">;</span>
  <span class="br0">&#125;</span>
<span class="br0">&#125;</span>
&nbsp;
<span class="co1">// lib/form/doctrine/ArticleForm.class.php</span>
<span class="kw2">class</span> ArticleForm <span class="kw2">extends</span> BaseArticleForm
<span class="br0">&#123;</span>
  <span class="co4">/**
   * Available languages
   *
   * @var array $languages
   **/</span>
  protected <span class="re0">$langages</span><span class="sy0">;</span>
&nbsp;
  <span class="kw2">public</span> <span class="kw2">function</span> configure<span class="br0">&#40;</span><span class="br0">&#41;</span>
  <span class="br0">&#123;</span>
    <span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">languages</span> <span class="sy0">=</span> sfConfig<span class="sy0">::</span><span class="me2">get</span><span class="br0">&#40;</span><span class="st_h">'app_cultures_enabled'</span><span class="br0">&#41;</span><span class="sy0">;</span>
&nbsp;
    <span class="re0">$langs</span> <span class="sy0">=</span> <a href="http://www.php.net/array_keys"><span class="kw3">array_keys</span></a><span class="br0">&#40;</span><span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">languages</span><span class="br0">&#41;</span><span class="sy0">;</span>
&nbsp;
    <span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">embedI18n</span><span class="br0">&#40;</span><span class="re0">$langs</span><span class="br0">&#41;</span><span class="sy0">;</span>
    <span class="kw1">foreach</span><span class="br0">&#40;</span><span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">languages</span> <span class="kw1">as</span> <span class="re0">$lang</span> <span class="sy0">=&gt;</span> <span class="re0">$label</span><span class="br0">&#41;</span>
    <span class="br0">&#123;</span>
      <span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">widgetSchema</span><span class="br0">&#91;</span><span class="re0">$lang</span><span class="br0">&#93;</span><span class="sy0">-&gt;</span><span class="me1">setLabel</span><span class="br0">&#40;</span><span class="re0">$label</span><span class="br0">&#41;</span><span class="sy0">;</span>
    <span class="br0">&#125;</span>
  <span class="br0">&#125;</span>
<span class="br0">&#125;</span></pre></div></div>

<p>Reload the edit form. Now, no matter how many articles sub-types you add, you still can configure available languages from one place.</p>
<h2>Translation edition</h2>
<p>Let&#8217;s try our brand new admin module by editing a news. If you used the fixtures I provided, you should have a french article, with no english translation. Try to update the french version, and click on the &#8216;Save&#8217; button.</p>
<p>This is a miserable failure. We can&#8217;t save the form, because the english fields are required. We&#8217;re stuck, we have to fill every translation for an article before we can save it. Wouldn&#8217;t it be nice if we could make a translation form optional<span class="fine">&nbsp;</span>? Let&#8217;s add a requirement&nbsp;: if every fields in a translation form are empty, it just should be ignored.</p>
<p>To achieve this goal, we will update the doBind method. Here&#8217;s the new ArticleForm class.</p>

<div class="wp_syntax"><div class="code"><pre class="php" style="font-family:monospace;color: #ccc; font: 12px Consolas, Lucida Console, Monaco, monospace;"><span class="co1">// lib/form/doctrine/ArticleForm.class.php</span>
<span class="kw2">class</span> ArticleForm <span class="kw2">extends</span> BaseArticleForm
<span class="br0">&#123;</span>
  <span class="co4">/**
   * Available languages
   *
   * @var array $languages
   **/</span>
  protected <span class="re0">$langages</span><span class="sy0">;</span>
&nbsp;
  <span class="kw2">public</span> <span class="kw2">function</span> configure<span class="br0">&#40;</span><span class="br0">&#41;</span>
  <span class="br0">&#123;</span>
    <span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">languages</span> <span class="sy0">=</span> sfConfig<span class="sy0">::</span><span class="me2">get</span><span class="br0">&#40;</span><span class="st_h">'app_cultures_enabled'</span><span class="br0">&#41;</span><span class="sy0">;</span>
&nbsp;
    <span class="re0">$langs</span> <span class="sy0">=</span> <a href="http://www.php.net/array_keys"><span class="kw3">array_keys</span></a><span class="br0">&#40;</span><span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">languages</span><span class="br0">&#41;</span><span class="sy0">;</span>
&nbsp;
    <span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">embedI18n</span><span class="br0">&#40;</span><span class="re0">$langs</span><span class="br0">&#41;</span><span class="sy0">;</span>
    <span class="kw1">foreach</span><span class="br0">&#40;</span><span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">languages</span> <span class="kw1">as</span> <span class="re0">$lang</span> <span class="sy0">=&gt;</span> <span class="re0">$label</span><span class="br0">&#41;</span>
    <span class="br0">&#123;</span>
      <span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">widgetSchema</span><span class="br0">&#91;</span><span class="re0">$lang</span><span class="br0">&#93;</span><span class="sy0">-&gt;</span><span class="me1">setLabel</span><span class="br0">&#40;</span><span class="re0">$label</span><span class="br0">&#41;</span><span class="sy0">;</span>
    <span class="br0">&#125;</span>
  <span class="br0">&#125;</span>
&nbsp;
  <span class="co4">/**
   * Cleans and binds values to the current form
   *
   * Ignore i18n forms when all their fields are empty
   *
   * @see sfForm::doBind
   **/</span>
  protected <span class="kw2">function</span> doBind<span class="br0">&#40;</span><a href="http://www.php.net/array"><span class="kw3">array</span></a> <span class="re0">$values</span><span class="br0">&#41;</span>
  <span class="br0">&#123;</span>
    <span class="kw1">foreach</span><span class="br0">&#40;</span><span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">languages</span> <span class="kw1">as</span> <span class="re0">$lang</span> <span class="sy0">=&gt;</span> <span class="re0">$label</span><span class="br0">&#41;</span>
    <span class="br0">&#123;</span>
      <span class="kw1">if</span><span class="br0">&#40;</span><span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">embeddedI18nFormIsEmpty</span><span class="br0">&#40;</span><span class="re0">$values</span><span class="br0">&#91;</span><span class="re0">$lang</span><span class="br0">&#93;</span><span class="br0">&#41;</span><span class="br0">&#41;</span>
      <span class="br0">&#123;</span>
        <a href="http://www.php.net/unset"><span class="kw3">unset</span></a><span class="br0">&#40;</span>
          <span class="re0">$values</span><span class="br0">&#91;</span><span class="re0">$lang</span><span class="br0">&#93;</span><span class="sy0">,</span>
          <span class="re0">$this</span><span class="br0">&#91;</span><span class="re0">$lang</span><span class="br0">&#93;</span>
        <span class="br0">&#41;</span><span class="sy0">;</span>
      <span class="br0">&#125;</span>
    <span class="br0">&#125;</span>
&nbsp;
    parent<span class="sy0">::</span><span class="me2">doBind</span><span class="br0">&#40;</span><span class="re0">$values</span><span class="br0">&#41;</span><span class="sy0">;</span>
  <span class="br0">&#125;</span>
&nbsp;
  <span class="co4">/**
   * Check if every fields, except for id and lang, are empty
   **/</span>
  protected <span class="kw2">function</span> embeddedI18nFormIsEmpty<span class="br0">&#40;</span><a href="http://www.php.net/array"><span class="kw3">array</span></a> <span class="re0">$values</span><span class="br0">&#41;</span>
  <span class="br0">&#123;</span>
    <span class="kw1">foreach</span><span class="br0">&#40;</span><span class="re0">$values</span> <span class="kw1">as</span> <span class="re0">$key</span> <span class="sy0">=&gt;</span> <span class="re0">$value</span><span class="br0">&#41;</span>
    <span class="br0">&#123;</span>
      <span class="kw1">if</span><span class="br0">&#40;</span><a href="http://www.php.net/in_array"><span class="kw3">in_array</span></a><span class="br0">&#40;</span><span class="re0">$key</span><span class="sy0">,</span> <a href="http://www.php.net/array"><span class="kw3">array</span></a><span class="br0">&#40;</span><span class="st_h">'id'</span><span class="sy0">,</span> <span class="st_h">'lang'</span><span class="br0">&#41;</span><span class="br0">&#41;</span><span class="br0">&#41;</span>
        <span class="kw1">continue</span><span class="sy0">;</span>
&nbsp;
      <span class="kw1">if</span><span class="br0">&#40;</span><span class="st_h">''</span> <span class="sy0">!==</span> <a href="http://www.php.net/trim"><span class="kw3">trim</span></a><span class="br0">&#40;</span><span class="re0">$value</span><span class="br0">&#41;</span><span class="br0">&#41;</span>
      <span class="br0">&#123;</span>
        <span class="kw1">return</span> <span class="kw4">false</span><span class="sy0">;</span>
      <span class="br0">&#125;</span>
    <span class="br0">&#125;</span>
    <span class="kw1">return</span> <span class="kw4">true</span><span class="sy0">;</span>
  <span class="br0">&#125;</span>
<span class="br0">&#125;</span></pre></div></div>

<p>In the overidden doBind method, we check, for every I18n form, if some values has been submited. If not, we just unset the corresponding fields.</p>
<p>Reload the edit form, update the french translation, leave the english field empty, and click on &#8216;Save&#8217;. Hurray, the forms is saved correctly.</p>
<p>However, you should keep reading, because you&#8217;re gonna have a surprise. Let&#8217;s look into the database.</p>

<div class="wp_syntax"><div class="code"><pre class="sql" style="font-family:monospace;color: #ccc; font: 12px Consolas, Lucida Console, Monaco, monospace;">mysql<span class="sy0">&gt;</span> <span class="kw1">SELECT</span> id<span class="sy0">,</span> lang<span class="sy0">,</span> slug <span class="kw1">FROM</span> news_translation;
<span class="sy0">+</span><span class="co1">----+------+--------------------------------------------------------------+</span>
<span class="sy0">|</span> id <span class="sy0">|</span> lang <span class="sy0">|</span> slug                                                         <span class="sy0">|</span>
<span class="sy0">+</span><span class="co1">----+------+--------------------------------------------------------------+</span>
<span class="sy0">|</span>  <span class="nu0">1</span> <span class="sy0">|</span> en   <span class="sy0">|</span>                                                              <span class="sy0">|</span> 
<span class="sy0">|</span>  <span class="nu0">1</span> <span class="sy0">|</span> fr   <span class="sy0">|</span> ok<span class="sy0">-</span>l<span class="sy0">-</span>ipad<span class="sy0">-</span>est<span class="sy0">-</span>sorti<span class="sy0">-</span>vous<span class="sy0">-</span>allez<span class="sy0">-</span>me<span class="sy0">-</span>foutre<span class="sy0">-</span>la<span class="sy0">-</span>paix<span class="sy0">-</span>maintentant <span class="sy0">|</span> 
<span class="sy0">|</span>  <span class="nu0">2</span> <span class="sy0">|</span> fr   <span class="sy0">|</span> sinon<span class="sy0">-</span>a<span class="sy0">-</span>part<span class="sy0">-</span>ca<span class="sy0">-</span>ca<span class="sy0">-</span>va                                        <span class="sy0">|</span> 
<span class="sy0">+</span><span class="co1">----+------+--------------------------------------------------------------</span></pre></div></div>

<p>Something went wrong<span class="fine">&nbsp;</span>! Somewhere in the process, Symfony created an empty translation object for our article. Notice the void &laquo;&nbsp;&nbsp;slug&nbsp;&nbsp;&raquo; field<span class="fine">&nbsp;</span>? If you try to edit another news, you will receive a duplicate content error. WTF<span class="fine">&nbsp;</span>?</p>
<p>I must admit that I don&#8217;t fully understand why this happens (sometimes I think I do, and a few minutes later, I realize I don&#8217;t). However, here&#8217;s the solution to overcome this problem.</p>

<div class="wp_syntax"><div class="code"><pre class="php" style="font-family:monospace;color: #ccc; font: 12px Consolas, Lucida Console, Monaco, monospace;"><span class="co1">// lib/form/doctrine/ArticleForm.class.php</span>
&nbsp;
  <span class="co1">// Add this at the beginnig of the class:</span>
  <span class="co4">/**
   * I18n ignored forms
   **/</span>
  protected <span class="re0">$I18nFormsIgnored</span> <span class="sy0">=</span> <a href="http://www.php.net/array"><span class="kw3">array</span></a><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">;</span>
&nbsp;
  <span class="co1">// update the doBind method:</span>
  <span class="co4">/**
   * Unset i18n forms values when every field is empty
   **/</span>
  protected <span class="kw2">function</span> doBind<span class="br0">&#40;</span><a href="http://www.php.net/array"><span class="kw3">array</span></a> <span class="re0">$values</span><span class="br0">&#41;</span>
  <span class="br0">&#123;</span>
    <span class="kw1">foreach</span><span class="br0">&#40;</span><span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">languages</span> <span class="kw1">as</span> <span class="re0">$lang</span> <span class="sy0">=&gt;</span> <span class="re0">$label</span><span class="br0">&#41;</span>
    <span class="br0">&#123;</span>
      <span class="kw1">if</span><span class="br0">&#40;</span><span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">embeddedI18nFormEmpty</span><span class="br0">&#40;</span><span class="re0">$values</span><span class="br0">&#91;</span><span class="re0">$lang</span><span class="br0">&#93;</span><span class="br0">&#41;</span><span class="br0">&#41;</span>
      <span class="br0">&#123;</span>
        <span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">I18nFormsIgnored</span><span class="br0">&#91;</span><span class="br0">&#93;</span> <span class="sy0">=</span> <span class="re0">$lang</span><span class="sy0">;</span>
        <a href="http://www.php.net/unset"><span class="kw3">unset</span></a><span class="br0">&#40;</span>
          <span class="re0">$values</span><span class="br0">&#91;</span><span class="re0">$lang</span><span class="br0">&#93;</span><span class="sy0">,</span>
          <span class="re0">$this</span><span class="br0">&#91;</span><span class="re0">$lang</span><span class="br0">&#93;</span>
        <span class="br0">&#41;</span><span class="sy0">;</span>
      <span class="br0">&#125;</span>
    <span class="br0">&#125;</span>
&nbsp;
    parent<span class="sy0">::</span><span class="me2">doBind</span><span class="br0">&#40;</span><span class="re0">$values</span><span class="br0">&#41;</span><span class="sy0">;</span>
  <span class="br0">&#125;</span>
&nbsp;
  <span class="co1">// And override the doUpdateObject method:</span>
  <span class="co4">/**
   * Updates the values of the object with the cleaned up values.
   *
   * @param  array $values An array of values
   *
   * @see sfFormDoctrine::doUpdateObject()
   */</span>
  protected <span class="kw2">function</span> doUpdateObject<span class="br0">&#40;</span><span class="re0">$values</span><span class="br0">&#41;</span>
  <span class="br0">&#123;</span>
    parent<span class="sy0">::</span><span class="me2">doUpdateObject</span><span class="br0">&#40;</span><span class="re0">$values</span><span class="br0">&#41;</span><span class="sy0">;</span>
&nbsp;
    <span class="kw1">foreach</span><span class="br0">&#40;</span><span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">I18nFormsIgnored</span> <span class="kw1">as</span> <span class="re0">$lang</span><span class="br0">&#41;</span>
    <span class="br0">&#123;</span>
      <a href="http://www.php.net/unset"><span class="kw3">unset</span></a><span class="br0">&#40;</span><span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">object</span><span class="sy0">-&gt;</span><span class="me1">Translation</span><span class="br0">&#91;</span><span class="re0">$lang</span><span class="br0">&#93;</span><span class="br0">&#41;</span><span class="sy0">;</span>
      <a href="http://www.php.net/unset"><span class="kw3">unset</span></a><span class="br0">&#40;</span><span class="re0">$values</span><span class="br0">&#91;</span><span class="re0">$lang</span><span class="br0">&#93;</span><span class="br0">&#41;</span><span class="sy0">;</span>
    <span class="br0">&#125;</span>
  <span class="br0">&#125;</span></pre></div></div>

<p>Delete the useless line in the mysql database, so we can start on a clean base. Once more, reload the edit form, leave the english translation empty, save the form, et voil&agrave;<span class="fine">&nbsp;</span>! The form is saved, the translation is updated, and no empty line is added in the table.</p>
<h2>Adding and deleting a translation</h2>
<p>Just to be sure, add some data in the english translation title, and try to save the form. You should get a &laquo;&nbsp;&nbsp;body is required&nbsp;&nbsp;&raquo; error, which is the expected behavior. Try to fill correctly the english translation, and save again. Check that the english translation is correctly added in the database. Erase all french fields, save one more time, and check that the french translation is definitly removed from the DB (no dummy entry).</p>
<p>Well, it seems that our form is pretty functional. One last thing&nbsp;: there is still a &laquo;&nbsp;&nbsp;slug&nbsp;&nbsp;&raquo; field in each translation form, that you might want to unset. Easy, just edit the ArticleTranslationForm.class.php file&nbsp;:</p>

<div class="wp_syntax"><div class="code"><pre class="php" style="font-family:monospace;color: #ccc; font: 12px Consolas, Lucida Console, Monaco, monospace;"><span class="co1">// lib/form/doctrine/ArticleTranslationForm.class.php</span>
&nbsp;
<span class="kw2">class</span> ArticleTranslationForm <span class="kw2">extends</span> BaseArticleTranslationForm
<span class="br0">&#123;</span>
  <span class="kw2">public</span> <span class="kw2">function</span> configure<span class="br0">&#40;</span><span class="br0">&#41;</span>
  <span class="br0">&#123;</span>
    <a href="http://www.php.net/unset"><span class="kw3">unset</span></a><span class="br0">&#40;</span><span class="re0">$this</span><span class="br0">&#91;</span><span class="st_h">'slug'</span><span class="br0">&#93;</span><span class="br0">&#41;</span><span class="sy0">;</span>
  <span class="br0">&#125;</span>
<span class="br0">&#125;</span></pre></div></div>

<p>Edit a news. If you&#8217;re using Symfony 1.{3,4}.1, you&#8217;ll see that the damn slug field is still there. It&#8217;s a Symfony bug. Indeed, <a href="http://trac.symfony-project.org/ticket/8199">the translations forms don&#8217;t follow the inheritance schema</a>. Notice that BaseNewsTranslationForm extends BaseFormDoctrine instead of ArticleNewsTranslation.</p>
<p>You&#8217;ll have to manually change that, however, this will be overidden each time you rebuild your forms.</p>

<div class="wp_syntax"><div class="code"><pre class="php" style="font-family:monospace;color: #ccc; font: 12px Consolas, Lucida Console, Monaco, monospace;"><span class="co1">// lib/form/doctrine/base/BaseNewsTranslationForm.class.php</span>
<span class="co1">// Replace</span>
abstract <span class="kw2">class</span> BaseNewsTranslationForm <span class="kw2">extends</span> BaseFormDoctrine
&nbsp;
<span class="co1">// with</span>
abstract <span class="kw2">class</span> BaseNewsTranslationForm <span class="kw2">extends</span> ArticleTranslationForm
&nbsp;
<span class="co1">// lib/form/doctrine/NewsTranslationForm.class.php</span>
<span class="co1">// Edit the configure method</span>
<span class="kw2">class</span> NewsTranslationForm <span class="kw2">extends</span> BaseNewsTranslationForm
<span class="br0">&#123;</span>
  <span class="kw2">public</span> <span class="kw2">function</span> configure<span class="br0">&#40;</span><span class="br0">&#41;</span>
  <span class="br0">&#123;</span>
    parent<span class="sy0">::</span><span class="me2">configure</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">;</span>
  <span class="br0">&#125;</span>
<span class="br0">&#125;</span></pre></div></div>

<p>Now, the slug field should disapear. At the present moment, the bug is still open.</p>
<h2>Let&#8217;s test everything</h2>
<p>As it&#8217;s friday, I feel generous, so as a bonus, I will give you the functional tests to cover our magical form. Enjoy.</p>

<div class="wp_syntax"><div class="code"><pre class="php" style="font-family:monospace;color: #ccc; font: 12px Consolas, Lucida Console, Monaco, monospace;"><span class="co1">// lib/sfBackendTestFunctional.class.php</span>
<span class="co4">/**
 * This class is used to run functional tests in a secured backend
 *
 * Takes care of the login action, and the fixtures loading
 */</span>
<span class="kw2">class</span> sfBackendTestFunctional <span class="kw2">extends</span> sfTestFunctional
<span class="br0">&#123;</span>
  <span class="kw2">public</span> <span class="kw2">function</span> __construct<span class="br0">&#40;</span><span class="re0">$browser</span><span class="sy0">,</span> <span class="re0">$lime</span> <span class="sy0">=</span> <span class="kw4">null</span><span class="sy0">,</span> <span class="re0">$testers</span> <span class="sy0">=</span> <a href="http://www.php.net/array"><span class="kw3">array</span></a><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="br0">&#41;</span>
  <span class="br0">&#123;</span>
    parent<span class="sy0">::</span>__construct<span class="br0">&#40;</span><span class="re0">$browser</span><span class="sy0">,</span> <span class="re0">$lime</span><span class="sy0">,</span> <span class="re0">$testers</span><span class="br0">&#41;</span><span class="sy0">;</span>
  <span class="br0">&#125;</span>
&nbsp;
  <span class="co4">/**
   * Perform user authentication
   *
   * @param   array of String         $user_data
   * @return  sfGuardTestFunctional   $this
   */</span>
  <span class="kw2">public</span> <span class="kw2">function</span> signin<span class="br0">&#40;</span><span class="re0">$user_data</span><span class="br0">&#41;</span>
  <span class="br0">&#123;</span>
   <span class="kw1">return</span> <span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">info</span><span class="br0">&#40;</span><a href="http://www.php.net/sprintf"><span class="kw3">sprintf</span></a><span class="br0">&#40;</span><span class="st_h">'Login as &quot;%s&quot;'</span><span class="sy0">,</span> <span class="re0">$user_data</span><span class="br0">&#91;</span><span class="st_h">'username'</span><span class="br0">&#93;</span><span class="br0">&#41;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
     <span class="me1">get</span><span class="br0">&#40;</span><span class="st_h">'/login'</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
     <span class="me1">click</span><span class="br0">&#40;</span><span class="st_h">'sign in'</span><span class="sy0">,</span> <a href="http://www.php.net/array"><span class="kw3">array</span></a><span class="br0">&#40;</span><span class="st_h">'signin'</span> <span class="sy0">=&gt;</span> <span class="re0">$user_data</span><span class="br0">&#41;</span><span class="sy0">,</span> <a href="http://www.php.net/array"><span class="kw3">array</span></a><span class="br0">&#40;</span><span class="st_h">'_with_csrf'</span> <span class="sy0">=&gt;</span> <span class="kw4">true</span><span class="br0">&#41;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
&nbsp;
     <span class="me1">with</span><span class="br0">&#40;</span><span class="st_h">'form'</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span><span class="me1">begin</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
       <span class="me1">hasErrors</span><span class="br0">&#40;</span><span class="kw4">false</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
     <a href="http://www.php.net/end"><span class="kw3">end</span></a><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
&nbsp;
     <span class="me1">with</span><span class="br0">&#40;</span><span class="st_h">'user'</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span><span class="me1">begin</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
       <span class="me1">isAuthenticated</span><span class="br0">&#40;</span><span class="kw4">true</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
     <a href="http://www.php.net/end"><span class="kw3">end</span></a><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
&nbsp;
     <span class="me1">with</span><span class="br0">&#40;</span><span class="st_h">'request'</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span><span class="me1">begin</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
       <span class="me1">isParameter</span><span class="br0">&#40;</span><span class="st_h">'module'</span><span class="sy0">,</span> <span class="st_h">'sfGuardAuth'</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
       <span class="me1">isParameter</span><span class="br0">&#40;</span><span class="st_h">'action'</span><span class="sy0">,</span> <span class="st_h">'signin'</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
     <a href="http://www.php.net/end"><span class="kw3">end</span></a><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
&nbsp;
     <span class="me1">with</span><span class="br0">&#40;</span><span class="st_h">'response'</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span><span class="me1">begin</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
       <span class="me1">isRedirected</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
       <span class="me1">followRedirect</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
       <a href="http://www.php.net/end"><span class="kw3">end</span></a><span class="br0">&#40;</span><span class="br0">&#41;</span>
    <span class="sy0">;</span>
  <span class="br0">&#125;</span>
  <span class="co4">/**
   * Load project fixtures
   **/</span>
  <span class="kw2">public</span> <span class="kw2">function</span> loadData<span class="br0">&#40;</span><span class="br0">&#41;</span>
  <span class="br0">&#123;</span>
    Doctrine<span class="sy0">::</span><span class="me2">loadData</span><span class="br0">&#40;</span>sfConfig<span class="sy0">::</span><span class="me2">get</span><span class="br0">&#40;</span><span class="st_h">'sf_data_dir'</span><span class="br0">&#41;</span><span class="sy0">.</span><span class="st_h">'/fixtures'</span><span class="br0">&#41;</span><span class="sy0">;</span>
    <span class="kw1">return</span> <span class="re0">$this</span><span class="sy0">;</span>
  <span class="br0">&#125;</span>
<span class="br0">&#125;</span></pre></div></div>


<div class="wp_syntax"><div class="code"><pre class="php" style="font-family:monospace;color: #ccc; font: 12px Consolas, Lucida Console, Monaco, monospace;"><span class="co1">// test/functional/backend/newsActionsTest.php</span>
&nbsp;
<span class="kw1">include</span><span class="br0">&#40;</span><a href="http://www.php.net/dirname"><span class="kw3">dirname</span></a><span class="br0">&#40;</span><span class="kw4">__FILE__</span><span class="br0">&#41;</span><span class="sy0">.</span><span class="st_h">'/../../bootstrap/functional.php'</span><span class="br0">&#41;</span><span class="sy0">;</span>
&nbsp;
<span class="re0">$browser</span> <span class="sy0">=</span> <span class="kw2">new</span> sfBackendTestFunctional<span class="br0">&#40;</span><span class="kw2">new</span> sfBrowser<span class="br0">&#40;</span><span class="br0">&#41;</span><span class="br0">&#41;</span><span class="sy0">;</span>
<span class="re0">$browser</span><span class="sy0">-&gt;</span><span class="me1">loadData</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">;</span>
<span class="re0">$browser</span><span class="sy0">-&gt;</span><span class="me1">setTester</span><span class="br0">&#40;</span><span class="st_h">'doctrine'</span><span class="sy0">,</span> <span class="st_h">'sfTesterDoctrine'</span><span class="br0">&#41;</span><span class="sy0">;</span>
&nbsp;
<span class="re0">$languages</span> <span class="sy0">=</span> sfConfig<span class="sy0">::</span><span class="me2">get</span><span class="br0">&#40;</span><span class="st_h">'app_cultures_enabled'</span><span class="br0">&#41;</span><span class="sy0">;</span>
&nbsp;
<span class="re0">$news</span> <span class="sy0">=</span> Doctrine<span class="sy0">::</span><span class="me2">getTable</span><span class="br0">&#40;</span><span class="st_h">'News'</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
  <span class="me1">createQuery</span><span class="br0">&#40;</span><span class="st_h">'n'</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
  <span class="me1">leftJoin</span><span class="br0">&#40;</span><span class="st_h">'n.Translation t'</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
  <span class="me1">andWhere</span><span class="br0">&#40;</span><span class="st_h">'t.lang = ?'</span><span class="sy0">,</span> <span class="st_h">'fr'</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
  <span class="me1">orderBy</span><span class="br0">&#40;</span><span class="st_h">'n.id'</span><span class="sy0">,</span> <span class="st_h">'asc'</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
  <span class="me1">fetchOne</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">;</span>
&nbsp;
<span class="re0">$editUrl</span> <span class="sy0">=</span> <a href="http://www.php.net/sprintf"><span class="kw3">sprintf</span></a><span class="br0">&#40;</span><span class="st_h">'/news/%s/edit'</span><span class="sy0">,</span> <span class="re0">$news</span><span class="sy0">-&gt;</span><span class="me1">getId</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="br0">&#41;</span><span class="sy0">;</span>
&nbsp;
<span class="re0">$browser</span><span class="sy0">-&gt;</span>
  <span class="co1">// Uncomment this if your backend is protected by a login</span>
  <span class="co1">//signin(array(</span>
  <span class="co1">//  'username' =&gt; 'admin',</span>
  <span class="co1">//  'password' =&gt; 'admin'</span>
  <span class="co1">//)))-&gt;</span>
  get<span class="br0">&#40;</span><span class="st_h">'/news'</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
  <span class="me1">with</span><span class="br0">&#40;</span><span class="st_h">'response'</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span><span class="me1">begin</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
    <span class="me1">isStatusCode</span><span class="br0">&#40;</span><span class="nu0">200</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
  <a href="http://www.php.net/end"><span class="kw3">end</span></a><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
&nbsp;
  <span class="me1">info</span><span class="br0">&#40;</span><span class="st_h">'1 - News list'</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
  <span class="me1">with</span><span class="br0">&#40;</span><span class="st_h">'response'</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span><span class="me1">begin</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
    <span class="me1">checkElement</span><span class="br0">&#40;</span><span class="st_h">'h1'</span><span class="sy0">,</span> <span class="st_h">'/News List/'</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
    <span class="me1">checkElement</span><span class="br0">&#40;</span><span class="st_h">'body'</span><span class="sy0">,</span> <span class="st_h">'/Fantomas/'</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
  <a href="http://www.php.net/end"><span class="kw3">end</span></a><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
&nbsp;
  <span class="me1">info</span><span class="br0">&#40;</span><span class="st_h">'2 - News edit'</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
  <span class="me1">info</span><span class="br0">&#40;</span><span class="st_h">'  2.1 - I18n forms for all languages are embedded'</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
  <span class="me1">get</span><span class="br0">&#40;</span><span class="re0">$editUrl</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
  <span class="me1">with</span><span class="br0">&#40;</span><span class="st_h">'response'</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span><span class="me1">begin</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
    <span class="me1">isStatusCode</span><span class="br0">&#40;</span><span class="nu0">200</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
    <span class="me1">checkElement</span><span class="br0">&#40;</span><span class="st_h">'input[name$=&quot;[title]&quot;]'</span><span class="sy0">,</span> <a href="http://www.php.net/count"><span class="kw3">count</span></a><span class="br0">&#40;</span><span class="re0">$languages</span><span class="br0">&#41;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
  <a href="http://www.php.net/end"><span class="kw3">end</span></a><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
&nbsp;
  <span class="me1">info</span><span class="br0">&#40;</span><span class="st_h">'  2.2 - I18n forms are validated'</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
  <span class="me1">get</span><span class="br0">&#40;</span><span class="re0">$editUrl</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
  <span class="me1">click</span><span class="br0">&#40;</span><span class="st_h">'Save'</span><span class="sy0">,</span> <a href="http://www.php.net/array"><span class="kw3">array</span></a><span class="br0">&#40;</span><span class="st_h">'news'</span> <span class="sy0">=&gt;</span> <a href="http://www.php.net/array"><span class="kw3">array</span></a><span class="br0">&#40;</span>
    <span class="st_h">'en'</span> <span class="sy0">=&gt;</span> <a href="http://www.php.net/array"><span class="kw3">array</span></a><span class="br0">&#40;</span>
      <span class="st_h">'title'</span> <span class="sy0">=&gt;</span> <span class="st_h">'toto'</span><span class="sy0">,</span>
      <span class="st_h">'body'</span> <span class="sy0">=&gt;</span> <span class="st_h">''</span>
    <span class="br0">&#41;</span><span class="br0">&#41;</span>
    <span class="br0">&#41;</span><span class="sy0">,</span> <a href="http://www.php.net/array"><span class="kw3">array</span></a><span class="br0">&#40;</span><span class="st_h">'_with_csrf'</span> <span class="sy0">=&gt;</span> <span class="kw4">true</span><span class="br0">&#41;</span>
  <span class="br0">&#41;</span><span class="sy0">-&gt;</span>
  <span class="me1">with</span><span class="br0">&#40;</span><span class="st_h">'form'</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span><span class="me1">begin</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
    <span class="me1">hasErrors</span><span class="br0">&#40;</span><span class="nu0">1</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
    <span class="me1">isError</span><span class="br0">&#40;</span><span class="st_h">'en[body]'</span><span class="sy0">,</span> <span class="st_h">'required'</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
  <a href="http://www.php.net/end"><span class="kw3">end</span></a><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
&nbsp;
  <span class="me1">info</span><span class="br0">&#40;</span><span class="st_h">'  2.3 - Empty forms are validated'</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
  <span class="me1">get</span><span class="br0">&#40;</span><span class="re0">$editUrl</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
  <span class="me1">click</span><span class="br0">&#40;</span><span class="st_h">'Save'</span><span class="sy0">,</span> <a href="http://www.php.net/array"><span class="kw3">array</span></a><span class="br0">&#40;</span><span class="st_h">'news'</span> <span class="sy0">=&gt;</span> <a href="http://www.php.net/array"><span class="kw3">array</span></a><span class="br0">&#40;</span>
    <span class="st_h">'en'</span> <span class="sy0">=&gt;</span> <a href="http://www.php.net/array"><span class="kw3">array</span></a><span class="br0">&#40;</span>
      <span class="st_h">'title'</span> <span class="sy0">=&gt;</span> <span class="st_h">''</span><span class="sy0">,</span>
      <span class="st_h">'body'</span> <span class="sy0">=&gt;</span> <span class="st_h">''</span>
    <span class="br0">&#41;</span><span class="br0">&#41;</span>
    <span class="br0">&#41;</span><span class="sy0">,</span> <a href="http://www.php.net/array"><span class="kw3">array</span></a><span class="br0">&#40;</span><span class="st_h">'_with_csrf'</span> <span class="sy0">=&gt;</span> <span class="kw4">true</span><span class="br0">&#41;</span>
  <span class="br0">&#41;</span><span class="sy0">-&gt;</span>
  <span class="me1">with</span><span class="br0">&#40;</span><span class="st_h">'form'</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span><span class="me1">begin</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
    <span class="me1">hasErrors</span><span class="br0">&#40;</span><span class="nu0">0</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
  <a href="http://www.php.net/end"><span class="kw3">end</span></a><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
&nbsp;
  <span class="me1">info</span><span class="br0">&#40;</span><span class="st_h">'  2.4 - Translations are updated'</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
  <span class="me1">get</span><span class="br0">&#40;</span><span class="re0">$editUrl</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
  <span class="me1">click</span><span class="br0">&#40;</span><span class="st_h">'Save'</span><span class="sy0">,</span> <a href="http://www.php.net/array"><span class="kw3">array</span></a><span class="br0">&#40;</span><span class="st_h">'news'</span> <span class="sy0">=&gt;</span> <a href="http://www.php.net/array"><span class="kw3">array</span></a><span class="br0">&#40;</span>
    <span class="st_h">'fr'</span> <span class="sy0">=&gt;</span> <a href="http://www.php.net/array"><span class="kw3">array</span></a><span class="br0">&#40;</span>
      <span class="st_h">'title'</span> <span class="sy0">=&gt;</span> <span class="st_h">'toto tutu tata'</span><span class="sy0">,</span>
      <span class="st_h">'body'</span> <span class="sy0">=&gt;</span> <span class="st_h">'riri fifi loulou'</span>
    <span class="br0">&#41;</span><span class="br0">&#41;</span>
    <span class="br0">&#41;</span><span class="sy0">,</span> <a href="http://www.php.net/array"><span class="kw3">array</span></a><span class="br0">&#40;</span><span class="st_h">'_with_csrf'</span> <span class="sy0">=&gt;</span> <span class="kw4">true</span><span class="br0">&#41;</span>
  <span class="br0">&#41;</span><span class="sy0">-&gt;</span>
  <span class="me1">with</span><span class="br0">&#40;</span><span class="st_h">'form'</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span><span class="me1">begin</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
    <span class="me1">hasErrors</span><span class="br0">&#40;</span><span class="nu0">0</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
  <a href="http://www.php.net/end"><span class="kw3">end</span></a><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
&nbsp;
  <span class="me1">with</span><span class="br0">&#40;</span><span class="st_h">'doctrine'</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span><span class="me1">begin</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
    <span class="me1">check</span><span class="br0">&#40;</span><span class="st_h">'NewsTranslation'</span><span class="sy0">,</span> <a href="http://www.php.net/array"><span class="kw3">array</span></a><span class="br0">&#40;</span>
      <span class="st_h">'id'</span> <span class="sy0">=&gt;</span> <span class="re0">$news</span><span class="sy0">-&gt;</span><span class="me1">getId</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">,</span>
      <span class="st_h">'lang'</span> <span class="sy0">=&gt;</span> <span class="st_h">'fr'</span><span class="sy0">,</span>
      <span class="st_h">'title'</span> <span class="sy0">=&gt;</span> <span class="st_h">'toto tutu tata'</span>
    <span class="br0">&#41;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
    <span class="me1">info</span><span class="br0">&#40;</span><span class="st_h">'  2.5 - No empty translation is created'</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
    <span class="me1">check</span><span class="br0">&#40;</span><span class="st_h">'NewsTranslation'</span><span class="sy0">,</span> <a href="http://www.php.net/array"><span class="kw3">array</span></a><span class="br0">&#40;</span>
      <span class="st_h">'id'</span> <span class="sy0">=&gt;</span> <span class="re0">$news</span><span class="sy0">-&gt;</span><span class="me1">getId</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">,</span>
      <span class="st_h">'lang'</span> <span class="sy0">=&gt;</span> <span class="st_h">'en'</span><span class="sy0">,</span>
    <span class="br0">&#41;</span><span class="sy0">,</span> <span class="kw4">false</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
  <a href="http://www.php.net/end"><span class="kw3">end</span></a><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
&nbsp;
  <span class="me1">info</span><span class="br0">&#40;</span><span class="st_h">'  2.6 - New translations can be added'</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
  <span class="me1">get</span><span class="br0">&#40;</span><span class="re0">$editUrl</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
  <span class="me1">click</span><span class="br0">&#40;</span><span class="st_h">'Save'</span><span class="sy0">,</span> <a href="http://www.php.net/array"><span class="kw3">array</span></a><span class="br0">&#40;</span><span class="st_h">'news'</span> <span class="sy0">=&gt;</span> <a href="http://www.php.net/array"><span class="kw3">array</span></a><span class="br0">&#40;</span>
    <span class="st_h">'en'</span> <span class="sy0">=&gt;</span> <a href="http://www.php.net/array"><span class="kw3">array</span></a><span class="br0">&#40;</span>
      <span class="st_h">'title'</span> <span class="sy0">=&gt;</span> <span class="st_h">'toto tutu tata'</span><span class="sy0">,</span>
      <span class="st_h">'body'</span> <span class="sy0">=&gt;</span> <span class="st_h">'riri fifi loulou'</span>
    <span class="br0">&#41;</span><span class="br0">&#41;</span>
    <span class="br0">&#41;</span><span class="sy0">,</span> <a href="http://www.php.net/array"><span class="kw3">array</span></a><span class="br0">&#40;</span><span class="st_h">'_with_csrf'</span> <span class="sy0">=&gt;</span> <span class="kw4">true</span><span class="br0">&#41;</span>
  <span class="br0">&#41;</span><span class="sy0">-&gt;</span>
&nbsp;
  <span class="me1">with</span><span class="br0">&#40;</span><span class="st_h">'form'</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span><span class="me1">begin</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
    <span class="me1">hasErrors</span><span class="br0">&#40;</span><span class="nu0">0</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
  <a href="http://www.php.net/end"><span class="kw3">end</span></a><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
&nbsp;
  <span class="me1">with</span><span class="br0">&#40;</span><span class="st_h">'doctrine'</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span><span class="me1">begin</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
    <span class="me1">check</span><span class="br0">&#40;</span><span class="st_h">'NewsTranslation'</span><span class="sy0">,</span> <a href="http://www.php.net/array"><span class="kw3">array</span></a><span class="br0">&#40;</span>
      <span class="st_h">'id'</span> <span class="sy0">=&gt;</span> <span class="re0">$news</span><span class="sy0">-&gt;</span><span class="me1">getId</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">,</span>
      <span class="st_h">'lang'</span> <span class="sy0">=&gt;</span> <span class="st_h">'en'</span><span class="sy0">,</span>
      <span class="st_h">'title'</span> <span class="sy0">=&gt;</span> <span class="st_h">'toto tutu tata'</span>
    <span class="br0">&#41;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
  <a href="http://www.php.net/end"><span class="kw3">end</span></a><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
&nbsp;
  <span class="me1">info</span><span class="br0">&#40;</span><span class="st_h">'  2.7 - Existing translations can be deleted'</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
  <span class="me1">get</span><span class="br0">&#40;</span><span class="re0">$editUrl</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
  <span class="me1">click</span><span class="br0">&#40;</span><span class="st_h">'Save'</span><span class="sy0">,</span> <a href="http://www.php.net/array"><span class="kw3">array</span></a><span class="br0">&#40;</span><span class="st_h">'news'</span> <span class="sy0">=&gt;</span> <a href="http://www.php.net/array"><span class="kw3">array</span></a><span class="br0">&#40;</span>
    <span class="st_h">'fr'</span> <span class="sy0">=&gt;</span> <a href="http://www.php.net/array"><span class="kw3">array</span></a><span class="br0">&#40;</span>
      <span class="st_h">'title'</span> <span class="sy0">=&gt;</span> <span class="st_h">''</span><span class="sy0">,</span>
      <span class="st_h">'body'</span> <span class="sy0">=&gt;</span> <span class="st_h">''</span>
    <span class="br0">&#41;</span><span class="br0">&#41;</span>
    <span class="br0">&#41;</span><span class="sy0">,</span> <a href="http://www.php.net/array"><span class="kw3">array</span></a><span class="br0">&#40;</span><span class="st_h">'_with_csrf'</span> <span class="sy0">=&gt;</span> <span class="kw4">true</span><span class="br0">&#41;</span>
  <span class="br0">&#41;</span><span class="sy0">-&gt;</span>
  <span class="me1">with</span><span class="br0">&#40;</span><span class="st_h">'form'</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span><span class="me1">begin</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
    <span class="me1">hasErrors</span><span class="br0">&#40;</span><span class="nu0">0</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
  <a href="http://www.php.net/end"><span class="kw3">end</span></a><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
&nbsp;
  <span class="me1">with</span><span class="br0">&#40;</span><span class="st_h">'doctrine'</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span><span class="me1">begin</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
    <span class="me1">check</span><span class="br0">&#40;</span><span class="st_h">'NewsTranslation'</span><span class="sy0">,</span> <a href="http://www.php.net/array"><span class="kw3">array</span></a><span class="br0">&#40;</span>
      <span class="st_h">'id'</span> <span class="sy0">=&gt;</span> <span class="re0">$news</span><span class="sy0">-&gt;</span><span class="me1">getId</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">,</span>
      <span class="st_h">'lang'</span> <span class="sy0">=&gt;</span> <span class="st_h">'fr'</span><span class="sy0">,</span>
    <span class="br0">&#41;</span><span class="sy0">,</span> <span class="kw4">false</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
  <a href="http://www.php.net/end"><span class="kw3">end</span></a><span class="br0">&#40;</span><span class="br0">&#41;</span>
<span class="sy0">;</span></pre></div></div>

<p>Here we are. Hope you find this helpful. I thought some kind of requirement would be pretty common, however, I didn&#8217;t find any directly related howto&#8217;s. If you can think of any other way to achieve this, please, let me know.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.miximum.fr/tutos/449-optional-translation-form-for-i18n-objects-with-symfony-and-doctrine/feed</wfw:commentRss>
		<slash:comments>16</slash:comments>
		</item>
		<item>
		<title>Créer une liste triable avec Symfony et jquery ui</title>
		<link>http://www.miximum.fr/tutos/435-creer-une-liste-triable-avec-symfony-et-jquery-ui</link>
		<comments>http://www.miximum.fr/tutos/435-creer-une-liste-triable-avec-symfony-et-jquery-ui#comments</comments>
		<pubDate>Sat, 23 Jan 2010 12:23:57 +0000</pubDate>
		<dc:creator>thibault</dc:creator>
				<category><![CDATA[Tutos]]></category>
		<category><![CDATA[doctrine]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[jquery]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[symfony]]></category>

		<guid isPermaLink="false">http://www.miximum.fr/?p=435</guid>
		<description><![CDATA[Lors de mes d&#233;veloppements de ces derniers jours, je suis tomb&#233; sur un besoin qui me semblait relativement simple. Je voulais &#234;tre capable de trier rapidement une liste d&#8217;&#233;l&#233;ments. Et comme, malgr&#233; tout, j&#8217;ai un peu gal&#233;r&#233; &#224; trouver des tutoriaux &#224; jour, je vous livre ici le fruit de mes travaux. J&#8217;utiliserai pour ce [...]]]></description>
			<content:encoded><![CDATA[<div class="postimg alignleft"><a href="http://www.flickr.com/photos/24469297@N05/4297558881/" title="Symfony backlog" target="_blank"><img src="http://farm5.static.flickr.com/4068/4297558881_f8b9f64056_m.jpg" alt="Symfony backlog" border="0" /></a><br /><small><a href="http://creativecommons.org/licenses/by-sa/2.0/" title="Attribution-ShareAlike License" target="_blank"><img src="http://www.miximum.fr/wp-content/plugins/photo-dropper/images/cc.png" alt="Creative Commons License" border="0" width="16" height="16" align="absmiddle" /></a> </small></div>
<p>Lors de mes d&eacute;veloppements de ces derniers jours, je suis tomb&eacute; sur un besoin qui me semblait relativement simple. Je voulais &ecirc;tre capable de trier rapidement une liste d&#8217;&eacute;l&eacute;ments. Et comme, malgr&eacute; tout, j&#8217;ai un peu gal&eacute;r&eacute; &agrave; trouver des tutoriaux &agrave; jour, je vous livre ici le fruit de mes travaux.</p>
<p>J&#8217;utiliserai pour ce faire la <a href="http://jqueryui.com/">librairie jquery UI</a>, elle m&ecirc;me bas&eacute;e sur Jquery.</p>
<p>Attention, hein, c&#8217;est samedi matin, je suis mal r&eacute;veill&eacute;, &minus; edit&nbsp;: tellement mal r&eacute;veill&eacute; que j&#8217;ai publi&eacute; l&#8217;article avant de l&#8217;avoir termin&eacute;. fail&hellip; &minus; alors je raccourcirai au maximum. Si je ne suis pas clair, laissez moi des commentaires.</p>
<h2>Comme d&#8217;hab, commen&ccedil;ons par le mod&egrave;le</h2>
<p>Je voulais trouver un outil capable de g&eacute;rer un backlog de produit &agrave; la scrum, histoire de communiquer avec mes clients &agrave; distance. Cependant, malgr&eacute; l&#8217;existence d&#8217;<a href="http://www.icescrum.org/">excellents outils</a> d&eacute;di&eacute;s &agrave; cette <a href="http://www.aubryconseil.com/">sympathique m&eacute;thodologie agile</a>, je n&#8217;ai pas r&eacute;ussi &agrave; trouver quelque chose de vraiment simple et r&eacute;pondant &agrave; mes besoins. Alors, en bon geek, j&#8217;ai choisi de coder le mien.</p>

<div class="wp_syntax"><div class="code"><pre class="yml" style="font-family:monospace;color: #ccc; font: 12px Consolas, Lucida Console, Monaco, monospace;">Project:
  actAs:
    Sluggable:
      fields: [ title ]
      unique: true
&nbsp;
  columns:
    id: { type: integer, primary: true, autoincrement: true }
    title: { type: string(255), notnull: true }
&nbsp;
Story:
  actAs:
    Timestampable: ~
&nbsp;
  columns:
    id: { type: integer, primary: true, autoincrement: true }
    project_id: { type: integer, notnull: true }
    description: { type: clob, notnull: true }
    priority: { type: integer(2), default: 0 }
    effort: { type: integer(2), default: 0 }
&nbsp;
  relations:
    Project:
      foreignAlias: Stories
      local: project_id
      foreign: id
      type: one
      foreignType: many</pre></div></div>

<p>Pour les paresseux, je vous colle aussi les fixtures&nbsp;:</p>

<div class="wp_syntax"><div class="code"><pre class="yml" style="font-family:monospace;color: #ccc; font: 12px Consolas, Lucida Console, Monaco, monospace;">&nbsp;
Project:
  demo:
    title: 'Demo project'
&nbsp;
Story:
  s1:
    description: 'En tant qu''anonyme, je peux m''inscrire sur le site pour devenir membre'
    priority: 1
    effort: 5
    Project: demo
&nbsp;
  s2:
    description: 'En tant que membre, je dispose d''une page d''accueil pour éditer mon profil et voir l''activité de mon réseau'
    priority: 3
    effort: 13
    Project: demo
&nbsp;
  s3:
    description: 'En tant que rédacteur, je peux écrire des articles de type magazine et les publier dans des catégories'
    priority: 2
    effort: 25
    Project: demo</pre></div></div>

<p>Voil&agrave; pour le mod&egrave;le. Je vous laisse construire tout &ccedil;a, vous connaissez le topo. Cr&eacute;ez ensuite un module &laquo;&nbsp;&nbsp;project&nbsp;&nbsp;&raquo;, avec une action &laquo;&nbsp;&nbsp;show&nbsp;&nbsp;&raquo; qui affichera une liste de users stories. Je vous passe le code de l&#8217;action qui n&#8217;a rien de sp&eacute;cial. Dans le template &laquo;&nbsp;&nbsp;showSuccess.php&nbsp;&nbsp;&raquo; du module project, ajoutez&nbsp;:</p>

<div class="wp_syntax"><div class="code"><pre class="php" style="font-family:monospace;color: #ccc; font: 12px Consolas, Lucida Console, Monaco, monospace;">// bla bla bla
&lt;div id=&quot;stories&quot;&gt;
<span class="kw2">&lt;?php</span> include_partial<span class="br0">&#40;</span><span class="st_h">'story/list'</span><span class="sy0">,</span> <a href="http://www.php.net/array"><span class="kw3">array</span></a><span class="br0">&#40;</span><span class="st_h">'stories'</span> <span class="sy0">=&gt;</span> <span class="re0">$project</span><span class="sy0">-&gt;</span><span class="me1">getStories</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="br0">&#41;</span><span class="br0">&#41;</span> <span class="sy1">?&gt;</span>
&lt;/div&gt;</pre></div></div>

<p>Vous aurez bien entendu surcharg&eacute; la fonction &laquo;&nbsp;&nbsp;getStories&nbsp;&nbsp;&raquo; pour trier les histoires par priorit&eacute;.</p>
<p>Cr&eacute;ez ensuite un module &laquo;&nbsp;&nbsp;story&nbsp;&nbsp;&raquo;, et ajoutez-y un template &laquo;&nbsp;&nbsp;_list.php&nbsp;&nbsp;&raquo; &hellip;</p>

<div class="wp_syntax"><div class="code"><pre class="php" style="font-family:monospace;color: #ccc; font: 12px Consolas, Lucida Console, Monaco, monospace;">&lt;table class=&quot;stories&quot;&gt;
&lt;tbody&gt;
<span class="kw2">&lt;?php</span> <span class="kw1">foreach</span><span class="br0">&#40;</span><span class="re0">$stories</span> <span class="kw1">as</span> <span class="re0">$story</span><span class="br0">&#41;</span><span class="sy0">:</span> <span class="sy1">?&gt;</span>
    &lt;tr class=&quot;story&quot; id=&quot;story_<span class="kw2">&lt;?php</span> <span class="kw1">echo</span> <span class="re0">$story</span><span class="sy0">-&gt;</span><span class="me1">getId</span><span class="br0">&#40;</span><span class="br0">&#41;</span> <span class="sy1">?&gt;</span>&quot;&gt;
      <span class="kw2">&lt;?php</span> include_partial<span class="br0">&#40;</span><span class="st_h">'story/detail'</span><span class="sy0">,</span> <a href="http://www.php.net/array"><span class="kw3">array</span></a><span class="br0">&#40;</span><span class="st_h">'story'</span> <span class="sy0">=&gt;</span> <span class="re0">$story</span><span class="br0">&#41;</span><span class="br0">&#41;</span> <span class="sy1">?&gt;</span>
    &lt;/tr&gt;
<span class="kw2">&lt;?php</span> <span class="kw1">endforeach</span> <span class="sy1">?&gt;</span>
&lt;/tbody&gt;
&lt;thead&gt;
  &lt;tr&gt;
    &lt;th&gt;#id&lt;/th&gt;
    &lt;th&gt;Description&lt;/th&gt;
    &lt;th&gt;Effort&lt;/th&gt;
  &lt;/tr&gt;
&lt;/thead&gt;
&lt;/table&gt;</pre></div></div>

<p>Ainsi qu&#8217;un autre template &laquo;&nbsp;&nbsp;_detail.php&nbsp;&nbsp;&raquo;</p>

<div class="wp_syntax"><div class="code"><pre class="php" style="font-family:monospace;color: #ccc; font: 12px Consolas, Lucida Console, Monaco, monospace;">&lt;td&gt;
  &lt;a href=&quot;#&quot; class=&quot;sort-button fg-button fg-button-icon-left ui-state-default ui-corner-all&quot;&gt;
    &lt;span class=&quot;ui-icon ui-icon-arrowthick-2-n-s&quot;&gt;&lt;/span&gt;
    <span class="kw2">&lt;?php</span> <span class="kw1">echo</span> <span class="re0">$story</span><span class="sy0">-&gt;</span><span class="me1">getId</span><span class="br0">&#40;</span><span class="br0">&#41;</span> <span class="sy1">?&gt;</span>
  &lt;a&gt;
&lt;/td&gt;
&lt;td&gt;<span class="kw2">&lt;?php</span> <span class="kw1">echo</span> <span class="re0">$story</span><span class="sy0">-&gt;</span><span class="me1">getDescription</span><span class="br0">&#40;</span><span class="br0">&#41;</span> <span class="sy1">?&gt;</span>&lt;/td&gt;
&lt;td&gt;<span class="kw2">&lt;?php</span> <span class="kw1">echo</span> <span class="re0">$story</span><span class="sy0">-&gt;</span><span class="me1">getEffort</span><span class="br0">&#40;</span><span class="br0">&#41;</span> <span class="sy1">?&gt;</span>&lt;/td&gt;</pre></div></div>

<p>Vous voil&agrave; donc avec une belle liste de users stories, affich&eacute;es par ordre de priorit&eacute;, et que vous souhaiteriez pouvoir r&eacute;ordonner par drag&#8217;n'drop.</p>
<h2>Des p&#8217;tits tris, des p&#8217;tits tris, encore des p&#8217;tits tris&hellip;</h2>
<p>Dans le r&eacute;pertoire web, ajoutez dans votre fichier js maison (cr&eacute;ez le s&#8217;il n&#8217;existe pas) le code suivant.</p>

<div class="wp_syntax"><div class="code"><pre class="javascript" style="font-family:monospace;color: #ccc; font: 12px Consolas, Lucida Console, Monaco, monospace;">$<span class="br0">&#40;</span>document<span class="br0">&#41;</span>.<span class="me1">ready</span><span class="br0">&#40;</span><span class="kw2">function</span><span class="br0">&#40;</span><span class="br0">&#41;</span>
<span class="br0">&#123;</span>
  $<span class="br0">&#40;</span><span class="st0">&quot;#stories table tbody&quot;</span><span class="br0">&#41;</span>.<span class="me1">sortable</span><span class="br0">&#40;</span><span class="br0">&#123;</span>
    <span class="co1">// limitons les déplacements sur l'axe des ordonnées, ce sera plus propre</span>
    axis<span class="sy0">:</span> <span class="st0">'y'</span><span class="sy0">,</span>
&nbsp;
    <span class="co1">// Il faut cliquer sur cet élément pour pouvoir initier le drag'n'drop</span>
    handle<span class="sy0">:</span> <span class="st0">'.sort-button'</span><span class="sy0">,</span>
&nbsp;
    <span class="co1">// Créons un joli trou stylé lors des déplacements</span>
    placeholder<span class="sy0">:</span> <span class="st0">'ui-state-highlight'</span><span class="sy0">,</span>
    forcePlaceholderSize<span class="sy0">:</span> <span class="kw2">true</span><span class="sy0">,</span>
&nbsp;
    <span class="co1">// Cette fonction permet à notre ligne de conserver son formatage lors du déplacement</span>
    <span class="co1">// Pas vraiment utile, mais plus agréable à l'œil</span>
    helper<span class="sy0">:</span> <span class="kw2">function</span><span class="br0">&#40;</span>e<span class="sy0">,</span> tr<span class="br0">&#41;</span>
    <span class="br0">&#123;</span>
      <span class="kw2">var</span> $originals <span class="sy0">=</span> tr.<span class="me1">children</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">;</span>
      <span class="kw2">var</span> $helper <span class="sy0">=</span> tr.<span class="me1">clone</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">;</span>
      $helper.<span class="me1">children</span><span class="br0">&#40;</span><span class="br0">&#41;</span>.<span class="me1">each</span><span class="br0">&#40;</span><span class="kw2">function</span><span class="br0">&#40;</span>index<span class="br0">&#41;</span>
      <span class="br0">&#123;</span>
        <span class="co1">// Set helper cell sizes to match the original sizes</span>
        $<span class="br0">&#40;</span><span class="kw1">this</span><span class="br0">&#41;</span>.<span class="me1">width</span><span class="br0">&#40;</span>$originals.<span class="me1">eq</span><span class="br0">&#40;</span>index<span class="br0">&#41;</span>.<span class="me1">width</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="br0">&#41;</span>
      <span class="br0">&#125;</span><span class="br0">&#41;</span><span class="sy0">;</span>
      <span class="kw1">return</span> $helper<span class="sy0">;</span>
    <span class="br0">&#125;</span><span class="sy0">,</span>
&nbsp;
    <span class="co1">// La fonction appelée quand un élément change de position</span>
    <span class="co1">// C'est le code vraiment utile, en fait</span>
    update<span class="sy0">:</span> <span class="kw2">function</span><span class="br0">&#40;</span>event<span class="sy0">,</span> ui<span class="br0">&#41;</span><span class="br0">&#123;</span>
      <span class="co1">// Construit un tableau des ids des stories</span>
      serial <span class="sy0">=</span> $<span class="br0">&#40;</span><span class="kw1">this</span><span class="br0">&#41;</span>.<span class="me1">sortable</span><span class="br0">&#40;</span><span class="st0">'serialize'</span><span class="br0">&#41;</span><span class="sy0">;</span>
&nbsp;
      <span class="co1">// Appelle une action en ajax</span>
      $.<span class="me1">ajax</span><span class="br0">&#40;</span><span class="br0">&#123;</span>
        url<span class="sy0">:</span> updateorderurl<span class="sy0">,</span> <span class="co1">// set in layout.php</span>
        type<span class="sy0">:</span> <span class="st0">&quot;post&quot;</span><span class="sy0">,</span>
        data<span class="sy0">:</span> serial<span class="sy0">,</span>
        error<span class="sy0">:</span> <span class="kw2">function</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="br0">&#123;</span>
          <span class="kw3">alert</span><span class="br0">&#40;</span><span class="st0">&quot;Error ! Order not updated&quot;</span><span class="br0">&#41;</span><span class="sy0">;</span>
        <span class="br0">&#125;</span>
      <span class="br0">&#125;</span><span class="br0">&#41;</span>
    <span class="br0">&#125;</span>
  <span class="br0">&#125;</span><span class="br0">&#41;</span><span class="sy0">;</span>
<span class="br0">&#125;</span><span class="br0">&#41;</span><span class="sy0">;</span></pre></div></div>

<p>Remarquez que la variable &laquo;&nbsp;&nbsp;updateorderurl&nbsp;&nbsp;&raquo; contient l&#8217;url de l&#8217;action qui va r&eacute;aliser la r&eacute;affectation des priorit&eacute;s. Comme cette url est g&eacute;n&eacute;r&eacute;e par Symfony, elle est d&eacute;finie dans le contr&ocirc;leur, puis affect&eacute;e &agrave; une variable javascript  dans la layout gr&acirc;ce &agrave; un slot. &Ccedil;a vaut ce que &ccedil;a vaut.</p>
<p>Normalement, vous devriez maintenant &ecirc;tre capable de changer l&#8217;ordre des stories c&ocirc;t&eacute; frontend. Bien entendu, le code m&eacute;tier charg&eacute; de g&eacute;rer le r&eacute;ordonnancement n&#8217;existe pas encore.</p>
<h2>Au c&oelig;ur du m&eacute;tier</h2>
<p>Cr&eacute;ons donc une nouvelle action dans le module &laquo;&nbsp;&nbsp;story&nbsp;&nbsp;&raquo;.</p>

<div class="wp_syntax"><div class="code"><pre class="php" style="font-family:monospace;color: #ccc; font: 12px Consolas, Lucida Console, Monaco, monospace;">  <span class="kw2">public</span> <span class="kw2">function</span> executeUpdateOrder<span class="br0">&#40;</span>sfWebRequest <span class="re0">$request</span><span class="br0">&#41;</span>
  <span class="br0">&#123;</span>
    <span class="co1">// Il nous faut un moyen de récupérer le projet en question</span>
    <span class="re0">$project</span> <span class="sy0">=</span> Doctrine<span class="sy0">::</span><span class="me2">getTable</span><span class="br0">&#40;</span><span class="st_h">'project'</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span><span class="me1">find</span><span class="br0">&#40;</span><span class="re0">$request</span><span class="sy0">-&gt;</span><span class="me1">getParameter</span><span class="br0">&#40;</span><span class="st_h">'project_id'</span><span class="br0">&#41;</span><span class="br0">&#41;</span><span class="sy0">;</span>
    <span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">forward404Unless</span><span class="br0">&#40;</span><span class="re0">$project</span><span class="br0">&#41;</span><span class="sy0">;</span>
&nbsp;
    <span class="co1">// Correspond à la variable 'serial' dans le js, vous vous souvenez ?</span>
    <span class="co1">// C'est un simple tableau d'ids</span>
    <span class="re0">$order</span> <span class="sy0">=</span> <span class="re0">$request</span><span class="sy0">-&gt;</span><span class="me1">getParameter</span><span class="br0">&#40;</span><span class="st_h">'story'</span><span class="br0">&#41;</span><span class="sy0">;</span>
    <span class="re0">$project</span><span class="sy0">-&gt;</span><span class="me1">updateStoriesOrder</span><span class="br0">&#40;</span><span class="re0">$order</span><span class="br0">&#41;</span><span class="sy0">;</span>
&nbsp;
    <span class="kw1">return</span> sfView<span class="sy0">::</span><span class="me2">HEADER_ONLY</span><span class="sy0">;</span>
  <span class="br0">&#125;</span></pre></div></div>

<p>Nous revoil&agrave; repartis dans le mod&egrave;le. &Eacute;ditons notre classe Project.</p>

<div class="wp_syntax"><div class="code"><pre class="php" style="font-family:monospace;color: #ccc; font: 12px Consolas, Lucida Console, Monaco, monospace;"><span class="co1">// lib/model/doctrine/Project.class.php</span>
<span class="co1">// …</span>
  <span class="co4">/**
   * Update the stories order
   *
   * @param array $order An array with the stories ids, sorted by priority
   **/</span>
  <span class="kw2">public</span> <span class="kw2">function</span> updateStoriesOrder<span class="br0">&#40;</span><a href="http://www.php.net/array"><span class="kw3">array</span></a> <span class="re0">$order</span><span class="br0">&#41;</span>
  <span class="br0">&#123;</span>
    <span class="kw1">foreach</span><span class="br0">&#40;</span><span class="re0">$order</span> <span class="kw1">as</span> <span class="re0">$priority</span> <span class="sy0">=&gt;</span> <span class="re0">$storyId</span><span class="br0">&#41;</span>
    <span class="br0">&#123;</span>
      <span class="re0">$story</span> <span class="sy0">=</span> Doctrine<span class="sy0">::</span><span class="me2">getTable</span><span class="br0">&#40;</span><span class="st_h">'story'</span><span class="br0">&#41;</span>
        <span class="sy0">-&gt;</span><span class="me1">find</span><span class="br0">&#40;</span><span class="re0">$storyId</span><span class="br0">&#41;</span><span class="sy0">;</span>
&nbsp;
      <span class="kw1">if</span><span class="br0">&#40;</span><span class="sy0">!</span><span class="re0">$story</span> <span class="sy0">||</span> <span class="re0">$story</span><span class="sy0">-&gt;</span><span class="me1">getProjectId</span><span class="br0">&#40;</span><span class="br0">&#41;</span> <span class="sy0">!=</span> <span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">getId</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="br0">&#41;</span>
        throw <span class="kw2">new</span> Exception<span class="br0">&#40;</span><span class="st_h">'moo'</span><span class="br0">&#41;</span><span class="sy0">;</span>
&nbsp;
      <span class="re0">$story</span><span class="sy0">-&gt;</span><span class="me1">setPriority</span><span class="br0">&#40;</span><span class="re0">$priority</span><span class="br0">&#41;</span><span class="sy0">;</span>
      <span class="re0">$story</span><span class="sy0">-&gt;</span><span class="me1">save</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">;</span>
    <span class="br0">&#125;</span>
  <span class="br0">&#125;</span></pre></div></div>

<p>Tadaaaaaam<span class="fine">&nbsp;</span>! &Ccedil;a devrait fonctionner. Voil&agrave;, c&#8217;est tout. Tiens, au moment o&ugrave; je finis d&#8217;&eacute;crire ces lignes, je m&#8217;aper&ccedil;ois qu&#8217;un <a href="http://www.symfony-project.org/plugins/sfDoctrineJQueryUISortablePlugin">plugin cens&eacute; faire exactement la m&ecirc;me chose</a> vient de sortir. Frustration. Bon, tant pis, bon week-end quand m&ecirc;me.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.miximum.fr/tutos/435-creer-une-liste-triable-avec-symfony-et-jquery-ui/feed</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>Naissance d&#8217;un plugin Solr pour Symfony</title>
		<link>http://www.miximum.fr/actus/413-naissance-dun-plugin-solr-pour-symfony</link>
		<comments>http://www.miximum.fr/actus/413-naissance-dun-plugin-solr-pour-symfony#comments</comments>
		<pubDate>Wed, 30 Dec 2009 16:05:13 +0000</pubDate>
		<dc:creator>thibault</dc:creator>
				<category><![CDATA[Actus]]></category>
		<category><![CDATA[doctrine]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[solr]]></category>
		<category><![CDATA[symfony]]></category>

		<guid isPermaLink="false">http://www.miximum.fr/?p=413</guid>
		<description><![CDATA[photo credit&#160;: B Tal Salut la compagnie. Ce billet (vraissemblablement le dernier de l&#8217;ann&#233;e) est destin&#233; &#224; faire passer une annonce (et aussi &#224; ramener un peu de traffic, j&#8217;avoue). Si vous n&#8217;&#234;tes pas abonn&#233; &#224; mon twitter, vous ne savez peut-&#234;tre pas encore que je viens de l&#226;cher dans la nature un nouveau plugin [...]]]></description>
			<content:encoded><![CDATA[<div class="postimg alignleft"><a href="http://www.flickr.com/photos/68634595@N00/116220689/" title="Our Direction" target="_blank"><img src="http://farm1.static.flickr.com/36/116220689_438039ddb3_m.jpg" alt="Our Direction" border="0" /></a><br /><small><a href="http://creativecommons.org/licenses/by-nc/2.0/" title="Attribution-NonCommercial License" target="_blank"><img src="http://www.miximum.fr/wp-content/plugins/photo-dropper/images/cc.png" alt="Creative Commons License" border="0" width="16" height="16" align="absmiddle" /></a> <a href="http://www.photodropper.com/photos/" target="_blank">photo</a> credit&nbsp;: <a href="http://www.flickr.com/photos/68634595@N00/116220689/" title="B Tal" target="_blank">B Tal</a></small></div>
<p>Salut la compagnie. Ce billet (vraissemblablement le dernier de l&#8217;ann&eacute;e) est destin&eacute; &agrave; faire passer une annonce (et aussi &agrave; ramener un peu de traffic, j&#8217;avoue).</p>
<p>Si vous n&#8217;&ecirc;tes pas <a href="http://twitter.com/thibaultj">abonn&eacute; &agrave; mon twitter</a>, vous ne savez peut-&ecirc;tre pas encore que je viens de l&acirc;cher dans la nature un <a href="http://www.symfony-project.org/plugins/tjSolrDoctrineBehaviorPlugin">nouveau plugin pour Symfony, qui porte le doux nom de <strong>tjSolrDoctrineBehaviorPlugin</strong></a>.</p>
<p>Ce plugin, comme son nom l&#8217;indique, permet de coupler tr&eacute;s simplement le <a href="http://www.symfony-project.org">framework Symfony</a> et le <a href="http://www.symfony-project.org/plugins/tjSolrDoctrineBehaviorPlugin">moteur de recherche Solr</a>. Ainsi, vous pouvez b&eacute;n&eacute;ficier dans vos projets Symfony d&#8217;un moteur de recherche au top, m&ecirc;me si vous n&#8217;&ecirc;tes pas familier avec Solr.</p>
<p>Il s&#8217;agit d&#8217;une version beta, ce qui signifie que je suis preneur de toutes remarques ou commentaires sur le sujet. Je n&#8217;en dit pas plus, la <a href="http://www.symfony-project.org/plugins/tjSolrDoctrineBehaviorPlugin/0_9_0?tab=plugin_readme">documentation du plugin</a> est suffisamment d&eacute;taill&eacute;e comme &ccedil;a.</p>
<p>C&#8217;est tout. Je m&#8217;en vais maintenant prendre un repos bien m&eacute;rit&eacute;, et d&eacute;guster de d&eacute;licieux chaussons aux pommes maison (tout le monde s&#8217;en fout, mais j&#8217;aime bien qu&#8217;on sache que j&#8217;appr&eacute;cie mes vacances).</p>
<p>Bonnes f&ecirc;tes &agrave; tous.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.miximum.fr/actus/413-naissance-dun-plugin-solr-pour-symfony/feed</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
	</channel>
</rss>

