<?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>Blog Excilys &#187; symfony</title>
	<atom:link href="http://blog.excilys.com/category/symfony/feed/" rel="self" type="application/rss+xml" />
	<link>http://blog.excilys.com</link>
	<description>Langages, Architectures &#38; Méthodologies</description>
	<lastBuildDate>Mon, 09 Jan 2012 17:14:03 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
		<item>
		<title>Menez le web à la baguette avec symfony (3) : refonte des glasses</title>
		<link>http://blog.excilys.com/2010/02/10/menez-le-web-a-la-baguette-avec-symfony-3-refonte-des-glasses/</link>
		<comments>http://blog.excilys.com/2010/02/10/menez-le-web-a-la-baguette-avec-symfony-3-refonte-des-glasses/#comments</comments>
		<pubDate>Wed, 10 Feb 2010 11:00:40 +0000</pubDate>
		<dc:creator>Bastien JANSEN</dc:creator>
				<category><![CDATA[symfony]]></category>
		<category><![CDATA[formulaires]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[series-symfony-plum]]></category>
		<category><![CDATA[tutoriel]]></category>
		<category><![CDATA[validation]]></category>
		<category><![CDATA[vue]]></category>

		<guid isPermaLink="false">http://blog.excilys.com/?p=1049</guid>
		<description><![CDATA[L&#8217;habit fait le moine Bienvenue dans le troisième épisode de la série &#8220;symfony, sex and fun&#8221;. Si vous arrivez en cours de route, vous pouvez consulter les articles précédents. Pour l&#8217;instant nous disposons d&#8217;un module bookmark fonctionnel (ajout, suppression etc.) mais qui n&#8217;est pas très joli. Voyons comment symfony permet de gérer les vues. Note : [...]]]></description>
			<content:encoded><![CDATA[<h3>L&#8217;habit fait le moine</h3>
<p><img class="alignright size-full wp-image-1050" title="La refonte en action..." src="http://blog.excilys.com/wp-content/uploads/2010/01/ours-pwn3d-glace.jpg" alt="ours-pwn3d-glace" width="225" height="253" />Bienvenue dans le troisième épisode de la série &#8220;symfony, sex and fun&#8221;. Si vous arrivez en cours de route, vous pouvez consulter les <a href="http://blog.excilys.com/2010/01/07/menez-le-web-a-la-baguette-avec-symfony/">articles</a> <a href="http://blog.excilys.com/2010/01/15/menez-le-web-a-la-baguette-avec-symfony-2-un-squelette-pour-pas-chair/">précédents</a>. Pour l&#8217;instant nous disposons d&#8217;un module <em>bookmark</em> fonctionnel (ajout, suppression etc.) mais qui n&#8217;est pas très joli. Voyons comment symfony permet de gérer les vues.</p>
<p class="note"><em>Note : </em>à partir de maintenant, nous allons faire pas mal de modifications dans les fichiers. Je vous suggère de vous munir d&#8217;un bon éditeur PHP si vous souhaitez refaire la démarche chez vous. Pourquoi pas <a href="http://www.netbeans.org">Netbeans</a> et son très bon <a href="http://wiki.netbeans.org/NB68symfony">support de symfony</a>, par exemple ?</p>
<p>L&#8217;affichage des vues dans symfony suit le <a href="http://en.wikipedia.org/wiki/Decorator_pattern">design pattern decorator</a> : chaque application dispose d&#8217;un <a href="http://www.symfony-project.org/jobeet/1_4/Doctrine/en/04#chapter_04_the_layout">layout</a> global, dans lequel on va injecter le rendu du template de l&#8217;action appelée (il va venir le <strong>décorer</strong>). L&#8217;approche généralement utilisée dans d&#8217;autres projets suit le chemin inverse : on concatène un en-tête, puis le contenu, et enfin un pied de page, mais ni le <em>header</em> ni le<em> footer</em> ne représentent un document HTML valide.</p>
<p><span id="more-1049"></span></p>
<p>Jetons un œil au layout (le template global), situé dans <strong>apps/frontend/templates/layout.php</strong> :</p>
<pre>
<div class="codecolorer-container php default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br />2<br />3<br />4<br />5<br />6<br />7<br />8<br />9<br />10<br />11<br />12<br />13<br />14<br />15<br /></div></td><td><div class="php codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">&lt;!DOCTYPE html PUBLIC &quot;-//W3C//DTD XHTML 1.0 Transitional//EN&quot;<br />
&nbsp; &nbsp; &quot;http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd&quot;&gt;<br />
&lt;html xmlns=&quot;http://www.w3.org/1999/xhtml&quot; xml:lang=&quot;en&quot; lang=&quot;en&quot;&gt;<br />
&nbsp; &lt;head&gt;<br />
&nbsp; &nbsp; <span style="color: #000000; font-weight: bold;">&lt;?php</span> include_http_metas<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span> <span style="color: #000000; font-weight: bold;">?&gt;</span><br />
&nbsp; &nbsp; <span style="color: #000000; font-weight: bold;">&lt;?php</span> include_metas<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span> <span style="color: #000000; font-weight: bold;">?&gt;</span><br />
&nbsp; &nbsp; <span style="color: #000000; font-weight: bold;">&lt;?php</span> include_title<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span> <span style="color: #000000; font-weight: bold;">?&gt;</span><br />
&nbsp; &nbsp; &lt;link rel=&quot;shortcut icon&quot; href=&quot;/favicon.ico&quot; /&gt;<br />
&nbsp; &nbsp; <span style="color: #000000; font-weight: bold;">&lt;?php</span> include_stylesheets<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span> <span style="color: #000000; font-weight: bold;">?&gt;</span><br />
&nbsp; &nbsp; <span style="color: #000000; font-weight: bold;">&lt;?php</span> include_javascripts<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span> <span style="color: #000000; font-weight: bold;">?&gt;</span><br />
&nbsp; &lt;/head&gt;<br />
&nbsp; &lt;body&gt;<br />
&nbsp; &nbsp; <span style="color: #000000; font-weight: bold;">&lt;?php</span> <span style="color: #b1b100;">echo</span> <span style="color: #000088;">$sf_content</span> <span style="color: #000000; font-weight: bold;">?&gt;</span><br />
&nbsp; &lt;/body&gt;<br />
&lt;/html&gt;</div></td></tr></tbody></table></div>
</pre>
<p>Plusieurs choses sont à noter :</p>
<ul>
<li>par défaut, symfony fait du rendu HTML (logique, puisque généralement il est utilisé pour construire des applications web),</li>
<li>les balises d&#8217;en-tête (métadonnées, titre, feuilles de style, javascripts) sont spécifiées ailleurs, et incluses par des fonctions <em>include_*()</em> : les <em><strong>helper</strong>,</em></li>
<li>le contenu effectif de la page, issu du rendu du template des actions, est stocké dans une variable nommée <em>$sf_content</em>,<em> </em>définie par le framework.</li>
</ul>
<p>Les <em>helpers</em> sont une notion empruntée à Ruby on Rails. Ce sont de simples fonctions, prenant éventuellement des paramètres, qui vont générer un morceau de vue HTML. Par exemple, le helper <strong><a href="http://www.symfony-project.org/api/1_4/UrlHelper#method_link_to">link_to</a></strong> va prendre une URL, un couple module/action ou encore une &#8220;adresse de routage&#8221; (comme @homepage) en paramètre et générer la balise <strong>&lt;a/&gt;</strong> correspondante. Pour des questions d&#8217;organisation logique, les helpers sont groupés dans des sortes de paquets, en fonction de ce sur quoi ils agissent. On retrouve ainsi les &#8220;paquets&#8221; UrlHelper, DateHelper, I18NHelper, etc. (par curiosité, vous pouvez les explorer dans la <a href="http://www.symfony-project.org/api/1_4/helper">documentation de l&#8217;API symfony</a>).</p>
<div class="note"><em>Note :</em> avant de continuer, il faut que je vous parle de la manière dont les configurations sont gérées. Symfony dispose de plusieurs niveaux de configuration :</p>
<ul>
<li>framework (configuration par défaut),</li>
<li>projet, dans le répertoire config/,</li>
<li>application, dans le répertoire apps/&lt;app&gt;/config,</li>
<li>module, dans le répertoire apps/&lt;app&gt;/modules/&lt;module&gt;/config.</li>
</ul>
<p>Ces niveaux sont fusionnés du haut vers le bas, c&#8217;est-à-dire que la configuration du projet peut surcharger celle du framework, ainsi de suite.</p></div>
<p>En regardant l&#8217;arborescence du projet, on s&#8217;aperçoit que seul l&#8217;application <em>frontend</em> dispose d&#8217;un répertoire config (pas le module <em>bookmark</em>). Si on ouvre le fichier <strong>apps/frontend/config/view.yml</strong>, on retrouve la pièce manquante du puzzle qui constitue la vue rendue :</p>
<pre>
<div class="codecolorer-container yaml default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br />2<br />3<br />4<br />5<br />6<br />7<br />8<br />9<br />10<br />11<br />12<br />13<br />14<br />15<br />16<br />17<br />18<br /></div></td><td><div class="yaml codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap"><span style="color: blue;"># You can find more information about this file on the symfony website:</span><br />
<span style="color: blue;"># http://www.symfony-project.org/reference/1_4/en/13-View</span><br />
<span style="color: #007F45;"><br />
default</span>:<span style="color: #007F45;"><br />
&nbsp; http_metas</span>:<span style="color: green;"><br />
&nbsp; &nbsp; content-type</span><span style="font-weight: bold; color: brown;">: </span>text/html<br />
<span style="color: #007F45;"><br />
&nbsp; metas</span><span style="font-weight: bold; color: brown;">:<br />
</span> &nbsp; &nbsp;<span style="color: blue;">#title: &nbsp; &nbsp; &nbsp; &nbsp;symfony project</span><br />
&nbsp; &nbsp; <span style="color: blue;">#description: &nbsp;symfony project</span><br />
&nbsp; &nbsp; <span style="color: blue;">#keywords: &nbsp; &nbsp; symfony, project</span><br />
&nbsp; &nbsp; <span style="color: blue;">#language: &nbsp; &nbsp; en</span><br />
&nbsp; &nbsp; <span style="color: blue;">#robots: &nbsp; &nbsp; &nbsp; index, follow</span><br />
<span style="color: green;"><br />
&nbsp; stylesheets</span><span style="font-weight: bold; color: brown;">: </span> &nbsp; <span class="br0">&#91;</span>main.css<span class="br0">&#93;</span><span style="color: green;"><br />
&nbsp; javascripts</span><span style="font-weight: bold; color: brown;">: </span> &nbsp; <span class="br0">&#91;</span><span class="br0">&#93;</span><span style="color: green;"><br />
&nbsp; has_layout</span><span style="font-weight: bold; color: brown;">: </span> &nbsp; &nbsp;true<span style="color: green;"><br />
&nbsp; layout</span><span style="font-weight: bold; color: brown;">: </span> &nbsp; &nbsp; &nbsp; &nbsp;layout</div></td></tr></tbody></table></div>
</pre>
<p>C&#8217;est donc ici que sont définis les scripts et les feuilles de style CSS à utiliser. Notez les deux dernières lignes : elles précisent que la vue utilise un layout, nommé&#8230; <code class="codecolorer text default"><span class="text">layout</span></code>. Il est donc possible pour un module ou une action d&#8217;utiliser un autre layout, voire aucun layout si, par exemple, il/elle génère des PDF à la volée. Adaptons tout cela à nos besoins :</p>
<pre>
<div class="codecolorer-container yaml default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br />2<br />3<br />4<br />5<br />6<br />7<br />8<br />9<br />10<br />11<br />12<br />13<br />14<br /></div></td><td><div class="yaml codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap"><span style="color: blue;"># apps/frontend/config/view.yml</span><span style="color: #007F45;"><br />
default</span>:<span style="color: #007F45;"><br />
&nbsp; http_metas</span>:<span style="color: green;"><br />
&nbsp; &nbsp; content-type</span><span style="font-weight: bold; color: brown;">: </span>text/html<br />
<span style="color: #007F45;"><br />
&nbsp; metas</span>:<span style="color: green;"><br />
&nbsp; &nbsp; title</span><span style="font-weight: bold; color: brown;">: </span> &nbsp; &nbsp; &nbsp; Plum Bookmark Manager<span style="color: green;"><br />
&nbsp; &nbsp; description</span><span style="font-weight: bold; color: brown;">: </span> Plum Bookmark Manager is a web application intended to store and manage your bookmarks<span style="color: green;"><br />
&nbsp; &nbsp; keywords</span><span style="font-weight: bold; color: brown;">: </span> &nbsp; &nbsp;plum, bookmark, manager, php, symfony<br />
<span style="color: green;"><br />
&nbsp; stylesheets</span><span style="font-weight: bold; color: brown;">: </span> &nbsp; <span class="br0">&#91;</span>main.css<span class="br0">&#93;</span><span style="color: green;"><br />
&nbsp; javascripts</span><span style="font-weight: bold; color: brown;">: </span> &nbsp; <span class="br0">&#91;</span><span class="br0">&#93;</span><span style="color: green;"><br />
&nbsp; has_layout</span><span style="font-weight: bold; color: brown;">: </span> &nbsp; &nbsp;true<span style="color: green;"><br />
&nbsp; layout</span><span style="font-weight: bold; color: brown;">: </span> &nbsp; &nbsp; &nbsp; &nbsp;layout</div></td></tr></tbody></table></div>
</pre>
<p>De plus, en modifiant le layout et la CSS <strong>main.css</strong>, on peut facilement obtenir un résultat visuel plus agréable : <img class="aligncenter size-full wp-image-1052" title="un-design-qu-il-est-beau" src="http://blog.excilys.com/wp-content/uploads/2010/01/un-design-qu-il-est-beau.png" alt="un-design-qu-il-est-beau" width="631" height="344" />Mais en général, ce n&#8217;est pas de cette façon que les bookmarks sont présentés. Il faudrait que le lien soit cliquable, que la date d&#8217;ajout soit mieux formatée, et que l&#8217;ID disparaisse (il ne nous intéresse pas, de toute manière). Allons faire un tour dans <strong>apps/frontend/modules/bookmark/templates/indexSuccess.php</strong>, et modifions-le comme ceci :</p>
<pre>
<div class="codecolorer-container php default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br />2<br />3<br />4<br />5<br />6<br />7<br />8<br />9<br />10<br />11<br />12<br />13<br />14<br />15<br />16<br /></div></td><td><div class="php codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap"><span style="color: #000000; font-weight: bold;">&lt;?php</span> use_helper<span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'Date'</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span> <span style="color: #000000; font-weight: bold;">?&gt;</span><br />
&lt;h1&gt;Bookmarks List&lt;/h1&gt;<br />
<br />
&lt;table id=&quot;bookmarksList&quot;&gt;<br />
&nbsp; &lt;tbody&gt;<br />
&nbsp; <span style="color: #000000; font-weight: bold;">&lt;?php</span> <span style="color: #b1b100;">foreach</span> <span style="color: #009900;">&#40;</span><span style="color: #000088;">$bookmarks</span> <span style="color: #b1b100;">as</span> <span style="color: #000088;">$bookmark</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">:</span> <span style="color: #000000; font-weight: bold;">?&gt;</span><br />
&nbsp; &nbsp; &lt;tr&gt;<br />
&nbsp; &nbsp; &nbsp; &lt;td&gt;<span style="color: #000000; font-weight: bold;">&lt;?php</span> <span style="color: #b1b100;">echo</span> format_date<span style="color: #009900;">&#40;</span><span style="color: #000088;">$bookmark</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">getCreatedAt</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">,</span> <span style="color: #0000ff;">'p'</span><span style="color: #009900;">&#41;</span> <span style="color: #000000; font-weight: bold;">?&gt;</span>&lt;/td&gt;<br />
&nbsp; &nbsp; &nbsp; &lt;td&gt;<span style="color: #000000; font-weight: bold;">&lt;?php</span> <span style="color: #b1b100;">echo</span> link_to<span style="color: #009900;">&#40;</span><span style="color: #000088;">$bookmark</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">getDescription</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">,</span> <span style="color: #000088;">$bookmark</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">getUrl</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">,</span> <span style="color: #990000;">array</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'popup'</span> <span style="color: #339933;">=&gt;</span> <span style="color: #0000ff;">'true'</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span> <span style="color: #000000; font-weight: bold;">?&gt;</span>&lt;/td&gt;<br />
&nbsp; &nbsp; &lt;/tr&gt;<br />
&nbsp; <span style="color: #000000; font-weight: bold;">&lt;?php</span> <span style="color: #b1b100;">endforeach</span><span style="color: #339933;">;</span> <span style="color: #000000; font-weight: bold;">?&gt;</span><br />
&nbsp; &lt;/tbody&gt;<br />
&lt;/table&gt;<br />
<br />
&lt;script type=&quot;text/javascript&quot;&gt;stripe(&quot;bookmarksList&quot;);&lt;/script&gt;<br />
&lt;a href=&quot;<span style="color: #000000; font-weight: bold;">&lt;?php</span> <span style="color: #b1b100;">echo</span> url_for<span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'bookmark/new'</span><span style="color: #009900;">&#41;</span> <span style="color: #000000; font-weight: bold;">?&gt;</span>&quot;&gt;New&lt;/a&gt;</div></td></tr></tbody></table></div>
</pre>
<p>La table a été épurée, et des helpers sont maintenant utilisés : <code class="codecolorer text default"><span class="text">format_date</span></code> et <code class="codecolorer text default"><span class="text">link_to</span></code><strong>.</strong> Ils permettent respectivement d&#8217;obtenir une date plus lisible et un lien HTML avec une description. Comme format_date n&#8217;est pas accessible par défaut dans les templates, il a fallu inclure <code class="codecolorer text default"><span class="text">DateHelper</span></code> grâce à la directive <code class="codecolorer php default"><span class="php">use_helper<span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'Date'</span><span style="color: #009900;">&#41;</span></span></code> (le suffixe &#8220;Helper&#8221; est ajouté automatiquement).</p>
<h3>Taguez-les tous !</h3>
<p>La deuxième partie à revoir après le design, c&#8217;est la gestion des tags. En effet, il nous reste deux classes qui ne sont pas encore utilisées : <em>Tag</em> et <em>BookmarkTag</em>. La seconde étant une table de liaison, on ne va pas la manipuler directement (i.e. nous n&#8217;allons pas en créer un module). Par contre, il peut être intéressant d&#8217;avoir un module <em>tag</em> :</p>
<pre>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br /></div></td><td><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">$ ./symfony doctrine:generate-module frontend tag Tag</div></td></tr></tbody></table></div>
</pre>
<p>Plutôt que d&#8217;utiliser les formulaires générés pour insérer des données de tests, nous allons utiliser des <strong>fixtures</strong>, un jeu de données spécifié dans un fichier, qui pourra être utilisé à n&#8217;importe quel moment pour réinitialiser la base de données :</p>
<pre>
<div class="codecolorer-container yaml default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;height:300px;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br />2<br />3<br />4<br />5<br />6<br />7<br />8<br />9<br />10<br />11<br />12<br />13<br />14<br />15<br />16<br />17<br />18<br />19<br />20<br />21<br />22<br />23<br />24<br />25<br /></div></td><td><div class="yaml codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap"><span style="color: blue;"># data/fixtures/fixtures.yml</span><span style="color: #007F45;"><br />
Bookmark</span>:<span style="color: green;"><br />
&nbsp; excilys</span><span style="font-weight: bold; color: brown;">: </span><span style="color: blue;"># cette clé sert de &quot;clé primaire&quot; pour BookmarkTag</span><span style="color: green;"><br />
&nbsp; &nbsp; url</span><span style="font-weight: bold; color: brown;">: </span>http://www.excilys.com<span style="color: green;"><br />
&nbsp; &nbsp; description</span><span style="font-weight: bold; color: brown;">: </span>Site du groupe Excilys<span style="color: #007F45;"><br />
&nbsp; google</span>:<span style="color: green;"><br />
&nbsp; &nbsp; url</span><span style="font-weight: bold; color: brown;">: </span>http://www.google.fr<span style="color: green;"><br />
&nbsp; &nbsp; description</span><span style="font-weight: bold; color: brown;">: </span>Un moteur de recherche peu connu<br />
<span style="color: #007F45;"><br />
Tag</span>:<span style="color: #007F45;"><br />
&nbsp; informatique</span>:<span style="color: green;"><br />
&nbsp; &nbsp; name</span><span style="font-weight: bold; color: brown;">: </span>Informatique<span style="color: #007F45;"><br />
&nbsp; dev</span>:<span style="color: green;"><br />
&nbsp; &nbsp; name</span><span style="font-weight: bold; color: brown;">: </span>Dev<br />
<span style="color: #007F45;"><br />
BookmarkTag</span>:<span style="color: #007F45;"><br />
&nbsp; bt1</span>:<span style="color: green;"><br />
&nbsp; &nbsp; Bookmark</span><span style="font-weight: bold; color: brown;">: </span>excilys<span style="color: green;"><br />
&nbsp; &nbsp; Tag</span><span style="font-weight: bold; color: brown;">: </span>informatique<span style="color: #007F45;"><br />
&nbsp; bt2</span>:<span style="color: green;"><br />
&nbsp; &nbsp; Bookmark</span><span style="font-weight: bold; color: brown;">: </span>excilys<span style="color: green;"><br />
&nbsp; &nbsp; Tag</span><span style="font-weight: bold; color: brown;">: </span>dev<span style="color: #007F45;"><br />
&nbsp; bt3</span>:<span style="color: green;"><br />
&nbsp; &nbsp; Bookmark</span><span style="font-weight: bold; color: brown;">: </span>google<span style="color: green;"><br />
&nbsp; &nbsp; Tag</span><span style="font-weight: bold; color: brown;">: </span>informatique</div></td></tr></tbody></table></div>
</pre>
<p>Ces données peuvent être insérées en base avec la commande suivante (qui efface les éventuelles données déjà présentes) :</p>
<pre>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br /></div></td><td><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">$ symfony doctrine:data-load</div></td></tr></tbody></table></div>
</pre>
<h4>Avec un nuage de tags, SVP&#8230;</h4>
<p>Nous pouvons maintenant ajouter une action au module <em>tag</em>, par exemple pour générer un <strong>nuage de tags</strong>. Pour cela, nous allons créer une méthode qui va chercher les tags utilisés en base, ainsi que le nombre de fois qu&#8217;ils sont utilisés. Cela se passe dans la classe <code class="codecolorer text default"><span class="text">BookmarkTagTable</span></code><strong> </strong>(un DAO étendant <code class="codecolorer text default"><span class="text">Doctrine_Table</span></code>) :</p>
<pre>
<div class="codecolorer-container php default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br />2<br />3<br />4<br />5<br />6<br />7<br />8<br />9<br />10<br />11<br />12<br />13<br />14<br />15<br />16<br />17<br />18<br /></div></td><td><div class="php codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap"><span style="color: #666666; font-style: italic;">// lib/model/doctrine/BookmarkTagTable.class.php</span><br />
<span style="color: #000000; font-weight: bold;">class</span> BookmarkTagTable <span style="color: #000000; font-weight: bold;">extends</span> Doctrine_Table<br />
<span style="color: #009900;">&#123;</span><br />
&nbsp; <span style="color: #009933; font-style: italic;">/**<br />
&nbsp; * Retrieves all the tags with the number of times they are attached to<br />
&nbsp; * a bookmark (weight).<br />
&nbsp; */</span><br />
&nbsp; <span style="color: #000000; font-weight: bold;">public</span> <span style="color: #000000; font-weight: bold;">function</span> retrieveTagsWithWeight<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><br />
&nbsp; <span style="color: #009900;">&#123;</span><br />
&nbsp; &nbsp; <span style="color: #000088;">$q</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$this</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">createQuery</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'bt'</span><span style="color: #009900;">&#41;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #339933;">-&gt;</span><span style="color: #004000;">leftJoin</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'bt.Tag t'</span><span style="color: #009900;">&#41;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #339933;">-&gt;</span><span style="color: #004000;">select</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'bt.tag_id, t.name, count(bt.tag_id)'</span><span style="color: #009900;">&#41;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #339933;">-&gt;</span><span style="color: #004000;">groupBy</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'bt.tag_id'</span><span style="color: #009900;">&#41;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #339933;">-&gt;</span><span style="color: #004000;">orderBy</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'t.name ASC'</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span><br />
<br />
&nbsp; &nbsp; <span style="color: #b1b100;">return</span> <span style="color: #000088;">$q</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">fetchArray</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span><br />
&nbsp; <span style="color: #009900;">&#125;</span><br />
<span style="color: #009900;">&#125;</span></div></td></tr></tbody></table></div>
</pre>
<p>Les méthodes appelées sont suffisamment explicites pour que je ne les détaille pas. L&#8217;étape suivante consiste à ajouter l&#8217;action qui va appeler ce DAO, puis la vue qui affichera le nuage :</p>
<pre>
<div class="codecolorer-container php default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br />2<br />3<br />4<br />5<br />6<br />7<br /></div></td><td><div class="php codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap"><span style="color: #666666; font-style: italic;">// apps/frontend/modules/tag/actions/actions.class.php</span><br />
<span style="color: #000000; font-weight: bold;">public</span> <span style="color: #000000; font-weight: bold;">function</span> executeTagCloud<span style="color: #009900;">&#40;</span>sfWebRequest <span style="color: #000088;">$request</span><span style="color: #009900;">&#41;</span><br />
<span style="color: #009900;">&#123;</span><br />
&nbsp; <span style="color: #000088;">$this</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">tags</span> <span style="color: #339933;">=</span> Doctrine<span style="color: #339933;">::</span><span style="color: #004000;">getTable</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'BookmarkTag'</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">retrieveTagsWithWeight</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span><br />
&nbsp; <span style="color: #000088;">$this</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">baseFontSize</span> <span style="color: #339933;">=</span> <span style="color: #990000;">doubleval</span><span style="color: #009900;">&#40;</span>sfConfig<span style="color: #339933;">::</span><span style="color: #004000;">get</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'app_fontSizes_base'</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span><br />
&nbsp; <span style="color: #000088;">$this</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">maxFontSize</span> <span style="color: #339933;">=</span> <span style="color: #990000;">doubleval</span><span style="color: #009900;">&#40;</span>sfConfig<span style="color: #339933;">::</span><span style="color: #004000;">get</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'app_fontSizes_max'</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span><br />
<span style="color: #009900;">&#125;</span></div></td></tr></tbody></table></div>
</pre>
<p>On définit deux tailles de police : minimale et maximale. Plutôt que de coder en dur ces valeurs, on les externalise dans le fichier de configuration de l&#8217;application, que l&#8217;on peut lire grâce à <code class="codecolorer php default"><span class="php">sfConfig<span style="color: #339933;">::</span><span style="color: #004000;">get</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'app_key_name'</span><span style="color: #009900;">&#41;</span></span></code> :</p>
<pre>
<div class="codecolorer-container yaml default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br />2<br />3<br />4<br />5<br /></div></td><td><div class="yaml codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap"><span style="color: blue;"># apps/frontend/config/app.yml</span><span style="color: #007F45;"><br />
all</span>:<span style="color: #007F45;"><br />
&nbsp; fontSizes</span>:<span style="color: green;"><br />
&nbsp; &nbsp; base</span><span style="font-weight: bold; color: brown;">: </span><span style="">0.8</span><span style="color: green;"><br />
&nbsp; &nbsp; max</span><span style="font-weight: bold; color: brown;">: </span><span style="">2.0</span></div></td></tr></tbody></table></div>
</pre>
<p>Notez que l&#8217;imbrication yaml se traduit en une <strong>concaténation avec des underscores</strong> au niveau du nom de la propriété (ex. <strong>app_fontSizes_base</strong>).</p>
<p>Voici la vue :</p>
<pre>
<div class="codecolorer-container php default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br />2<br />3<br />4<br />5<br />6<br />7<br />8<br />9<br /></div></td><td><div class="php codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap"><span style="color: #000000; font-weight: bold;">&lt;?php</span><br />
<span style="color: #b1b100;">foreach</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$tags</span> <span style="color: #b1b100;">as</span> <span style="color: #000088;">$tag</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span><br />
&nbsp; <span style="color: #000088;">$count</span> <span style="color: #339933;">=</span> <span style="color: #990000;">intval</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$tag</span><span style="color: #009900;">&#91;</span><span style="color: #0000ff;">'count'</span><span style="color: #009900;">&#93;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span><br />
&nbsp; <span style="color: #000088;">$fontSize</span> <span style="color: #339933;">=</span> <span style="color: #990000;">min</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$baseFontSize</span> <span style="color: #339933;">+</span> <span style="color: #009900;">&#40;</span><span style="color: #000088;">$count</span> <span style="color: #339933;">/</span> <span style="color: #cc66cc;">10</span><span style="color: #009900;">&#41;</span> <span style="color: #339933;">-</span> <span style="color:#800080;">0.1</span><span style="color: #339933;">,</span> <span style="color: #000088;">$maxFontSize</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span><br />
<br />
&nbsp; <span style="color: #b1b100;">echo</span> link_to<span style="color: #009900;">&#40;</span><span style="color: #000088;">$tag</span><span style="color: #009900;">&#91;</span><span style="color: #0000ff;">'Tag'</span><span style="color: #009900;">&#93;</span><span style="color: #009900;">&#91;</span><span style="color: #0000ff;">'name'</span><span style="color: #009900;">&#93;</span><span style="color: #339933;">,</span> <span style="color: #0000ff;">'bookmark/byTag?name='</span><span style="color: #339933;">.</span><span style="color: #000088;">$tag</span><span style="color: #009900;">&#91;</span><span style="color: #0000ff;">'Tag'</span><span style="color: #009900;">&#93;</span><span style="color: #009900;">&#91;</span><span style="color: #0000ff;">'name'</span><span style="color: #009900;">&#93;</span><span style="color: #339933;">,</span><br />
&nbsp; &nbsp; &nbsp; <span style="color: #990000;">array</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'style'</span> <span style="color: #339933;">=&gt;</span> <span style="color: #0000ff;">&quot;font-size: <span style="color: #006699; font-weight: bold;">${fontSize}</span>em;&quot;</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span><br />
&nbsp; <span style="color: #b1b100;">echo</span> <span style="color: #0000ff;">' '</span><span style="color: #339933;">;</span><br />
<span style="color: #009900;">&#125;</span></div></td></tr></tbody></table></div>
</pre>
<p>Le code fait un calcul savant maladroit de taille de police en fonction du poids (nombre d&#8217;associations entre ce tag et un bookmark), avec des bornes mini et maxi. Un autre algorithme possible calculerait la taille d&#8217;un tag relativement à sa fréquence d&#8217;utilisation par rapport aux autres (par exemple, si tous les tags sont utilisés 100 fois, ils auront la même <em>petite</em> taille, puisqu&#8217;aucun ne se démarque vraiment).</p>
<p>Il serait bien de pouvoir intégrer ce nuage de tags sur toutes les pages, idéalement il faudrait l&#8217;intégrer dans le layout global.</p>
<h4>Les components</h4>
<div id="attachment_1105" class="wp-caption alignright" style="width: 199px"><a href="http://blog.excilys.com/wp-content/uploads/2010/01/tag-cloud.png"><img class="size-full wp-image-1105" title="tag-cloud" src="http://blog.excilys.com/wp-content/uploads/2010/01/tag-cloud.png" alt="tag-cloud" width="189" height="76" /></a><p class="wp-caption-text">Notre nuage de tags</p></div>
<p>Comment rendre un template dans un autre ? Pour faire ce genre de tâches, symfony propose la notion de <strong>component</strong> : dans le layout global, on va ajouter une directive d&#8217;inclusion de composant, qui fait référence à un couple module/composant. Les composants sont différents des actions. Ils résident dans un fichier <strong>actions/components.class.php</strong>, et leur templates sont sous la forme <strong>_&lt;nomComposant&gt;.php</strong>.</p>
<p>Pour migrer notre action en composant, il faut donc créer le fichier <strong>apps/frontend/modules/tag/actions/components.class.php, </strong>y déplacer la méthode <code class="codecolorer text default"><span class="text">executeTagCloud()</span></code>, et renommer le template tagCloudSuccess.php en <strong>_tagCloud.php</strong>.</p>
<p>Le résultat du rendu de notre composant sera intégré à l&#8217;endroit de son inclusion :</p>
<pre>
<div class="codecolorer-container html4strict default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br />2<br />3<br />4<br />5<br />6<br />7<br /></div></td><td><div class="html4strict codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap"><span style="color: #009900;">&lt;<span style="color: #000000; font-weight: bold;">body</span>&gt;</span><br />
...<br />
&nbsp; <span style="color: #009900;">&lt;<span style="color: #000000; font-weight: bold;">div</span> <span style="color: #000066;">id</span><span style="color: #66cc66;">=</span><span style="color: #ff0000;">&quot;right-column&quot;</span>&gt;</span><br />
&nbsp; &nbsp; <span style="color: #009900;">&lt;?php include_component<span style="color: #66cc66;">&#40;</span><span style="color: #ff0000;">'tag'</span>, <span style="color: #ff0000;">'tagCloud'</span><span style="color: #66cc66;">&#41;</span>; ?&gt;</span><br />
&nbsp; <span style="color: #009900;">&lt;<span style="color: #66cc66;">/</span><span style="color: #000000; font-weight: bold;">div</span>&gt;</span><br />
...<br />
<span style="color: #009900;">&lt;<span style="color: #66cc66;">/</span><span style="color: #000000; font-weight: bold;">body</span>&gt;</span></div></td></tr></tbody></table></div>
</pre>
<p>En adaptant la CSS, on peut obtenir ce design :</p>
<p style="text-align: center;"><a href="http://blog.excilys.com/wp-content/uploads/2010/01/integrated-tag-cloud.png"><img class="aligncenter size-full wp-image-1106" title="integrated-tag-cloud" src="http://blog.excilys.com/wp-content/uploads/2010/01/integrated-tag-cloud.png" alt="integrated-tag-cloud" width="714" height="255" /></a></p>
<h3 style="text-align: left;">A fond les forms !</h3>
<p>Un <a href="http://www.symfony-project.org/jobeet/1_4/Doctrine/en/10">framework dédié aux formulaires</a> est présent dans symfony, il est assez complexe à appréhender lorsqu&#8217;on débute, je vais donc essayer d&#8217;être le plus clair possible et de ne pas m&#8217;égarer. Au niveau du vocabulaire, un formulaire est représenté par une classe *Form, contenant entre autre des <strong>widgets</strong> et des <strong>validators</strong>. Les premiers sont utilisés pour le rendu HTML (par exemple on a un widget <em>input text </em>et un widget <em>combobox</em>). Les validateurs, quant à eux, permettent de vérifier que les données entrées pour un widget donné sont &#8220;valides&#8221; ; il y a donc un validateur par widget.</p>
<p>Ouvrons la classe <code class="codecolorer text default"><span class="text">BookmarkForm</span></code>, générée par <strong>doctrine:build</strong>. Elle est vide, ce qui est normal puisque le comportement par défaut est défini dans sa classe parente gérée par le framework, <code class="codecolorer text default"><span class="text">BaseBookmarkForm</span></code>. La première chose que je vous propose de faire, c&#8217;est de désactiver le champ <code class="codecolorer text default"><span class="text">created_at</span></code>, puisqu&#8217;il est censé devoir être valorisé automatiquement :</p>
<pre>
<div class="codecolorer-container php default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br />2<br />3<br />4<br />5<br />6<br />7<br /></div></td><td><div class="php codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap"><span style="color: #000000; font-weight: bold;">class</span> BookmarkForm <span style="color: #000000; font-weight: bold;">extends</span> BaseBookmarkForm<br />
<span style="color: #009900;">&#123;</span><br />
&nbsp; <span style="color: #000000; font-weight: bold;">public</span> <span style="color: #000000; font-weight: bold;">function</span> configure<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><br />
&nbsp; <span style="color: #009900;">&#123;</span><br />
&nbsp; &nbsp; <span style="color: #990000;">unset</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$this</span><span style="color: #009900;">&#91;</span><span style="color: #0000ff;">'created_at'</span><span style="color: #009900;">&#93;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span><br />
&nbsp; <span style="color: #009900;">&#125;</span><br />
<span style="color: #009900;">&#125;</span></div></td></tr></tbody></table></div>
</pre>
<p>Il faudra également supprimer les références à <em>created_at</em> dans les templates de vues.</p>
<p>Toujours dans la méthode <code class="codecolorer text default"><span class="text">configure()</span></code>, il serait intéressant d&#8217;utiliser un validateur d&#8217;URL pour notre champ <code class="codecolorer text default"><span class="text">url</span></code> :</p>
<pre>
<div class="codecolorer-container php default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br />2<br />3<br />4<br /></div></td><td><div class="php codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap"><span style="color: #000088;">$this</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">validatorSchema</span><span style="color: #009900;">&#91;</span><span style="color: #0000ff;">'url'</span><span style="color: #009900;">&#93;</span> <span style="color: #339933;">=</span> <span style="color: #000000; font-weight: bold;">new</span> sfValidatorAnd<span style="color: #009900;">&#40;</span><span style="color: #990000;">array</span><span style="color: #009900;">&#40;</span><br />
&nbsp; <span style="color: #000088;">$this</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">validatorSchema</span><span style="color: #009900;">&#91;</span><span style="color: #0000ff;">'url'</span><span style="color: #009900;">&#93;</span><span style="color: #339933;">,</span><br />
&nbsp; <span style="color: #000000; font-weight: bold;">new</span> sfValidatorUrl<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><br />
<span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span></div></td></tr></tbody></table></div>
</pre>
<p>L&#8217;astuce ici consiste à utiliser un &#8220;super-validateur&#8221; qui va faire un &#8220;et logique&#8221; entre le <strong>validateur par défaut</strong> (pour la taille maximale de 255 caractères) et un <strong>sfValidatorUrl</strong>.</p>
<p>Si l&#8217;on essaie d&#8217;ajouter un nouveau bookmark, la vue est maintenant minimaliste. Si l&#8217;on tente de rentrer une URL invalide, des messages d&#8217;erreur nous préviennent :</p>
<div id="attachment_1438" class="wp-caption aligncenter" style="width: 268px"><a href="http://blog.excilys.com/wp-content/uploads/2010/02/invalid.png"><img class="size-full wp-image-1438" title="invalid" src="http://blog.excilys.com/wp-content/uploads/2010/02/invalid.png" alt="Message en cas d'URL invalide" width="258" height="131" /></a><p class="wp-caption-text">Message en cas d&#39;URL invalide</p></div>
<p>Regardons de plus près le fonctionnement du processus de soumission. Une fois les données entrées, l&#8217;action <code class="codecolorer text default"><span class="text">create</span></code> va exécuter les étapes suivantes (dans la méthode <code class="codecolorer text default"><span class="text">processForm</span></code>) :</p>
<ul>
<li><em>binder</em> les données POST sur l&#8217;objet *Form adéquat,</li>
<li>valider les données en appelant la méthode <code class="codecolorer text default"><span class="text">validate()</span></code>,</li>
<li>réafficher la vue en cas d&#8217;erreur,</li>
<li>sinon persister les objets en cas de succès, puis rediriger vers la liste de bookmarks.</li>
</ul>
<h4>Passez le bonjour agile</h4>
<p>Après réflexion, le modèle de données n&#8217;est pas forcément le meilleur. Pour associer les tags à un bookmark, on utilise généralement un seul champ de saisie dans lequel on séparera les tags par des virgules. Après validation du formulaire, il faudra donc rechercher tous les éventuels enregistrements correspondant à ces tags en base, pour pouvoir ajouter les relations tag-bookmark.</p>
<p>Il serait peut-être plus judicieux et plus pratique de ne pas avoir une relation <strong>n-m</strong> (notre classe BookmarkTag), mais <strong>1-n</strong>. Certes, il y aura des doublons dans la base, mais on gagnera surement en simplicité.</p>
<p>Fort heureusement pour nous, nous avons vu que symfony permet facilement de modifier le modèle de données. Il suffit d&#8217;éditer le fichier <strong>config/doctrine/schema.yml</strong> et de reconstruire les classes du domaine, de formulaires, etc.</p>
<pre>
<div class="codecolorer-container yaml default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br />2<br />3<br />4<br />5<br />6<br />7<br />8<br />9<br />10<br />11<br />12<br />13<br />14<br />15<br />16<br /></div></td><td><div class="yaml codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap"><span style="color: blue;"># schema.yml</span><span style="color: #007F45;"><br />
Bookmark</span>:<span style="color: #007F45;"><br />
&nbsp; actAs</span>:<span style="color: #007F45;"><br />
&nbsp; &nbsp; Timestampable</span>:<span style="color: #007F45;"><br />
&nbsp; &nbsp; &nbsp; updated</span>:<span style="color: green;"><br />
&nbsp; &nbsp; &nbsp; &nbsp; disabled</span><span style="font-weight: bold; color: brown;">: </span>true<span style="color: #007F45;"><br />
&nbsp; columns</span>:<span style="color: green;"><br />
&nbsp; &nbsp; url</span><span style="font-weight: bold; color: brown;">: </span> <span class="br0">&#123;</span> type<span style="font-weight: bold; color: brown;">: </span>string<span class="br0">&#40;</span><span style="">255</span><span class="br0">&#41;</span>, notnull<span style="font-weight: bold; color: brown;">: </span>true <span class="br0">&#125;</span><span style="color: green;"><br />
&nbsp; &nbsp; description</span><span style="font-weight: bold; color: brown;">: </span><span class="br0">&#123;</span> type<span style="font-weight: bold; color: brown;">: </span>string<span class="br0">&#40;</span><span style="">255</span><span class="br0">&#41;</span> <span class="br0">&#125;</span><br />
<span style="color: #007F45;"><br />
Tag</span>:<span style="color: #007F45;"><br />
&nbsp; columns</span>:<span style="color: green;"><br />
&nbsp; &nbsp; bookmark_id</span><span style="font-weight: bold; color: brown;">: </span> <span class="br0">&#123;</span> type<span style="font-weight: bold; color: brown;">: </span>integer <span class="br0">&#125;</span><span style="color: green;"><br />
&nbsp; &nbsp; name</span><span style="font-weight: bold; color: brown;">: </span><span class="br0">&#123;</span> type<span style="font-weight: bold; color: brown;">: </span>string<span class="br0">&#40;</span><span style="">255</span><span class="br0">&#41;</span>, notnull<span style="font-weight: bold; color: brown;">: </span>true <span class="br0">&#125;</span><span style="color: #007F45;"><br />
&nbsp; relations</span>:<span style="color: green;"><br />
&nbsp; &nbsp; Bookmark</span><span style="font-weight: bold; color: brown;">: </span> <span class="br0">&#123;</span> onDelete<span style="font-weight: bold; color: brown;">: </span>CASCADE, local<span style="font-weight: bold; color: brown;">: </span>bookmark_id, foreign<span style="font-weight: bold; color: brown;">: </span>id <span class="br0">&#125;</span></div></td></tr></tbody></table></div>
</pre>
<p>Avant de reconstruire les classes, il faut au préalable supprimer les fichiers *BookmarkTag* dans lib/, sinon Doctrine continuera de créer la table BookmarkTag en BDD (pour une raison qui m&#8217;échappe). Il ne faut évidemment pas oublier de déplacer le code gérant le nuage de tags dans les classes/vues de gestion de Tag (au lieu de BookmarkTag précédemment).</p>
<pre>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br /></div></td><td><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">$ ./symfony doctrine:build --all</div></td></tr></tbody></table></div>
</pre>
<p>Pour obtenir le nouveau comportement décrit plus haut, il faut à présent modifier :</p>
<ul>
<li>la classe BookmarkForm pour y ajouter un widget correspondant à notre liste de tags,</li>
<li>la vue permettant l&#8217;insertion d&#8217;un bookmark pour y ajouter un champ de saisie de type texte.</li>
</ul>
<pre>
<div class="codecolorer-container php default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br />2<br />3<br />4<br />5<br />6<br />7<br />8<br /></div></td><td><div class="php codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap"><span style="color: #666666; font-style: italic;">// lib/form/doctrine/BookmarkForm.class.php</span><br />
<br />
<span style="color: #000000; font-weight: bold;">public</span> <span style="color: #000000; font-weight: bold;">function</span> configure<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><br />
<span style="color: #009900;">&#123;</span><br />
&nbsp; <span style="color: #666666; font-style: italic;">// ...</span><br />
&nbsp; <span style="color: #000088;">$this</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">widgetSchema</span><span style="color: #009900;">&#91;</span><span style="color: #0000ff;">'tags'</span><span style="color: #009900;">&#93;</span> <span style="color: #339933;">=</span> <span style="color: #000000; font-weight: bold;">new</span> sfWidgetFormInputText<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span><br />
&nbsp; <span style="color: #000088;">$this</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">validatorSchema</span><span style="color: #009900;">&#91;</span><span style="color: #0000ff;">'tags'</span><span style="color: #009900;">&#93;</span> <span style="color: #339933;">=</span> <span style="color: #000000; font-weight: bold;">new</span> sfValidatorString<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span><br />
<span style="color: #009900;">&#125;</span></div></td></tr></tbody></table></div>
<div class="codecolorer-container html4strict default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br />2<br />3<br />4<br />5<br />6<br />7<br /></div></td><td><div class="html4strict codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap"><span style="color: #808080; font-style: italic;">&lt;!-- apps/frontend/modules/bookmark/templates/_form.php --&gt;</span><br />
<span style="color: #009900;">&lt;<span style="color: #000000; font-weight: bold;">tr</span>&gt;</span><br />
&nbsp; <span style="color: #009900;">&lt;<span style="color: #000000; font-weight: bold;">th</span>&gt;</span>Tags :<span style="color: #009900;">&lt;<span style="color: #66cc66;">/</span><span style="color: #000000; font-weight: bold;">th</span>&gt;</span><br />
&nbsp; <span style="color: #009900;">&lt;<span style="color: #000000; font-weight: bold;">td</span>&gt;</span><br />
&nbsp; &nbsp; <span style="color: #009900;">&lt;?php echo $form<span style="color: #66cc66;">&#91;</span><span style="color: #ff0000;">'tags'</span><span style="color: #66cc66;">&#93;</span> ?&gt;</span><br />
&nbsp; <span style="color: #009900;">&lt;<span style="color: #66cc66;">/</span><span style="color: #000000; font-weight: bold;">td</span>&gt;</span><br />
<span style="color: #009900;">&lt;<span style="color: #66cc66;">/</span><span style="color: #000000; font-weight: bold;">tr</span>&gt;</span></div></td></tr></tbody></table></div>
</pre>
<p>L&#8217;astuce est ensuite de convertir la valeur récupérée dans le widget &#8216;tags&#8217; en autant de tags. Pour cela, il faut intervenir juste la sauvegarde du formulaire, dans <strong>actions.class.php</strong> :</p>
<pre>
<div class="codecolorer-container php default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br />2<br />3<br />4<br />5<br />6<br />7<br />8<br />9<br /></div></td><td><div class="php codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap"><span style="color: #000000; font-weight: bold;">protected</span> <span style="color: #000000; font-weight: bold;">function</span> processForm<span style="color: #009900;">&#40;</span>sfWebRequest <span style="color: #000088;">$request</span><span style="color: #339933;">,</span> sfForm <span style="color: #000088;">$form</span><span style="color: #009900;">&#41;</span><br />
<span style="color: #009900;">&#123;</span><br />
&nbsp; <span style="color: #666666; font-style: italic;">// ...</span><br />
&nbsp; <span style="color: #b1b100;">if</span> <span style="color: #009900;">&#40;</span><span style="color: #000088;">$form</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">isValid</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span><br />
&nbsp; <span style="color: #009900;">&#123;</span><br />
&nbsp; &nbsp; <span style="color: #000088;">$bookmark</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$form</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">save</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span><br />
<br />
&nbsp; &nbsp; Doctrine<span style="color: #339933;">::</span><span style="color: #004000;">getTable</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'Bookmark'</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">createTags</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$bookmark</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">getId</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">,</span> <span style="color: #000088;">$form</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">getValue</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'tags'</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span><br />
&nbsp; &nbsp; <span style="color: #666666; font-style: italic;">// ...</span></div></td></tr></tbody></table></div>
<div class="codecolorer-container php default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br />2<br />3<br />4<br />5<br />6<br />7<br />8<br />9<br />10<br />11<br />12<br />13<br />14<br /></div></td><td><div class="php codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap"><span style="color: #000000; font-weight: bold;">class</span> BookmarkTable <span style="color: #000000; font-weight: bold;">extends</span> Doctrine_Table<br />
<span style="color: #009900;">&#123;</span><br />
&nbsp; &nbsp; <span style="color: #000000; font-weight: bold;">public</span> static <span style="color: #000000; font-weight: bold;">function</span> createTags<span style="color: #009900;">&#40;</span><span style="color: #000088;">$bookmarkId</span><span style="color: #339933;">,</span> <span style="color: #000088;">$tagList</span><span style="color: #009900;">&#41;</span><br />
&nbsp; &nbsp; <span style="color: #009900;">&#123;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #000088;">$tags</span> <span style="color: #339933;">=</span> <span style="color: #990000;">explode</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">','</span><span style="color: #339933;">,</span> <span style="color: #000088;">$tagList</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span><br />
<br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #b1b100;">foreach</span> <span style="color: #009900;">&#40;</span><span style="color: #000088;">$tags</span> <span style="color: #b1b100;">as</span> <span style="color: #000088;">$tag</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #000088;">$t</span> <span style="color: #339933;">=</span> <span style="color: #000000; font-weight: bold;">new</span> Tag<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #000088;">$t</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">setBookmarkId</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$bookmarkId</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #000088;">$t</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">setName</span><span style="color: #009900;">&#40;</span><span style="color: #990000;">trim</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$tag</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #000088;">$t</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">save</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #009900;">&#125;</span><br />
&nbsp; &nbsp; <span style="color: #009900;">&#125;</span><br />
<span style="color: #009900;">&#125;</span></div></td></tr></tbody></table></div>
</pre>
<p>Vérifions si tout fonctionne bien :</p>
<p style="text-align: center;">
<div id="attachment_1437" class="wp-caption aligncenter" style="width: 770px"><a href="http://blog.excilys.com/wp-content/uploads/2010/02/ajout-auto-de-tags.png"><img class="size-full wp-image-1437 " title="ajout-auto-de-tags" src="http://blog.excilys.com/wp-content/uploads/2010/02/ajout-auto-de-tags.png" alt="Création d'un bookmark avec des tags associés" width="760" height="188" /></a><p class="wp-caption-text">Création d&#39;un bookmark avec des tags associés</p></div>
<p>Ceci n&#8217;est qu&#8217;une ébauche assez maladroite. Dans le monde merveilleux des Bisounours, il faudrait vérifier que l&#8217;on n&#8217;insère pas plusieurs fois le même tag sur le même bookmark, en particulier lorsqu&#8217;on utilise le formulaire d&#8217;édition (qui contient lui aussi le champ &#8216;tags&#8217;). Ceci peut faire un parfait exercice pour le lecteur assidu, d&#8217;autant plus que nous approchons dangereusement de la limite de taille (raisonnable) pour un article <img src='http://blog.excilys.com/wp-includes/images/smilies/icon_biggrin.gif' alt=':D' class='wp-smiley' /> . Si vous cherchez un autre exercice pour cogiter le soir dans le métro, vous pouvez également essayer d&#8217;afficher la liste des tags associés à un bookmark.</p>
<h3>Conclusion</h3>
<p>Nous voici déjà à la fin de cet article (ohhhhh&#8230; <img src='http://blog.excilys.com/wp-includes/images/smilies/icon_sad.gif' alt=':(' class='wp-smiley' /> ). Ce que nous pouvons en retenir, c&#8217;est qu&#8217;à chaque problème, symfony propose une solution. Les problèmes que l&#8217;on peut rencontrer sont souvent les mêmes d&#8217;un projet à l&#8217;autre, c&#8217;est pourquoi les concepteurs du framework le développent de manière à ce que l&#8217;on perde le moins de temps possible.</p>
<p>symfony est suffisamment flexible et agile pour permettre à tout moment de modifier le modèle de données et pouvoir profiter de ces modifications assez rapidement, notamment grâce à la génération automatique de code. J&#8217;ajouterais néanmoins qu&#8217;il est préférable, à mon sens, de bien réfléchir au modèle dès le départ pour éviter de perdre trop de temps par la suite.</p>
<p>Il est également intéressant de noter que l&#8217;on peut faire des thèmes assez jolis à base de rose, et que ça ne fait pas forcément <em>girly</em> (le lecteur visé se reconnaitra <img src='http://blog.excilys.com/wp-includes/images/smilies/icon_razz.gif' alt=':P' class='wp-smiley' /> ).</p>
<h3>Accès au code source :</h3>
<p><a href="http://code.google.com/p/excilys/source/browse/projects#projects/plum/tags/plum_article_3">http://code.google.com/p/excilys/source/browse/projects#projects/plum/tags/plum_article_3</a></p>
<div id="_mcePaste" style="overflow: hidden; position: absolute; left: -10000px; top: 2020px; width: 1px; height: 1px;">[cci]</div>
<p><!--[if IE]><iframe frameborder="0" allowTransparency="true" class="addtoany_special_service twitter_tweet" src="http://platform.twitter.com/widgets/tweet_button.html?url=http%3A%2F%2Fblog.excilys.com%2F2010%2F02%2F10%2Fmenez-le-web-a-la-baguette-avec-symfony-3-refonte-des-glasses%2F&amp;counturl=http%3A%2F%2Fblog.excilys.com%2F2010%2F02%2F10%2Fmenez-le-web-a-la-baguette-avec-symfony-3-refonte-des-glasses%2F&amp;count=horizontal&amp;text=Menez%20le%20web%20%C3%A0%20la%20baguette%20avec%20symfony%20%283%29%20%3A%20refonte%20des%20glasses" scrolling="no" style="border:none;overflow:hidden;width:130px;height:20px"></iframe><![endif]--><!--[if !IE]><!--><iframe class="addtoany_special_service twitter_tweet" src="http://platform.twitter.com/widgets/tweet_button.html?url=http%3A%2F%2Fblog.excilys.com%2F2010%2F02%2F10%2Fmenez-le-web-a-la-baguette-avec-symfony-3-refonte-des-glasses%2F&amp;counturl=http%3A%2F%2Fblog.excilys.com%2F2010%2F02%2F10%2Fmenez-le-web-a-la-baguette-avec-symfony-3-refonte-des-glasses%2F&amp;count=horizontal&amp;text=Menez%20le%20web%20%C3%A0%20la%20baguette%20avec%20symfony%20%283%29%20%3A%20refonte%20des%20glasses" scrolling="no" style="border:none;overflow:hidden;width:130px;height:20px"></iframe><!--<![endif]--><!--[if IE]><iframe frameborder="0" allowTransparency="true" class="addtoany_special_service google_plusone" src="https://plusone.google.com/u/0/_/%2B1/fastbutton?url=http%3A%2F%2Fblog.excilys.com%2F2010%2F02%2F10%2Fmenez-le-web-a-la-baguette-avec-symfony-3-refonte-des-glasses%2F&amp;size=medium&amp;count=true" scrolling="no" style="border:none;overflow:hidden;width:90px;height:20px"></iframe><![endif]--><!--[if !IE]><!--><iframe class="addtoany_special_service google_plusone" src="https://plusone.google.com/u/0/_/%2B1/fastbutton?url=http%3A%2F%2Fblog.excilys.com%2F2010%2F02%2F10%2Fmenez-le-web-a-la-baguette-avec-symfony-3-refonte-des-glasses%2F&amp;size=medium&amp;count=true" scrolling="no" style="border:none;overflow:hidden;width:90px;height:20px"></iframe><!--<![endif]--><a class="a2a_dd a2a_target addtoany_share_save" href="http://www.addtoany.com/share_save#url=http%3A%2F%2Fblog.excilys.com%2F2010%2F02%2F10%2Fmenez-le-web-a-la-baguette-avec-symfony-3-refonte-des-glasses%2F&amp;title=Menez%20le%20web%20%C3%A0%20la%20baguette%20avec%20symfony%20%283%29%20%3A%20refonte%20des%20glasses" id="wpa2a_2"><img src="http://blog.excilys.com/wp-content/plugins/add-to-any/share_save_120_16.png" width="120" height="16" alt="Share"/></a></p>]]></content:encoded>
			<wfw:commentRss>http://blog.excilys.com/2010/02/10/menez-le-web-a-la-baguette-avec-symfony-3-refonte-des-glasses/feed/</wfw:commentRss>
		<slash:comments>9</slash:comments>
		</item>
		<item>
		<title>Menez le web à la baguette avec symfony (2) : un squelette pour pas chair</title>
		<link>http://blog.excilys.com/2010/01/15/menez-le-web-a-la-baguette-avec-symfony-2-un-squelette-pour-pas-chair/</link>
		<comments>http://blog.excilys.com/2010/01/15/menez-le-web-a-la-baguette-avec-symfony-2-un-squelette-pour-pas-chair/#comments</comments>
		<pubDate>Fri, 15 Jan 2010 19:38:17 +0000</pubDate>
		<dc:creator>Bastien JANSEN</dc:creator>
				<category><![CDATA[symfony]]></category>
		<category><![CDATA[auto-génération]]></category>
		<category><![CDATA[doctrine]]></category>
		<category><![CDATA[domain model]]></category>
		<category><![CDATA[entreprise]]></category>
		<category><![CDATA[environnements]]></category>
		<category><![CDATA[mapping]]></category>
		<category><![CDATA[mvc]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[scaffold]]></category>
		<category><![CDATA[series-symfony-plum]]></category>
		<category><![CDATA[test]]></category>
		<category><![CDATA[tutoriel]]></category>
		<category><![CDATA[urlMapping]]></category>

		<guid isPermaLink="false">http://blog.excilys.com/?p=950</guid>
		<description><![CDATA[Résumé de l&#8217;épisode 1 Dans l&#8217;article précédent,  nous avons rapidement vu que des solutions intéressantes apparaissent au sein du monde PHP. Parmi l&#8217;un des nombreux frameworks émergeants, nous avons choisi d&#8217;étudier symfony à travers la création d&#8217;une application web de gestion de bookmarks, Plum. Nous avons pour l&#8217;instant créé une application par défaut. Il reste [...]]]></description>
			<content:encoded><![CDATA[<h3>Résumé de l&#8217;épisode 1</h3>
<p>Dans <a href="http://blog.excilys.com/2010/01/07/menez-le-web-a-la-baguette-avec-symfony/">l&#8217;article précédent</a>,  nous avons rapidement vu que des solutions intéressantes apparaissent au sein du monde <strong>PHP</strong>. Parmi l&#8217;un des nombreux frameworks émergeants, nous avons choisi d&#8217;étudier <strong>symfony</strong> à travers la création d&#8217;une application web de gestion de bookmarks, Plum. Nous avons pour l&#8217;instant créé une <strong>application par défaut</strong>. Il reste maintenant à lui faire faire son &#8220;vrai&#8221; travail&#8230;</p>
<p>Nous manipulons pour l&#8217;instant peu de types de données : les bookmarks et les tags. Voici un diagramme UML les représentant :</p>
<div id="attachment_954" class="wp-caption aligncenter" style="width: 351px"><img class="size-full wp-image-954" src="http://blog.excilys.com/wp-content/uploads/2010/01/7bda79a82.png" alt="7bda79a8" width="341" height="99" /><p class="wp-caption-text">Diagramme de classes de notre modèle</p></div>
<p>(Ce magnifique diagramme a été réalisé en ligne sur le site de <a href="http://yuml.me/">yuml.me</a>. Publicité gratuite <img src='http://blog.excilys.com/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> ).</p>
<p>Pour facilement manipuler les données en base, symfony gère de base deux <em>Object-Relational Mappers</em> (ORM) : Doctrine et Propel. Cependant, comme Doctrine est devenu l&#8217;ORM par défaut depuis la version 1.3 du framework, c&#8217;est lui que nous utiliserons. Doctrine propose une solution élégante d&#8217;accès aux données via le langage DQL, inspiré du HQL d&#8217;Hibernate, ainsi qu&#8217;une API d&#8217;accès aux données.   Propel se base de son côté sur une API semblable aux Criteria. Que l&#8217;on utilise Doctrine ou Propel, symfony fonctionne de manière semblable. Dans la suite de cet article, je vais détailler pas à pas les étapes suivantes :</p>
<ul>
<li>configuration de l&#8217;<strong>accès à la BDD</strong> (hôte, port etc.),</li>
<li>définition du <strong>modèle de données</strong> dans un fichier au format <a href="http://www.yaml.org/">YAML</a> : <strong>config/doctrine/schema.yml</strong>,</li>
<li>appel de la commande responsable de la génération des classes du modèle, du code SQL, de la création de la BDD et chargement des données de test en BDD,</li>
<li>éventuelle génération de <strong>modules</strong> dans une application pour faire du <strong>CRUD</strong> basique,</li>
<li>adaptation du code et utilisation de l&#8217;API Doctrine&#8230;<span id="more-950"></span></li>
</ul>
<h3>Définition du modèle de données</h3>
<p>La configuration de la base de données se fait au moyen d&#8217;une commande :</p>
<pre>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br /></div></td><td><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">./symfony configure:database --name=doctrine --class=sfDoctrineDatabase &quot;mysql:host=localhost;dbname=plum&quot; root password</div></td></tr></tbody></table></div>
</pre>
<p>Ce qui crée un fichier <a href="http://www.symfony-project.org/reference/1_4/en/07-Databases">config/databases.yml</a> utilisant le &#8220;driver&#8221; sfDoctrineDatabase sur une base MySQL locale nommé plum. Par la suite, il suffira de modifier directement ce fichier au lieu de rappeler cette commande :</p>
<pre>
<div class="codecolorer-container yaml default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br />2<br />3<br />4<br />5<br />6<br />7<br />8<br /></div></td><td><div class="yaml codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap"><span style="color: blue;"># config/databases.yml</span><span style="color: #007F45;"><br />
all</span>:<span style="color: #007F45;"><br />
&nbsp; doctrine</span>:<span style="color: green;"><br />
&nbsp; &nbsp; class</span><span style="font-weight: bold; color: brown;">: </span>sfDoctrineDatabase<span style="color: #007F45;"><br />
&nbsp; &nbsp; param</span>:<span style="color: green;"><br />
&nbsp; &nbsp; &nbsp; dsn</span><span style="font-weight: bold; color: brown;">: </span>'mysql:host=localhost;dbname=plum'<span style="color: green;"><br />
&nbsp; &nbsp; &nbsp; username</span><span style="font-weight: bold; color: brown;">: </span>root<span style="color: green;"><br />
&nbsp; &nbsp; &nbsp; password</span><span style="font-weight: bold; color: brown;">: </span>password</div></td></tr></tbody></table></div>
</pre>
<div class="note"><em>Note : </em>si vous commencez à vous perdre au milieu de toutes ces commandes, sachez qu&#8217;elles disposent d&#8217;une aide contextuelle. Vous pouvez y accéder en utilisant &#8220;symfony help&#8221; suivi de la commande qui vous cause du souci, par exemple :</p>
<pre>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br />2<br />3<br />4<br /></div></td><td><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">$ ./symfony help doctrine:build<br />
Usage:<br />
&nbsp;symfony doctrine:build [--application[=&quot;...&quot;]] [--env=&quot;...&quot;] [--no-confirmation] [--all] [--all-classes] [--model] [--forms] [--filters] [--sql] [--db] [--and-migrate] [--and-load[=&quot;...&quot;]] [--and-append[=&quot;...&quot;]]<br />
...</div></td></tr></tbody></table></div>
</pre>
</div>
<p>La seconde étape est la définition du modèle, elle se fait de la manière suivante :</p>
<pre>
<div class="codecolorer-container yaml default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;height:300px;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br />2<br />3<br />4<br />5<br />6<br />7<br />8<br />9<br />10<br />11<br />12<br />13<br />14<br />15<br />16<br />17<br />18<br />19<br />20<br />21<br /></div></td><td><div class="yaml codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap"><span style="color: blue;"># config/doctrine/schema.yml</span><span style="color: #007F45;"><br />
Bookmark</span>:<span style="color: #007F45;"><br />
&nbsp; actAs</span>:<span style="color: #007F45;"><br />
&nbsp; &nbsp; Timestampable</span>:<span style="color: #007F45;"><br />
&nbsp; &nbsp; &nbsp; updated</span>:<span style="color: green;"><br />
&nbsp; &nbsp; &nbsp; &nbsp; disabled</span><span style="font-weight: bold; color: brown;">: </span>true<span style="color: #007F45;"><br />
&nbsp; columns</span>:<span style="color: green;"><br />
&nbsp; &nbsp; url</span><span style="font-weight: bold; color: brown;">: </span> <span class="br0">&#123;</span> type<span style="font-weight: bold; color: brown;">: </span>string<span class="br0">&#40;</span><span style="">255</span><span class="br0">&#41;</span>, notnull<span style="font-weight: bold; color: brown;">: </span>true <span class="br0">&#125;</span><span style="color: green;"><br />
&nbsp; &nbsp; description</span><span style="font-weight: bold; color: brown;">: </span><span class="br0">&#123;</span> type<span style="font-weight: bold; color: brown;">: </span>string<span class="br0">&#40;</span><span style="">255</span><span class="br0">&#41;</span> <span class="br0">&#125;</span><br />
<span style="color: #007F45;"><br />
Tag</span>:<span style="color: #007F45;"><br />
&nbsp; columns</span>:<span style="color: green;"><br />
&nbsp; &nbsp; name</span><span style="font-weight: bold; color: brown;">: </span><span class="br0">&#123;</span> type<span style="font-weight: bold; color: brown;">: </span>string<span class="br0">&#40;</span><span style="">255</span><span class="br0">&#41;</span>, notnull<span style="font-weight: bold; color: brown;">: </span>true, unique<span style="font-weight: bold; color: brown;">: </span>true <span class="br0">&#125;</span><br />
<span style="color: #007F45;"><br />
BookmarkTag</span>:<span style="color: #007F45;"><br />
&nbsp; columns</span>:<span style="color: green;"><br />
&nbsp; &nbsp; bookmark_id</span><span style="font-weight: bold; color: brown;">: </span> <span class="br0">&#123;</span> type<span style="font-weight: bold; color: brown;">: </span>integer, primary<span style="font-weight: bold; color: brown;">: </span>true <span class="br0">&#125;</span><span style="color: green;"><br />
&nbsp; &nbsp; tag_id</span><span style="font-weight: bold; color: brown;">: </span><span class="br0">&#123;</span> type<span style="font-weight: bold; color: brown;">: </span>integer, primary<span style="font-weight: bold; color: brown;">: </span>true <span class="br0">&#125;</span><span style="color: #007F45;"><br />
&nbsp; relations</span>:<span style="color: green;"><br />
&nbsp; &nbsp; Bookmark</span><span style="font-weight: bold; color: brown;">: </span> <span class="br0">&#123;</span> onDelete<span style="font-weight: bold; color: brown;">: </span>CASCADE, local<span style="font-weight: bold; color: brown;">: </span>bookmark_id, foreign<span style="font-weight: bold; color: brown;">: </span>id <span class="br0">&#125;</span><span style="color: green;"><br />
&nbsp; &nbsp; Tag</span><span style="font-weight: bold; color: brown;">: </span><span class="br0">&#123;</span> onDelete<span style="font-weight: bold; color: brown;">: </span>CASCADE, local<span style="font-weight: bold; color: brown;">: </span>tag_id, foreign<span style="font-weight: bold; color: brown;">: </span>id <span class="br0">&#125;</span></div></td></tr></tbody></table></div>
</pre>
<p>On définit ici trois tables, ayant chacune une série de <strong>colonnes</strong> (champs). La clé <strong>actAs</strong> permet d&#8217;ajouter des comportements (<a href="http://www.doctrine-project.org/documentation/manual/1_2/en/behaviors"><em>behaviours</em></a>) spécifiques à Doctrine. Par exemple, ici on décide que la table Bookmark est <em>Timestampable</em>, ce qui aura pour but d&#8217;ajouter automatiquement deux champs <em>created_at</em> et <em>updated_at</em>, qui seront mis à jour par magie. Mais nous ne sommes pas intéressés par le champ updated_at, donc nous le supprimons.</p>
<p>La table <strong>BookmarkTag</strong> est la table de liaison de la relation n-n entre bookmarks et tags. On y retrouve donc les deux champs correspondant aux clés étrangères. La relation proprement dite est définie sous la clé <em>relations</em>, avec le nom des tables référencées, ainsi que les champs locaux et distants. Nous allons maintenant pouvoir passer à la génération de la base de donn&#8230; comment ? &#8220;D&#8217;où viennent les champs <strong><em>id</em></strong> référencés dans la relation de la table BookmarkTag ?&#8221; Eh bien, ils sont <strong>générés automagiquement</strong> par Doctrine ! Ainsi, un champ &#8220;id&#8221; en clé primaire autoincrémentée sera ajouté aux tables Bookmark et Tag (mais pas à BookmarkTag qui possède déjà une clé primaire composée des deux clés étrangères).</p>
<h3>Génération enchantée</h3>
<h4>Base et modèle de données</h4>
<p>C&#8217;est à partir d&#8217;ici que nous allons pouvoir mesurer toute la puissance du framework (et de ses composants). Même si jusqu&#8217;ici on en a déjà pris plein les yeux, ça ne fait que commencer <img src='http://blog.excilys.com/wp-includes/images/smilies/icon_biggrin.gif' alt=':D' class='wp-smiley' /> .  Tout va se produire dans cette ligne de commande aux allures mystiques :</p>
<pre>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;height:300px;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br />2<br />3<br />4<br />5<br />6<br />7<br />8<br />9<br />10<br />11<br />12<br />13<br />14<br />15<br />16<br />17<br />18<br />19<br />20<br />21<br />22<br />23<br />24<br />25<br />26<br />27<br />28<br />29<br />30<br />31<br />32<br />33<br />34<br /></div></td><td><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">$ symfony doctrine:build --all<br />
<br />
&nbsp;This command will remove all data in the following &quot;dev&quot; connection(s): &nbsp;<br />
<br />
&nbsp;- doctrine &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<br />
<br />
&nbsp;Are you sure you want to proceed? (y/N) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<br />
<br />
y<br />
&gt;&gt; doctrine &nbsp;Dropping &quot;doctrine&quot; database<br />
&gt;&gt; doctrine &nbsp;SQLSTATE[HY000]: General error:...ing Query: &quot;DROP DATABASE plum&quot;<br />
&gt;&gt; doctrine &nbsp;Creating &quot;dev&quot; environment &quot;doctrine&quot; database<br />
&gt;&gt; doctrine &nbsp;generating model classes<br />
&gt;&gt; file+ &nbsp; &nbsp; C:\Users\Bastien\AppData\Local\Temp/doctrine_schema_38869.yml<br />
&gt;&gt; tokens &nbsp; &nbsp;C:/wamp/www/plum/trunk/lib/mode...ine/base/BaseBookmark.class.php<br />
..<br />
&gt;&gt; autoload &nbsp;Resetting application autoloaders<br />
&gt;&gt; file- &nbsp; &nbsp; C:/wamp/www/plum/trunk/cache/fr.../config/config_autoload.yml.php<br />
&gt;&gt; doctrine &nbsp;generating form classes<br />
&gt;&gt; tokens &nbsp; &nbsp;C:/wamp/www/plum/trunk/lib/form/BaseForm.class.php<br />
&gt;&gt; tokens &nbsp; &nbsp;C:/wamp/www/plum/trunk/lib/form...base/BaseBookmarkForm.class.php<br />
...<br />
&gt;&gt; tokens &nbsp; &nbsp;C:/wamp/www/plum/trunk/lib/form/doctrine/BookmarkForm.class.php<br />
...<br />
&gt;&gt; autoload &nbsp;Resetting application autoloaders<br />
&gt;&gt; file- &nbsp; &nbsp; C:/wamp/www/plum/trunk/cache/fr.../config/config_autoload.yml.php<br />
&gt;&gt; doctrine &nbsp;generating filter form classes<br />
&gt;&gt; tokens &nbsp; &nbsp;C:/wamp/www/plum/trunk/lib/filt...aseBookmarkFormFilter.class.php<br />
...<br />
&gt;&gt; autoload &nbsp;Resetting application autoloaders<br />
&gt;&gt; file- &nbsp; &nbsp; C:/wamp/www/plum/trunk/cache/fr.../config/config_autoload.yml.php<br />
&gt;&gt; doctrine &nbsp;generating sql for models<br />
&gt;&gt; doctrine &nbsp;Generated SQL successfully for models<br />
&gt;&gt; doctrine &nbsp;created tables successfully</div></td></tr></tbody></table></div>
</pre>
<p>Vu le nombre de lignes affichées en sortie, cette commande a dû faire pas mal de choses&#8230; En fait, c&#8217;est un énorme raccourci (d&#8217;où le <strong>&#8211;all</strong>) qui a fait les actions suivantes :</p>
<ul>
<li>DROP de la base de données définie précédemment (avec une confirmation, qui peut être évitée en passant le paramètre supplémentaire  <strong>&#8211;no-confirmation</strong>),</li>
<li>création de la base de données,</li>
<li>création des <strong>model classes</strong>, qui sont une &#8220;traduction&#8221; sous formes <strong>d&#8217;entités PHP</strong> de notre modèle de données (typiquement ce qu&#8217;il y a dans le package <strong>domain </strong>dans les applis Java),</li>
<li>création des <strong>form classes</strong>, qui serviront dans les formulaires des vues (équivalent des <em>Command </em>Spring ou des <em>ActionForm </em>Struts). Ils seront entre autres chargés de valider les données entrées, et éventuellement de les transformer,</li>
<li>création de <strong>filter form classes</strong>, qui seront utilisées dans les formulaires pour chercher des enregistrements selon certains critères (des utilisateurs par sexe, couleur de cheveux, &#8230; enfin pas avec le modèle de données de Plum, bien sûr <img src='http://blog.excilys.com/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> ),</li>
<li>génération du code SQL nécessaire à la création des tables (correspondant à ce que nous avons décrit dans config/doctrine/schema.yml)</li>
<li>création des tables en BDD à partir du code SQL précédemment généré</li>
</ul>
<div class="note"><em>Note :</em> pour une fois, nous avons été feignants. Il est possible de faire toutes ces opérations étape par étape, en passant des paramètres à <strong>doctrine:build</strong> (comme &#8211;model, &#8211;filters etc. se référer à l&#8217;aide contextuelle !).</div>
<p>Ne vous inquiétez pas, nous nous attarderons plus tard sur chacun des points de la liste précédente (lorsque nous en aurons besoin, en fait). Notez tout de même que dans les noms de classes générées, on retrouve des héritages, comme par exemple <em>BaseBookmarkForm</em> et <em>BookmarkForm</em>. La première est gérée par symfony, et est susceptible d&#8217;être modifiée automatiquement suite à l&#8217;exécution d&#8217;une commande. La seconde hérite de la première, c&#8217;est celle-ci que nous pourrons modifier librement pour adapter le comportement à nos besoins (et bénéficier immédiatement des répercussions de modèle, par exemple, qui seront intégrées dans la classe de base regénérée).</p>
<h4>Scaffolding</h4>
<div id="attachment_1024" class="wp-caption alignright" style="width: 310px"><img class="size-medium wp-image-1024" title="Scaffolding" src="http://blog.excilys.com/wp-content/uploads/2010/01/Scaffolding-300x297.jpg" alt="Scaffolding" width="300" height="297" /><p class="wp-caption-text">Scaffolding en action...</p></div>
<p>Bien, maintenant que nous avons une version PHP et SQL de notre modèle, il faut l&#8217;utiliser. Pour cela, symfony, comme d&#8217;autres frameworks, propose de générer un squelette de code permettant de faire du CRUD (<em>Create-Retrieve-Update-Delete)</em>. Cette génération est généralement appelée <em>scaffolding</em> dans le jargon RoR/Grails/<em>&lt;insérez ici votre framework agile préféré&gt;</em>. Allons-y :</p>
<pre>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br />2<br />3<br />4<br />5<br />6<br />7<br />8<br />9<br />10<br /></div></td><td><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">$ symfony doctrine:generate-module frontend bookmark Bookmark<br />
&gt;&gt; dir+ &nbsp; &nbsp; &nbsp;..nd\modules/bookmark\actions<br />
&gt;&gt; file+ &nbsp; &nbsp; ...kmark\actions/actions.class.php<br />
&gt;&gt; dir+ &nbsp; &nbsp; &nbsp;...nd\modules/bookmark\templates<br />
&gt;&gt; file+ &nbsp; &nbsp; ...kmark\templates/editSuccess.php<br />
&gt;&gt; file+ &nbsp; &nbsp; ...mark\templates/indexSuccess.php<br />
&gt;&gt; file+ &nbsp; &nbsp; ...okmark\templates/newSuccess.php<br />
&gt;&gt; file+ &nbsp; &nbsp; ...kmark\templates/showSuccess.php<br />
&gt;&gt; file+ &nbsp; &nbsp; ...es/bookmark\templates/_form.php<br />
...</div></td></tr></tbody></table></div>
</pre>
<p>Nous venons de générer un <strong>module</strong>. Dans le vocabulaire symfoniesque, un module est un sous-ensemble d&#8217;une application, qui va regrouper un certains nombre de fonctionnalités liées à une &#8220;entité&#8221;. Dans notre cas, les opérations de manipulation d&#8217;un marque-page seront groupées dans le module nommé <em>bookmark</em>. Ce module a été créé dans l&#8217;application <em>frontend</em>, et il est basé sur l&#8217;objet <em>Bookmark</em> défini dans notre modèle de données. Cette commande est donc liée à Doctrine (il suffit de regarder son préfixe &#8220;doctrine:&#8221;). Dans le cas où nous souhaiterions créer un module qui n&#8217;a rien à voir avec le modèle (par exemple un module contenant des pages statiques), il faudra utiliser la commande <strong>generate:module</strong>.</p>
<h4>Aperçu</h4>
<p>Il est temps de voir ce que cette génération rend visuellement. Pour cela, <a href="http://fr.wiktionary.org/wiki/butineur">butinez</a> l&#8217;adresse <a href="http://www.plum.local/frontend_dev.php/bookmark">http://www.plum.local/frontend_dev.php/bookmark</a> :</p>
<div id="attachment_1021" class="wp-caption aligncenter" style="width: 755px"><img class="size-full wp-image-1021" title="bookmarks-scaffolding-1" src="http://blog.excilys.com/wp-content/uploads/2010/01/bookmarks-scaffolding-1.png" alt="bookmarks-scaffolding-1" width="745" height="148" /><p class="wp-caption-text">Etape 1 : la liste des bookmarks est vide</p></div>
<div id="attachment_1022" class="wp-caption aligncenter" style="width: 755px"><img class="size-full wp-image-1022" title="bookmarks-scaffolding-2" src="http://blog.excilys.com/wp-content/uploads/2010/01/bookmarks-scaffolding-2.png" alt="bookmarks-scaffolding-2" width="745" height="184" /><p class="wp-caption-text">Etape 2 : on clique sur &quot;Create&quot;, et on remplit les champs</p></div>
<div id="attachment_1023" class="wp-caption aligncenter" style="width: 754px"><img class="size-full wp-image-1023" title="bookmarks-scaffolding-3" src="http://blog.excilys.com/wp-content/uploads/2010/01/bookmarks-scaffolding-3.png" alt="bookmarks-scaffolding-3" width="744" height="162" /><p class="wp-caption-text">Etape 3 : on valide. Tadaaa !!</p></div>
<p style="text-align: left">Remarquez la <strong>barre de debug</strong> en haut à droite des pages rendues. En cliquant sur les différents textes affichés, il est possible d&#8217;avoir le détail de la configuration, des requêtes SQL exécutées, les logs générés, etc. Très utile donc, et en exclusivité chez symfony à ma connaissance (<em>edit : ou alors ils étaient les premiers, les implémentations dans Django ou Grails ne semblent pas aussi complètes)</em>. Elle n&#8217;est disponible qu&#8217;en <strong>environnement &#8220;dev&#8221;</strong>, accessible via le contrôleur <strong>frontend_dev.php</strong>. Si vous souhaitez accéder à l&#8217;<strong>environnement prod</strong>, il faut utiliser le contrôleur <strong>index.php</strong>. Il existe également les environnement de <strong>test</strong> et de <strong>staging</strong>, pour plus d&#8217;info consultez la <a href="http://www.symfony-project.org/jobeet/1_4/Doctrine/en/01#chapter_01_the_environments">documentation symfony</a>.</p>
<p style="text-align: left">Remarquez également le motif des URL dans les captures précédentes. Après le nom du contrôleur <em>frontend_dev.php</em> identifiant l&#8217;application utilisée (qui peut être omis en production grâce à de l&#8217;<a href="http://apache.developpez.com/cours/urlrewriting/">URL rewriting Apache</a> par exemple), nous retrouvons le nom du module (<em>bookmark</em>) et éventuellement une <strong>action</strong> (<em>new</em>, ou <em>index</em> par défaut). Plus d&#8217;infos sur les actions dans le chapitre qui suit.</p>
<h3>Analyse du code généré</h3>
<div id="attachment_1032" class="wp-caption alignleft" style="width: 393px"><img class="size-full wp-image-1032" title="arborescence-module" src="http://blog.excilys.com/wp-content/uploads/2010/01/arborescence-module.png" alt="arborescence-module" width="383" height="542" /><p class="wp-caption-text">Arborescence des modules</p></div>
<p>Cette magnifique image montre les deux endroits où le <em>scaffolding</em> a opéré.</p>
<p>Tout d&#8217;abord, un répertoire <em>bookmark</em> a été créé dans apps/frontend/modules. Il contient les <strong>actions</strong> et les <strong>vues</strong>. Les actions contiennent de la logique (mais pas métier), elles peuvent être apparentées à une couche service. C&#8217;est ici que nous ferons des appels aux classes du modèle générées par Doctrine.</p>
<p>Le fichier <strong>actions.class.php</strong> contient la classe <strong>bookmarkActions</strong>, dont voici un extrait :</p>
<pre>
<div class="codecolorer-container php default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br />2<br />3<br />4<br />5<br />6<br />7<br />8<br />9<br />10<br />11<br />12<br />13<br />14<br /></div></td><td><div class="php codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap"><span style="color: #000000; font-weight: bold;">class</span> bookmarkActions <span style="color: #000000; font-weight: bold;">extends</span> sfActions<br />
<span style="color: #009900;">&#123;</span><br />
&nbsp; <span style="color: #000000; font-weight: bold;">public</span> <span style="color: #000000; font-weight: bold;">function</span> executeIndex<span style="color: #009900;">&#40;</span>sfWebRequest <span style="color: #000088;">$request</span><span style="color: #009900;">&#41;</span><br />
&nbsp; <span style="color: #009900;">&#123;</span><br />
&nbsp; &nbsp; <span style="color: #000088;">$this</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">bookmarks</span> <span style="color: #339933;">=</span> Doctrine<span style="color: #339933;">::</span><span style="color: #004000;">getTable</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'Bookmark'</span><span style="color: #009900;">&#41;</span><br />
&nbsp; &nbsp; &nbsp; <span style="color: #339933;">-&gt;</span><span style="color: #004000;">createQuery</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'a'</span><span style="color: #009900;">&#41;</span><br />
&nbsp; &nbsp; &nbsp; <span style="color: #339933;">-&gt;</span><span style="color: #004000;">execute</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span><br />
&nbsp; <span style="color: #009900;">&#125;</span><br />
<br />
&nbsp; <span style="color: #000000; font-weight: bold;">public</span> <span style="color: #000000; font-weight: bold;">function</span> executeNew<span style="color: #009900;">&#40;</span>sfWebRequest <span style="color: #000088;">$request</span><span style="color: #009900;">&#41;</span><br />
&nbsp; <span style="color: #009900;">&#123;</span><br />
&nbsp; &nbsp; <span style="color: #000088;">$this</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">form</span> <span style="color: #339933;">=</span> <span style="color: #000000; font-weight: bold;">new</span> BookmarkForm<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span><br />
&nbsp; <span style="color: #009900;">&#125;</span><br />
&nbsp; <span style="color: #666666; font-style: italic;">//...</span></div></td></tr></tbody></table></div>
</pre>
<p>Chaque action correspond à une méthode dont le nom est de la forme <strong>execute<em>&lt;NomDeL&#8217;ActionEnCamlCase&gt;</em></strong>. Dans l&#8217;extrait de code précédent, nous pouvons voir deux actions, &#8220;index&#8221; et &#8220;new&#8221;, responsable du listage et de la création d&#8217;un bookmark. Nous pouvons voir l&#8217;utilisation de la classe <em>BookmarkForm</em> générée par Doctrine, qui sera utilisée dans la vue.</p>
<p>Les vues sont définies dans le répertoire <em>templates</em>, en général on en retrouve une par action. Leur nom suit le motif  <strong><em>nomAction&lt;Etat&gt;</em></strong><strong>.php</strong>. Par défaut, l&#8217;état vaut &#8220;success&#8221;, mais il est possible d&#8217;en spécifier un autre en retournant une chaine de caractères à la fin de la méthode d&#8217;action, par exemple &#8220;error&#8221;, ou mieux : sfView::ERROR. Dans ce cas, le template <strong><em>&lt;<strong><em>nomAction</em></strong>&gt;</em>Error.php</strong> sera utilisé.</p>
<p>Symfony permet de renvoyer n&#8217;importe quel type de données dans la vue. Par défaut c&#8217;est du HTML, mais rien n&#8217;empêche de générer un PDF ou une image en réponse de la requête. Il faut alors veiller à ce que le serveur envoie le bon type MIME dans la réponse, pour que le navigateur gère correctement le résultat.</p>
<p>Le second endroit où le scaffolding a ajouté des fichier est le répertoire de tests. On remarque qu&#8217;un <strong>test fonctionnel</strong> a été ajouté pour la classe <strong>bookmarkActions</strong> :</p>
<pre>
<div class="codecolorer-container php default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br />2<br />3<br />4<br />5<br />6<br />7<br />8<br />9<br />10<br />11<br />12<br />13<br />14<br />15<br />16<br />17<br /></div></td><td><div class="php codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap"><span style="color: #b1b100;">include</span><span style="color: #009900;">&#40;</span><span style="color: #990000;">dirname</span><span style="color: #009900;">&#40;</span><span style="color: #009900; font-weight: bold;">__FILE__</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">.</span><span style="color: #0000ff;">'/../../bootstrap/functional.php'</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span><br />
<br />
<span style="color: #000088;">$browser</span> <span style="color: #339933;">=</span> <span style="color: #000000; font-weight: bold;">new</span> sfTestFunctional<span style="color: #009900;">&#40;</span><span style="color: #000000; font-weight: bold;">new</span> sfBrowser<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span><br />
<br />
<span style="color: #000088;">$browser</span><span style="color: #339933;">-&gt;</span><br />
&nbsp; <span style="color: #004000;">get</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'/bookmark/index'</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">-&gt;</span><br />
<br />
&nbsp; <span style="color: #004000;">with</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'request'</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">begin</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">-&gt;</span><br />
&nbsp; &nbsp; <span style="color: #004000;">isParameter</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'module'</span><span style="color: #339933;">,</span> <span style="color: #0000ff;">'bookmark'</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">-&gt;</span><br />
&nbsp; &nbsp; <span style="color: #004000;">isParameter</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'action'</span><span style="color: #339933;">,</span> <span style="color: #0000ff;">'index'</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">-&gt;</span><br />
&nbsp; <span style="color: #990000;">end</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">-&gt;</span><br />
<br />
&nbsp; <span style="color: #004000;">with</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'response'</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">begin</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">-&gt;</span><br />
&nbsp; &nbsp; <span style="color: #004000;">isStatusCode</span><span style="color: #009900;">&#40;</span><span style="color: #cc66cc;">200</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">-&gt;</span><br />
&nbsp; &nbsp; <span style="color: #004000;">checkElement</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'body'</span><span style="color: #339933;">,</span> <span style="color: #0000ff;">'!/This is a temporary page/'</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">-&gt;</span><br />
&nbsp; <span style="color: #990000;">end</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><br />
<span style="color: #339933;">;</span></div></td></tr></tbody></table></div>
</pre>
<p>Nous n&#8217;allons trop nous attarder sur ce test. Sachez juste que le framework propose un &#8220;émulateur de navigateur&#8221;, sur lequel nous allons pouvoir appeler des pages, puis faire des vérifications dans la requête ainsi que dans la réponse. Ici, le test vérifie que si l&#8217;on appelle l&#8217;action <em>index</em> du module <em>bookmark</em>, la réponse a un statut HTTP 200 (aucun souci) et la page renvoyée ne contient pas la chaine &#8220;This is a temporary page&#8221; que l&#8217;on trouve dans la page par défaut d&#8217;un projet symfony.</p>
<p>Juste pour être surs, nous pouvons lancer les tests :</p>
<pre>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br />2<br />3<br />4<br /></div></td><td><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">$ ./symfony test:functional frontend<br />
bookmarkActionsTest..................................................ok<br />
&nbsp; All tests successful.<br />
&nbsp; Files=1, Tests=4</div></td></tr></tbody></table></div>
</pre>
<h3>Routage</h3>
<p>Récapitulons ce qui s&#8217;est passé. Nous venons de générer un module permettant de faire du CRUD sur la table des bookmarks. Les formulaires HTML générés sont accessibles à l&#8217;URL <a href="http://www.plum.local/frontend_dev.php/bookmark">http://www.plum.local/frontend_dev.php/bookmark</a>, mais pour l&#8217;instant l&#8217;URL &#8220;/&#8221; pointe toujours vers la page par défaut de symfony si on ne spécifie aucun module. Corrigeons cela en éditant le fichier de <strong>routage</strong> du frontend :</p>
<pre>
<div class="codecolorer-container yaml default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br />2<br />3<br />4<br />5<br />6<br />7<br />8<br />9<br />10<br />11<br />12<br />13<br />14<br />15<br />16<br />17<br /></div></td><td><div class="yaml codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap"><span style="color: blue;"># apps/frontend/config/routing.yml</span><br />
<span style="color: blue;"># You can find more information about this file on the symfony website:</span><br />
<span style="color: blue;"># http://www.symfony-project.org/reference/1_4/en/10-Routing</span><br />
<br />
<span style="color: blue;"># default rules</span><span style="color: #007F45;"><br />
homepage</span>:<span style="color: green;"><br />
&nbsp; url</span><span style="font-weight: bold; color: brown;">: </span> &nbsp;/<span style="color: green;"><br />
&nbsp; param</span><span style="font-weight: bold; color: brown;">: </span><span class="br0">&#123;</span> module<span style="font-weight: bold; color: brown;">: </span>default, action<span style="font-weight: bold; color: brown;">: </span>index <span class="br0">&#125;</span><br />
<br />
<span style="color: blue;"># generic rules</span><br />
<span style="color: blue;"># please, remove them by adding more specific rules</span><span style="color: #007F45;"><br />
default_index</span>:<span style="color: green;"><br />
&nbsp; url</span><span style="font-weight: bold; color: brown;">: </span> &nbsp;/:module<span style="color: green;"><br />
&nbsp; param</span><span style="font-weight: bold; color: brown;">: </span><span class="br0">&#123;</span> action<span style="font-weight: bold; color: brown;">: </span>index <span class="br0">&#125;</span><br />
<span style="color: #007F45;"><br />
default</span>:<span style="color: green;"><br />
&nbsp; url</span><span style="font-weight: bold; color: brown;">: </span> &nbsp;/:module/:action/*</div></td></tr></tbody></table></div>
</pre>
<p>Le <strong>routage</strong> est un composant important du framework (même si en fait, tous les composants sont importants <img src='http://blog.excilys.com/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> ). Il va permettre d&#8217;appeler la bonne action sur le bon module en fonction de l&#8217;URL demandée. Le fichier de configuration précédent est une <strong>série de règles</strong>, qui vont être parcourues l&#8217;une après l&#8217;autre. La première <em>matchant</em> l&#8217;URL appelée gagne. Pour notre page par défaut, c&#8217;est la première règle qui est utilisée (rien après le slash). La clé <em>param</em> indique le module et l&#8217;action à utiliser : ici, c&#8217;est l&#8217;action <em>index</em> dans le module <em>default</em> situé au fin fond du framework.</p>
<p>Si la première règle ne convient pas, alors on essaye de voir si on peut trouver un nom de module, auquel cas on exécute par défaut l&#8217;action <em>index</em>. En dernier choix, le routage tente de trouver un module <strong>et</strong> une action (éventuellement suivie de paramètres dénotés par l&#8217;astérisque).</p>
<p>Nous souhaiterions afficher par défaut la liste des bookmarks enregistrés, il va donc falloir pointer vers <strong>bookmark/index</strong> :</p>
<pre>
<div class="codecolorer-container yaml default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br />2<br />3<br />4<br /></div></td><td><div class="yaml codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap"><span style="color: blue;"># apps/frontend/config/routing.yml</span><span style="color: #007F45;"><br />
homepage</span>:<span style="color: green;"><br />
&nbsp; url</span><span style="font-weight: bold; color: brown;">: </span> &nbsp;/<span style="color: green;"><br />
&nbsp; param</span><span style="font-weight: bold; color: brown;">: </span><span class="br0">&#123;</span> module<span style="font-weight: bold; color: brown;">: </span>bookmark, action<span style="font-weight: bold; color: brown;">: </span>index <span class="br0">&#125;</span></div></td></tr></tbody></table></div>
</pre>
<p>Et voilà le résultat :</p>
<div id="attachment_1034" class="wp-caption aligncenter" style="width: 559px"><img class="size-full wp-image-1034" title="homepage-apres-routing-vers-bookmark" src="http://blog.excilys.com/wp-content/uploads/2010/01/homepage-apres-routing-vers-bookmark.png" alt="homepage-apres-routing-vers-bookmark" width="549" height="168" /><p class="wp-caption-text">Nouvelle homepage</p></div>
<div class="note"><em>Note :</em> si vous utilisez le contrôleur de prod, index.php, il se peut que les changements ne soient pas pris en compte, à cause du cache de l&#8217;application. Il faut alors exécuter la commande <strong>symfony cache:clear</strong>, ou pour les plus feignants <strong>symfony cc</strong>.</div>
<p>Grails propose un système de routes du même acabit, où le nom du contrôleur et de l&#8217;action sont également concaténés avec des slashs. Après recherches, Grails ne semble pas proposer de <strong>routes nommées</strong> comme le font symfony et Ruby on Rails (du moins, pas directement). Les routes nommées permettent de rediriger vers ou de créer une URL à partir d&#8217;un nom plus explicite. Dans le listing précédent, on voit un exemple avec la route nommée <strong>homepage</strong> qui pointe vers <strong>/bookmark/index</strong>. Elle peut ensuite être référencée ainsi :</p>
<pre>
<div class="codecolorer-container php default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br /></div></td><td><div class="php codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">link_to<span style="color: #009900;">&#40;</span><span style="color: #0000ff;">&quot;Rentrer à la maison&quot;</span><span style="color: #339933;">,</span> <span style="color: #0000ff;">'@homepage?param1=value1'</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span></div></td></tr></tbody></table></div>
</pre>
<h3>Bonux : comparaison des commandes grails et symfony + vocabulaire utilisé</h3>
<pre>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br />2<br />3<br />4<br />5<br />6<br />7<br /></div></td><td><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap"># Création des classes du domaine<br />
$ grails create-domain-class com.excilys.grails.Song # On fait générer des classes du modèle une par une. La base de donnée est modifiée au runtime.<br />
$ symfony doctrine:build --all # On définit le modèle dans un fichier de config, et symfony génère les classes associées et met à jour la base de donnée.<br />
<br />
# Génération des controleurs et des vues pour le CRUD<br />
$ grails generate-all com.excilys.grails.Song<br />
$ symfony doctrine:generate-module frontend bookmark Bookmark</div></td></tr></tbody></table></div>
</pre>
<div class="note"><em>Note: attention tout de même, ces commandes ne sont pas strictement équivalentes (notamment au niveau de la spécification d&#8217;une classe particulère à générer)</em></div>
<p>Certains relecteurs m&#8217;ont fait remarquer que les différences de vocabulaire entre symfony/grails/une application Java peuvent porter à confusion. Voici donc des équivalences de termes (à mon sens) :</p>
<ul>
<li>contrôleur symfony &lt;=&gt; DispatcherServlet Spring</li>
<li>actions symfony/grails &lt;=&gt; Controller Spring</li>
<li>*Table extends Doctrine_Table &lt;=&gt; DAO</li>
</ul>
<p>Notez également que symfony recommande de placer la logique métier dans les classes *Table, les actions vont uniquement faire des tâches &#8220;simples&#8221; comme vérifier les paramètres obligatoires dans une URL, appeler les DAO, définir les variables utilisées dans les vues, faire des redirect/forward et laisser la main à la vue.</p>
<h3>Stay tuned for scenes from our next episode&#8230;</h3>
<p>Nous venons de générer un module avec du code par défaut. Il va maintenant falloir l&#8217;adapter : supprimer les actions non utilisées, en ajouter si besoin, rendre l&#8217;apparence un peu plus attrayante&#8230; Bref, c&#8217;est maintenant que nous allons arrêter de faire du &#8220;par défaut&#8221; pour faire du &#8220;adapté à ce qu&#8217;on veut&#8221;. Et c&#8217;est aussi maintenant que l&#8217;on va pouvoir voir si le framework est suffisamment souple pour répondre à nos besoins (un indice : la réponse est &#8220;oui&#8221;).</p>
<p>A bientôt !</p>
<h3>Accès au code source</h3>
<p>Le code source de cet article est disponible sur le projet googlecode d’Excilys à l’adresse : <a href="http://excilys.googlecode.com/svn/projects/plum/tags/plum_article_2/">http://excilys.googlecode.com/svn/projects/plum/tags/plum_article_2/</a></p>
<p><!--[if IE]><iframe frameborder="0" allowTransparency="true" class="addtoany_special_service twitter_tweet" src="http://platform.twitter.com/widgets/tweet_button.html?url=http%3A%2F%2Fblog.excilys.com%2F2010%2F01%2F15%2Fmenez-le-web-a-la-baguette-avec-symfony-2-un-squelette-pour-pas-chair%2F&amp;counturl=http%3A%2F%2Fblog.excilys.com%2F2010%2F01%2F15%2Fmenez-le-web-a-la-baguette-avec-symfony-2-un-squelette-pour-pas-chair%2F&amp;count=horizontal&amp;text=Menez%20le%20web%20%C3%A0%20la%20baguette%20avec%20symfony%20%282%29%20%3A%20un%20squelette%20pour%20pas%20chair" scrolling="no" style="border:none;overflow:hidden;width:130px;height:20px"></iframe><![endif]--><!--[if !IE]><!--><iframe class="addtoany_special_service twitter_tweet" src="http://platform.twitter.com/widgets/tweet_button.html?url=http%3A%2F%2Fblog.excilys.com%2F2010%2F01%2F15%2Fmenez-le-web-a-la-baguette-avec-symfony-2-un-squelette-pour-pas-chair%2F&amp;counturl=http%3A%2F%2Fblog.excilys.com%2F2010%2F01%2F15%2Fmenez-le-web-a-la-baguette-avec-symfony-2-un-squelette-pour-pas-chair%2F&amp;count=horizontal&amp;text=Menez%20le%20web%20%C3%A0%20la%20baguette%20avec%20symfony%20%282%29%20%3A%20un%20squelette%20pour%20pas%20chair" scrolling="no" style="border:none;overflow:hidden;width:130px;height:20px"></iframe><!--<![endif]--><!--[if IE]><iframe frameborder="0" allowTransparency="true" class="addtoany_special_service google_plusone" src="https://plusone.google.com/u/0/_/%2B1/fastbutton?url=http%3A%2F%2Fblog.excilys.com%2F2010%2F01%2F15%2Fmenez-le-web-a-la-baguette-avec-symfony-2-un-squelette-pour-pas-chair%2F&amp;size=medium&amp;count=true" scrolling="no" style="border:none;overflow:hidden;width:90px;height:20px"></iframe><![endif]--><!--[if !IE]><!--><iframe class="addtoany_special_service google_plusone" src="https://plusone.google.com/u/0/_/%2B1/fastbutton?url=http%3A%2F%2Fblog.excilys.com%2F2010%2F01%2F15%2Fmenez-le-web-a-la-baguette-avec-symfony-2-un-squelette-pour-pas-chair%2F&amp;size=medium&amp;count=true" scrolling="no" style="border:none;overflow:hidden;width:90px;height:20px"></iframe><!--<![endif]--><a class="a2a_dd a2a_target addtoany_share_save" href="http://www.addtoany.com/share_save#url=http%3A%2F%2Fblog.excilys.com%2F2010%2F01%2F15%2Fmenez-le-web-a-la-baguette-avec-symfony-2-un-squelette-pour-pas-chair%2F&amp;title=Menez%20le%20web%20%C3%A0%20la%20baguette%20avec%20symfony%20%282%29%20%3A%20un%20squelette%20pour%20pas%20chair" id="wpa2a_4"><img src="http://blog.excilys.com/wp-content/plugins/add-to-any/share_save_120_16.png" width="120" height="16" alt="Share"/></a></p>]]></content:encoded>
			<wfw:commentRss>http://blog.excilys.com/2010/01/15/menez-le-web-a-la-baguette-avec-symfony-2-un-squelette-pour-pas-chair/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>Menez le web à la baguette avec symfony</title>
		<link>http://blog.excilys.com/2010/01/07/menez-le-web-a-la-baguette-avec-symfony/</link>
		<comments>http://blog.excilys.com/2010/01/07/menez-le-web-a-la-baguette-avec-symfony/#comments</comments>
		<pubDate>Thu, 07 Jan 2010 09:55:53 +0000</pubDate>
		<dc:creator>Bastien JANSEN</dc:creator>
				<category><![CDATA[symfony]]></category>
		<category><![CDATA[entreprise]]></category>
		<category><![CDATA[framework web]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[series-symfony-plum]]></category>
		<category><![CDATA[tutoriel]]></category>

		<guid isPermaLink="false">http://blog.excilys.com/?p=847</guid>
		<description><![CDATA[Introduction Chez Excilys, nous ne sommes pas sectaires. Bien que le groupe concentre son activité autour des technologies Java, bon nombre de collaborateurs s&#8217;intéressent à d&#8217;autres langages ou technologies. Après Android et Grails, je vais vous présenter le framework PHP symfony au travers d&#8217;une série d&#8217;articles dans lesquels nous construirons petit à petit une application [...]]]></description>
			<content:encoded><![CDATA[<h3>Introduction</h3>
<p>Chez Excilys, nous ne sommes pas sectaires. Bien que le groupe concentre son activité autour des technologies Java, bon nombre de collaborateurs s&#8217;intéressent à d&#8217;autres langages ou technologies. Après Android et Grails, je vais vous présenter le framework PHP symfony au travers d&#8217;une série d&#8217;articles dans lesquels nous construirons petit à petit une application web.</p>
<p>Commençons par un rapide aperçu de <a href="http://www.php.net/" target="_blank">PHP</a>.</p>
<pre><img class="alignright size-medium wp-image-943" src="http://blog.excilys.com/wp-content/uploads/2009/12/elephpant-300x192.png" alt="elephpant" width="210" height="134" /></pre>
<p><em>PHP: Hypertext Preprocessor</em> est un langage de script créé par Rasmus Lerdorf en 1994. Bien que le langage soit majoritairement utilisé pour créer des sites/applications web (du fait qu&#8217;il propose beaucoup d&#8217;outils allant dans ce sens), il est tout à fait possible d&#8217;utiliser PHP pour créer des applications type script d&#8217;administration système, voire des applications graphiques (avec les <a href="http://gtk.php.net/" target="_blank">bindings GTK</a> par exemple).</p>
<p>Depuis ses débuts où il était écrit en Perl, PHP a évolué petit à petit pour devenir un langage objet (<em>grosso modo</em> depuis sa version 5), avec de nombreuses facilités telles que :</p>
<ul>
<li>un accès très simple aux SGBD (MySQL, PostGreSQL&#8230;),</li>
<li>une manipulation de XML via DOM ou SimpleXML,</li>
<li>la possibilité de générer des images via GD,</li>
<li>de <a href="http://www.php.net/manual/fr/funcref.php" target="_blank">nombreuses autres API</a>.</li>
</ul>
<p>Durant de nombreuses années, PHP a été considéré comme un langage d&#8217;amateur, <span id="more-847"></span>tout juste bon à bidouiller son site perso (PHP signifiait initialement <em>Personal Home Page</em> <img src='http://blog.excilys.com/wp-includes/images/smilies/icon_smile.gif' alt=':-)' class='wp-smiley' /> ). Il est vrai que cette techno n&#8217;a pas tous les atouts de son côté :</p>
<ul>
<li>apparition tardive et pas forcément bien implémentée d&#8217;une syntaxe orientée objet,</li>
<li>incohérence (ou absence ?) de conventions de nommage et de paramétrage des fonctions des API,</li>
<li>nombreux <a href="http://www.coelho.net/php_cve.html" target="_blank">problèmes de sécurité</a> (aussi bien dans le langage que dans les principales applications).</li>
</ul>
<p>Cependant, il est à noter que PHP a tout de même certains avantages :</p>
<ul>
<li>langage assez simple à apprendre et à utiliser (un <em>hello world</em> consiste à écrire &#8220;Hello world!&#8221; dans un fichier .php),</li>
<li>performances  convenables (et de nombreux <em>tunings</em> peuvent être effectués pour la <em>scalability</em> : cache, opcodes etc.),</li>
<li>documentation et aide très faciles à trouver.</li>
</ul>
<p>Comme nous l&#8217;avons vu, PHP a longtemps été délaissé par les professionnels. Cependant la tendance s&#8217;inverse, le monde de l&#8217;entreprise semble de plus en plus s&#8217;intéresser (et contribuer) à cette technologie. La preuve en est avec la <a href="http://fr.wikipedia.org/wiki/Liste_de_frameworks_PHP" target="_blank">pléthore de frameworks PHP</a> apparus depuis quelques années :</p>
<ul>
<li>symfony,</li>
<li>Zend Framework,</li>
<li>CakePHP,</li>
<li>Code Igniter.</li>
</ul>
<p>Les outils, bonnes pratiques et <em>design patterns</em> qui font le succès de langages tels que Java commencent à faire leur apparition au sein de la communauté PHP :</p>
<ul>
<li><a href="http://www.phpdoc.org/" target="_blank">phpdoc</a>,</li>
<li><a href="http://components.symfony-project.org/dependency-injection/" target="_blank">injection de dépendances</a>,</li>
<li>mapping <a href="http://www.doctrine-project.org/" target="_blank">relationnel</a>-<a href="http://propel.phpdb.org/trac/" target="_blank">objet</a>.</li>
</ul>
<h3>Présentation de symfony</h3>
<p><img class="alignleft size-full wp-image-945" src="http://blog.excilys.com/wp-content/uploads/2009/12/symfony1.gif" alt="symfony" width="188" height="51" />Le but de cet article, puis de ses petits frères, sera de nous familiariser avec l&#8217;un des frameworks PHP les plus répandus actuellement, à savoir <a href="http://www.symfony-project.org/" target="_self">symfony</a>. symfony est développé par Sensio Labs, une entreprise française spécialisée dans le développement web et les technologies Open Source.</p>
<p>Ce framework est intéressant sous plusieurs aspects. Outre le fait qu&#8217;il soit open source, il est développé par une entreprise, pour des entreprises. Sensio Labs assure donc le support adéquat, avec notamment des <a href="http://trainings.sensiolabs.com/" target="_blank">formations</a> permettant de maitriser la bête en quelques jours. Côté <a href="http://www.symfony-project.org/doc/1_4/" target="_blank">documentation</a>, nous sommes gâtés : tutoriels officiels (sous forme de calendrier de l&#8217;Avent), livres sous format électronique (gratuits) ou papier, forums, ainsi qu&#8217;une foultitude de sites de la communauté.</p>
<p>symfony, tout comme Maven ou Ruby on Rails, prône le paradigme <em>&#8220;convention over configuration&#8221;</em>. Toutefois, rien n&#8217;empêche le développeur téméraire de bousculer totalement le &#8220;normal&#8221; et adapter la structure à son goût (ou à de l&#8217;existant), de remplacer certains composants etc. Les différentes briques du frameworks se veulent les moins couplées possibles, de manière à pouvoir les remplacer ou les supprimer si elle ne sont pas nécessaires dans un projet.</p>
<p>Dans sa prochaine version majeure (actuellement en <em>heavy development</em>), Symfony 2 (notez la majuscule cette fois !) utilisera différents <a href="http://components.symfony-project.org/" target="_blank">composants</a> récemment développés, qui peuvent tout à fait être intégrés dans des projets utilisant (ou non) un autre framework : gestion du format YAML, event dispatcher, injection de dépendances, templating etc.</p>
<p>&#8220;Assez de blabla, plus de concret !&#8221;, entends-je dans le fond de la salle (près du radiateur&#8230;). Passons donc à l&#8217;action !</p>
<h3>Présentation du projet : Plum</h3>
<p>Afin de plonger dans le framework, je vous propose de développer directement une application web, que nous nommerons Plum (pour <em>Plum Link &amp; Url Manager</em> par exemple, ou tout ce que votre imagination trouvera). Cette application aura pour principal fonction d&#8217;agréger des marques-pages, à la manière de <a href="http://www.delicious.com" target="_blank">del.icio.us</a>. Un peu de fonctionnel, alors.</p>
<p>On désire pouvoir stocker des <em>bookmarks</em>, qui ne sont rien de plus que des <strong>URLs</strong> associées éventuellement à une <strong>description</strong>, ajoutées à une certaine <strong>date</strong>. À ces URLs, on va associer zéro ou plusieurs <strong>tags</strong>, qui sont en fait des mots-clés permettant de retrouver facilement des bookmarks liés à un sujet particulier.</p>
<p>Exemple :</p>
<pre>URL : http://www.excilys.com
Description : Site du groupe Excilys
Date de création : 25/12/2009
Tags : excilys, développement, java, javaee, trop bien</pre>
<h3>Mise en place de l&#8217;environnement</h3>
<p>À partir de maintenant, je suppose que vous disposez déjà d&#8217;un environnement Apache/PHP/MySQL fonctionnel. Si ce n&#8217;est pas le cas, de nombreux <a href="http://www.wampserver.com/" target="_blank">outils</a> <a href="http://www.mamp.info/en/index.html" target="_blank">et</a> <a href="http://doc.ubuntu-fr.org/lamp" target="_blank">tutoriels</a> vous aideront si vous souhaitez installer Plum chez vous.</p>
<p>La récupération de symfony peut se faire de trois façons : par SVN, PEAR ou en téléchargeant une archive .zip ou .tgz. Nous choisirons la troisième solution (car elle nécessite moins d&#8217;utilitaires prérequis). Nous allons travailler sur la version 1.4.1 de symfony, sortie récemment et disposant d&#8217;un <em>Long Term Support</em> (LTS). Elle est à télécharger <a href="http://www.symfony-project.org/get/symfony-1.4.1.tgz">ici</a>.</p>
<p>Dans la suite du projet, j&#8217;utiliserai le répertoire ~/prog/php/ comme répertoire de travail, mais vous êtes libres de l&#8217;adapter à votre OS/goût. Après avoir extrait l&#8217;archive symfony-1.4.1.tgz dans ~/prog/php, un dossier symfony-1.4.1 a dû être créé. Il contient l&#8217;ensemble du framework (dans <strong>lib/</strong>) ainsi que des assets et scripts de lancement (dans <strong>data/</strong>) et les tests unitaires/fonctionnels du framework (dans <strong>test/</strong>).</p>
<p>La première étape est de créer la structure du <strong>projet</strong> :</p>
<pre>
<div class="codecolorer-container bash default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br />2<br />3<br />4<br />5<br />6<br />7<br />8<br />9<br />10<br />11<br />12<br /></div></td><td><div class="bash codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">bastien:php $ <span style="color: #c20cb9; font-weight: bold;">mkdir</span> plum<br />
bastien:php $ <span style="color: #7a0874; font-weight: bold;">cd</span> plum<span style="color: #000000; font-weight: bold;">/</span><br />
bastien:plum $ ..<span style="color: #000000; font-weight: bold;">/</span>symfony-1.4.1<span style="color: #000000; font-weight: bold;">/</span>data<span style="color: #000000; font-weight: bold;">/</span>bin<span style="color: #000000; font-weight: bold;">/</span>symfony generate:project plum<br />
<span style="color: #000000; font-weight: bold;">&gt;&gt;</span> <span style="color: #c20cb9; font-weight: bold;">dir</span>+ &nbsp; &nbsp; &nbsp;<span style="color: #000000; font-weight: bold;">/</span>home<span style="color: #000000; font-weight: bold;">/</span>bastien<span style="color: #000000; font-weight: bold;">/</span>prog<span style="color: #000000; font-weight: bold;">/</span>php<span style="color: #000000; font-weight: bold;">/</span>plum<span style="color: #000000; font-weight: bold;">/</span>log<br />
<span style="color: #000000; font-weight: bold;">&gt;&gt;</span> <span style="color: #c20cb9; font-weight: bold;">file</span>+ &nbsp; &nbsp; <span style="color: #000000; font-weight: bold;">/</span>home<span style="color: #000000; font-weight: bold;">/</span>bastien<span style="color: #000000; font-weight: bold;">/</span>prog<span style="color: #000000; font-weight: bold;">/</span>php<span style="color: #000000; font-weight: bold;">/</span>plum<span style="color: #000000; font-weight: bold;">/</span>symfony<br />
<span style="color: #000000; font-weight: bold;">&gt;&gt;</span> <span style="color: #c20cb9; font-weight: bold;">dir</span>+ &nbsp; &nbsp; &nbsp;<span style="color: #000000; font-weight: bold;">/</span>home<span style="color: #000000; font-weight: bold;">/</span>bastien<span style="color: #000000; font-weight: bold;">/</span>prog<span style="color: #000000; font-weight: bold;">/</span>php<span style="color: #000000; font-weight: bold;">/</span>plum<span style="color: #000000; font-weight: bold;">/</span><span style="color: #7a0874; font-weight: bold;">test</span><br />
<span style="color: #000000; font-weight: bold;">&gt;&gt;</span> <span style="color: #c20cb9; font-weight: bold;">dir</span>+ &nbsp; &nbsp; &nbsp;<span style="color: #000000; font-weight: bold;">/</span>home<span style="color: #000000; font-weight: bold;">/</span>bastien<span style="color: #000000; font-weight: bold;">/</span>prog<span style="color: #000000; font-weight: bold;">/</span>php<span style="color: #000000; font-weight: bold;">/</span>plum<span style="color: #000000; font-weight: bold;">/</span>test<span style="color: #000000; font-weight: bold;">/</span>functional<br />
<span style="color: #000000; font-weight: bold;">&gt;&gt;</span> <span style="color: #c20cb9; font-weight: bold;">dir</span>+ &nbsp; &nbsp; &nbsp;<span style="color: #000000; font-weight: bold;">/</span>home<span style="color: #000000; font-weight: bold;">/</span>bastien<span style="color: #000000; font-weight: bold;">/</span>prog<span style="color: #000000; font-weight: bold;">/</span>php<span style="color: #000000; font-weight: bold;">/</span>plum<span style="color: #000000; font-weight: bold;">/</span>test<span style="color: #000000; font-weight: bold;">/</span>bootstrap<br />
...<br />
<span style="color: #000000; font-weight: bold;">&gt;&gt;</span> tokens &nbsp; &nbsp;<span style="color: #000000; font-weight: bold;">/</span>home<span style="color: #000000; font-weight: bold;">/</span>bastien<span style="color: #000000; font-weight: bold;">/</span>prog<span style="color: #000000; font-weight: bold;">/</span>php<span style="color: #000000; font-weight: bold;">/</span>plum<span style="color: #000000; font-weight: bold;">/</span>config<span style="color: #000000; font-weight: bold;">/</span>doctrine<span style="color: #000000; font-weight: bold;">/</span>schema.yml<br />
<span style="color: #000000; font-weight: bold;">&gt;&gt;</span> tokens &nbsp; &nbsp;<span style="color: #000000; font-weight: bold;">/</span>home<span style="color: #000000; font-weight: bold;">/</span>bastien<span style="color: #000000; font-weight: bold;">/</span>prog<span style="color: #000000; font-weight: bold;">/</span>php<span style="color: #000000; font-weight: bold;">/</span>plum<span style="color: #000000; font-weight: bold;">/</span>lib<span style="color: #000000; font-weight: bold;">/</span>form<span style="color: #000000; font-weight: bold;">/</span>BaseForm.class.php<br />
bastien:plum $</div></td></tr></tbody></table></div>
</pre>
<p><strong>Explications :</strong> rien de bien sorcier, on créé un répertoire qui va contenir notre projet, on se met dedans et on appelle le script principal de symfony (que nous venons d&#8217;extraire), avec les paramètres suivants :</p>
<ul>
<li><em>generate:project</em> : c&#8217;est la commande symfony que nous invoquons, qui va&#8230; générer un projet <img src='http://blog.excilys.com/wp-includes/images/smilies/icon_smile.gif' alt=':-)' class='wp-smiley' /> </li>
<li>plum : c&#8217;est le seul et unique paramètre de la commande, ici le nom de notre projet</li>
</ul>
<p>Maintenant que nous avons un projet, il faut y ajouter une <strong>application</strong> (un projet peut contenir plusieurs applications). Nous allons dans un premier temps générer le <em>frontend</em>, c&#8217;est-à-dire l&#8217;application principale que verront les utilisateurs <em>lambda</em> :</p>
<pre>
<div class="codecolorer-container bash default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br />2<br />3<br />4<br />5<br />6<br /></div></td><td><div class="bash codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">bastien:plum $ .<span style="color: #000000; font-weight: bold;">/</span>symfony generate:app frontend<br />
<span style="color: #000000; font-weight: bold;">&gt;&gt;</span> <span style="color: #c20cb9; font-weight: bold;">dir</span>+ &nbsp; &nbsp; &nbsp;<span style="color: #000000; font-weight: bold;">/</span>home<span style="color: #000000; font-weight: bold;">/</span>bastien<span style="color: #000000; font-weight: bold;">/</span>prog<span style="color: #000000; font-weight: bold;">/</span>php<span style="color: #000000; font-weight: bold;">/</span>plum<span style="color: #000000; font-weight: bold;">/</span>apps<span style="color: #000000; font-weight: bold;">/</span>frontend<span style="color: #000000; font-weight: bold;">/</span>templates<br />
<span style="color: #000000; font-weight: bold;">&gt;&gt;</span> <span style="color: #c20cb9; font-weight: bold;">file</span>+ &nbsp; &nbsp; <span style="color: #000000; font-weight: bold;">/</span>home<span style="color: #000000; font-weight: bold;">/</span>bastien<span style="color: #000000; font-weight: bold;">/</span>prog<span style="color: #000000; font-weight: bold;">/</span>php<span style="color: #000000; font-weight: bold;">/</span>plum<span style="color: #000000; font-weight: bold;">/</span>apps<span style="color: #000000; font-weight: bold;">/</span>frontend<span style="color: #000000; font-weight: bold;">/</span>templates<span style="color: #000000; font-weight: bold;">/</span>layout.php<br />
...<br />
<span style="color: #000000; font-weight: bold;">&gt;&gt;</span> <span style="color: #c20cb9; font-weight: bold;">dir</span>+ &nbsp; &nbsp; &nbsp;<span style="color: #000000; font-weight: bold;">/</span>home<span style="color: #000000; font-weight: bold;">/</span>bastien<span style="color: #000000; font-weight: bold;">/</span>prog<span style="color: #000000; font-weight: bold;">/</span>php<span style="color: #000000; font-weight: bold;">/</span>plum<span style="color: #000000; font-weight: bold;">/</span>test<span style="color: #000000; font-weight: bold;">/</span>functional<span style="color: #000000; font-weight: bold;">/</span>frontend<br />
bastien:plum $</div></td></tr></tbody></table></div>
</pre>
<p>Notez que maintenant nous utilisons directement le script <strong>./symfony</strong> (qui appelle derrière le script que nous appelions précédemment). La commande <strong>generate:app </strong>est invoquée pour générer une application nommée <strong>frontend</strong>, qui sera accessible dans un sous-dossier de <strong>apps/</strong><em>.</em></p>
<h3>Analyse du projet généré</h3>
<p>En retour, le script va nous générer un tas de dossiers et de fichiers, qui constituent l&#8217;arborescence de base d&#8217;un projet symfony :<img class="alignright size-full wp-image-995" src="http://blog.excilys.com/wp-content/uploads/2010/01/arborescence-projet-symfony.png" alt="arborescence-projet-symfony" width="387" height="458" /></p>
<ul>
<li><em>apps</em> : contient les <strong>applications</strong><em> </em>du projet (typiquement le frontend que nous venons de générer)</li>
<li><em>cache</em> : ici sera stocké le cache de l&#8217;application (afin de servir le client plus rapidement en évitant par exemple des parsings de configuration ou des remplissages de templates)</li>
<li><em>config</em> : stocke tous les fichiers de configuration du projet (base de données, projet, rsync etc.)</li>
<li><em>data</em> : c&#8217;est dans ce répertoire que seront mis entre autres les <em>fixtures</em> (des jeux de tests fixes à insérer dans une BDD)</li>
<li><em>lib</em> : contiendra des classes PHP, de préférence globales au projet (en opposition aux classes ayant un scope <em>application</em> ou <em>module</em>, comme nous le verrons par la suite)</li>
<li><em>log</em> : dossier dans lequel les logs applicatifs seront enregistrés</li>
<li><em>plugins</em> : le répertoire des plugins externes (par exemple un plugin de gestion des comptes utilisateurs)</li>
<li><em>test</em> : c&#8217;est là que nous placerons nos tests unitaires / fonctionnels</li>
<li><em>web</em> : c&#8217;est le seul répertoire qui sera accessible depuis l&#8217;extérieur (c&#8217;est-à-dire par des requêtes sur le site), c&#8217;est donc ici que nous mettrons les images, les CSS etc.</li>
<li><em>symfony</em> : ce n&#8217;est ni plus ni moins qu&#8217;un « raccourci » vers le script principal de symfony que nous avons utilisé tout à l&#8217;heure, il nous évitera d&#8217;avoir à retrouver son chemin à chaque fois <img src='http://blog.excilys.com/wp-includes/images/smilies/icon_smile.gif' alt=':-)' class='wp-smiley' /> </li>
</ul>
<p>Afin de facilement accéder à l&#8217;application, j&#8217;ai choisi de créer un VirtualHost apache :</p>
<p><em>/etc/apache2/sites-available/plum.local</em></p>
<pre>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br />2<br />3<br />4<br />5<br />6<br />7<br />8<br />9<br />10<br />11<br />12<br />13<br />14<br />15<br /></div></td><td><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">&lt;VirtualHost *:80&gt;<br />
&nbsp;ServerName www.plum.local<br />
&nbsp;DocumentRoot &quot;/home/bastien/prog/php/plum/web&quot;<br />
&nbsp;DirectoryIndex index.php<br />
&nbsp;&lt;Directory &quot;/home/bastien/prog/php/plum/web&quot;&gt;<br />
&nbsp;AllowOverride All<br />
&nbsp;Allow from All<br />
&nbsp;&lt;/Directory&gt;<br />
<br />
&nbsp;Alias /sf /home/bastien/prog/php/symfony-1.4.1/data/web/sf<br />
&nbsp;&lt;Directory &quot;/home/bastien/prog/php/symfony-1.4.1/data/web/sf&quot;&gt;<br />
&nbsp;AllowOverride All<br />
&nbsp;Allow from All<br />
&nbsp;&lt;/Directory&gt;<br />
&lt;/VirtualHost&gt;</div></td></tr></tbody></table></div>
</pre>
<p>En gros, on redirige www.plum.local (à ajouter dans /etc/hosts) vers le répertoire <strong>web/</strong> de l&#8217;application, et on définit l&#8217;alias <strong>/sf</strong> qui pointe vers le répertoire <strong>data/sf/</strong> du framework. Ce répertoire contient les CSS et images utilisées dans le code généré lors de la création du projet.</p>
<p>Voici à quoi ressemble l&#8217;application générée :</p>
<p><img class="aligncenter size-full wp-image-895" src="http://blog.excilys.com/wp-content/uploads/2009/12/frontend_par_defaut.png" alt="frontend_par_defaut" width="595" height="417" /></p>
<p>Nous n&#8217;avons pour l&#8217;instant généré qu&#8217;un projet vide pointant vers une page par défaut du framework. Nous corrigerons tout cela dans le second article, dans lequel nous définirons le modèle de données de notre application avant de tester la génération de code.</p>
<h3>Bonux : comparaison des commandes grails et symfony</h3>
<p>Génération du projet :</p>
<pre>
<div class="codecolorer-container text default" style="overflow:auto;white-space:nowrap;border:1px solid #9F9F9F;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br />2<br /></div></td><td><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">grails create-app ourapp<br />
symfony generate:project ourapp</div></td></tr></tbody></table></div>
</pre>
<p>(Il faut par la suite générer une ou des application(s) avec symfony pour obtenir un résultat exploitable par le serveur web)</p>
<h3>Accès au code source :</h3>
<p>Le code source de cet article est disponible sur le projet googlecode d&#8217;Excilys à l&#8217;adresse : <a href="http://excilys.googlecode.com/svn/projects/plum/tags/plum_article_1/">http://excilys.googlecode.com/svn/projects/plum/tags/plum_article_1/</a></p>
<p><em>La suite de la série est disponible ! Lire <a href="http://blog.excilys.com/2010/01/15/menez-le-web-a-la-baguette-avec-symfony-2-un-squelette-pour-pas-chair/">l&#8217;épisode 2</a>.</em></p>
<p><!--[if IE]><iframe frameborder="0" allowTransparency="true" class="addtoany_special_service twitter_tweet" src="http://platform.twitter.com/widgets/tweet_button.html?url=http%3A%2F%2Fblog.excilys.com%2F2010%2F01%2F07%2Fmenez-le-web-a-la-baguette-avec-symfony%2F&amp;counturl=http%3A%2F%2Fblog.excilys.com%2F2010%2F01%2F07%2Fmenez-le-web-a-la-baguette-avec-symfony%2F&amp;count=horizontal&amp;text=Menez%20le%20web%20%C3%A0%20la%20baguette%20avec%20symfony" scrolling="no" style="border:none;overflow:hidden;width:130px;height:20px"></iframe><![endif]--><!--[if !IE]><!--><iframe class="addtoany_special_service twitter_tweet" src="http://platform.twitter.com/widgets/tweet_button.html?url=http%3A%2F%2Fblog.excilys.com%2F2010%2F01%2F07%2Fmenez-le-web-a-la-baguette-avec-symfony%2F&amp;counturl=http%3A%2F%2Fblog.excilys.com%2F2010%2F01%2F07%2Fmenez-le-web-a-la-baguette-avec-symfony%2F&amp;count=horizontal&amp;text=Menez%20le%20web%20%C3%A0%20la%20baguette%20avec%20symfony" scrolling="no" style="border:none;overflow:hidden;width:130px;height:20px"></iframe><!--<![endif]--><!--[if IE]><iframe frameborder="0" allowTransparency="true" class="addtoany_special_service google_plusone" src="https://plusone.google.com/u/0/_/%2B1/fastbutton?url=http%3A%2F%2Fblog.excilys.com%2F2010%2F01%2F07%2Fmenez-le-web-a-la-baguette-avec-symfony%2F&amp;size=medium&amp;count=true" scrolling="no" style="border:none;overflow:hidden;width:90px;height:20px"></iframe><![endif]--><!--[if !IE]><!--><iframe class="addtoany_special_service google_plusone" src="https://plusone.google.com/u/0/_/%2B1/fastbutton?url=http%3A%2F%2Fblog.excilys.com%2F2010%2F01%2F07%2Fmenez-le-web-a-la-baguette-avec-symfony%2F&amp;size=medium&amp;count=true" scrolling="no" style="border:none;overflow:hidden;width:90px;height:20px"></iframe><!--<![endif]--><a class="a2a_dd a2a_target addtoany_share_save" href="http://www.addtoany.com/share_save#url=http%3A%2F%2Fblog.excilys.com%2F2010%2F01%2F07%2Fmenez-le-web-a-la-baguette-avec-symfony%2F&amp;title=Menez%20le%20web%20%C3%A0%20la%20baguette%20avec%20symfony" id="wpa2a_6"><img src="http://blog.excilys.com/wp-content/plugins/add-to-any/share_save_120_16.png" width="120" height="16" alt="Share"/></a></p>]]></content:encoded>
			<wfw:commentRss>http://blog.excilys.com/2010/01/07/menez-le-web-a-la-baguette-avec-symfony/feed/</wfw:commentRss>
		<slash:comments>9</slash:comments>
		</item>
	</channel>
</rss>

