<?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"
	>

<channel>
	<title>Redo The Web</title>
	<atom:link href="http://www.redotheweb.com/feed/" rel="self" type="application/rss+xml" />
	<link>http://redotheweb.com</link>
	<description>Aren't you tired of being tied in a web?</description>
	<pubDate>Sat, 30 May 2009 18:57:54 +0000</pubDate>
	<generator>http://wordpress.org/?v=2.6.2</generator>
	<language>en</language>
			<item>
		<title>It&#8217;s Oh So Quiet</title>
		<link>http://redotheweb.com/2008/10/30/its-oh-so-quiet/</link>
		<comments>http://redotheweb.com/2008/10/30/its-oh-so-quiet/#comments</comments>
		<pubDate>Thu, 30 Oct 2008 08:24:48 +0000</pubDate>
		<dc:creator>Francois Zaninotto</dc:creator>
		
		<category><![CDATA[framework]]></category>

		<category><![CDATA[php]]></category>

		<category><![CDATA[plugins]]></category>

		<category><![CDATA[symfony]]></category>

		<guid isPermaLink="false">http://redotheweb.com/?p=92</guid>
		<description><![CDATA[It&#8217;s so quiet in this blog, because it is closed. Forever. No more post will ever be published, you can&#8217;t send any comment, and you can unsubscribe safely from its RSS feed. More than 8,000 unique visitors a month now have time to procrastinate elsewhere.

I have finally admitted that I have better things to do [...]]]></description>
			<content:encoded><![CDATA[<p>It&#8217;s so quiet in this blog, because it is closed. Forever. No more post will ever be published, you can&#8217;t send any comment, and you can unsubscribe safely from its RSS feed. More than 8,000 unique visitors a month now have time to procrastinate elsewhere.</p>

<p>I have finally admitted that I have better things to do in my free time than contributing to the symfony project. So I can say goodbye to all the following, without regret:</p>

<ul>
<li>Writing a book,</li>
<li>Publishing 54 blog posts here and a certain amount in the symfony project blog,</li>
<li>Reading 715 comments here and countless emails in the symfony mailing-lists,</li>
<li>Following the symfony timeline every day, and reviewing the code contributed to the framework core,</li>
<li>Developing, testing, and documenting more than 20 plugins,</li>
<li>Giving a few conferences and trainings,</li>
<li>And helping newcomers find their way in the symfony ecosystem via IRC, chat, and email.</li>
</ul>

<p>You can&#8217;t imagine how much time all that takes. Well, that&#8217;s how much free time I get by leaving symfony completely.</p>

<p>Since summer 2005, my involvement in symfony has been more and more thorough, more and more visceral, and more and more painful. I did my best to push symfony in the direction that I considered to be the right one, but I failed. Symfony used to be simple, well documented, and powerful; today it&#8217;s just powerful. Long forgotten are the days where symfony&#8217;s motto was &#8220;Professional Tools for Lazy Folks&#8221;. I see no future in a project where release dates are never satisfied, where new features are released undocumented, where discussions either never start or die without a generally accepted decision, where the community is tolerated only for its praises, and most of all, where the average user is despised.</p>

<p>If you use any of the symfony plugins that I developed and maintained, and if you want to contribute back to their code, you should contact Kris Wallsmith, the new symfony community manager. He&#8217;s responsible for these plugins now, and will give developer access at his own discretion. As for me, I may use my commit access for modifications regarding the projects I work on, without further notice.</p>

<ul>
<li>DbFinderPlugin</li>
<li>sfAssetsLibraryPlugin</li>
<li>sfControlPanelPlugin</li>
<li>sfFeed2Plugin</li>
<li>sfMediaLibraryPlugin</li>
<li>sfModerationPlugin</li>
<li>sfPagerNavigationPlugin</li>
<li>sfPropelActAsSortableBehaviorPlugin</li>
<li>sfPropelAlternativeSchemaPlugin</li>
<li>sfPropelSpamTagBehaviorPlugin</li>
<li>sfSimpleBlogPlugin</li>
<li>sfSimpleCMSPlugin</li>
<li>sfSimpleForumPlugin</li>
<li>sfSpyPlugin</li>
<li>sfStatsPlugin</li>
<li>sfUFOPlugin</li>
<li>sfUJSPlugin</li>
<li>sfWebBrowserPlugin</li>
</ul>

<p>Redoing the web was an ambitious task. Who knows, I might still manage to do it in the future.</p>
]]></content:encoded>
			<wfw:commentRss>http://redotheweb.com/2008/10/30/its-oh-so-quiet/feed/</wfw:commentRss>
		</item>
		<item>
		<title>The Good, The Bad and the Ugly</title>
		<link>http://redotheweb.com/2008/10/08/the-good-the-bad-and-the-ugly/</link>
		<comments>http://redotheweb.com/2008/10/08/the-good-the-bad-and-the-ugly/#comments</comments>
		<pubDate>Wed, 08 Oct 2008 07:39:31 +0000</pubDate>
		<dc:creator>Francois Zaninotto</dc:creator>
		
		<category><![CDATA[documentation]]></category>

		<category><![CDATA[symfony]]></category>

		<category><![CDATA[usability]]></category>

		<guid isPermaLink="false">http://redotheweb.com/?p=88</guid>
		<description><![CDATA[There is a lot to learn from Fabien Potencier, the creator of the symfony framework. He often comments people's work on other frameworks, but almost never when someone works on his own framework. So his recent reaction about my DDD experiment is rare enough to be thoroughly analyzed, and distilled. Let's look for the very [...]]]></description>
			<content:encoded><![CDATA[<p>There is a lot to learn from Fabien Potencier, the creator of the symfony framework. He often comments people's work on other frameworks, but almost never when someone works on his own framework. So his <a href="http://www.aide-de-camp.org/article/4/en/chapter-10-forms-revisited">recent reaction</a> about my <a href="http://redotheweb.com/2008/09/23/document-driven-development-in-practice-rethinking-sfforms/">DDD experiment</a> is rare enough to be thoroughly analyzed, and distilled. Let's look for the very substance of his latest post.</p>

<h2>The Good</h2>

<p>It's been more than ten months since my original <a href="http://redotheweb.com/2007/12/21/sfforms-the-missing-component/">Forms post</a>, about which Fabien basically told me that I knew nothing about programming (which is true) but nothing else. So I'm very glad that he finally decided to give more feedback about the ideas I suggest. My previous post asked for developers' thoughts about a reworked Chapter 10, and who could better do this than him?</p>

<p>Not only does Fabien talk about my DDD experiment, he actually implemented some features I suggested. Thanks to commits made to the symfony 1.2 branch early this week, building a form object using the sfForm class alone is easier. Added is the ability to define default values for each widget, the ability to iterate on a form object, the ability to directly set a widget or a validator from the from object. I proposed all those changes to make the documentation easier to read (and write), and it seems that they also have an interest for the developers.</p>

<p>Fabien points some mistakes I did, like the 'multiple' validator, which is a bad idea. Since there are two kinds of multiple validators (on the uncleaned value and on the cleaned value), the 'multiple' keyword is not the best choice. I still thing that 'pre' and 'post' validators could get a better name, but that's a detail.</p>

<p>Also, the modified version of my proposed Chapter 10, published as an attachment to Fabien's post, keeps about 90% of the original text unchanged. It seems that he disagrees mostly on some technical details, and didn't touch the order in which things were introduced, nor the length of the Chapter.</p>

<h2>The Bad</h2>

<p>But the idea to define form widgets and validators based on an associative array got no credit to his eyes. It is probably a matter of preference; I introduced this array syntax for two reasons:</p>

<ul>
<li>To avoid introducing too many classes too early in the documentation</li>
<li>To make the YAML form definition syntax completely natural</li>
</ul>

<p>To my eyes, this array syntax has always been a layer on top of the existing syntax; that means that the ability to define custom widgets and validators is still there, and the ability to pass an object instead of an associative array is still there as well. That's how I tried to remove the coding style preference problem: whatever you like more, symfony supports it. You get both simplicity of use and power.</p>

<p>"[The suggested API] is not shorter, it is not easier to understand, and it is more difficult to explain.". Let me respectfully disagree. It is shorter, easier to understand, and easier to explain. Compare what <a href="http://redotheweb.com/2008/09/27/chapter-10-forms/">a single chapter</a> explains and the confusion introduced by an <a href="http://www.symfony-project.org/book/forms/1_1/en/">unfinished and lengthy book</a>. Or better, compare:</p>

<div class="syntax_hilite"><div id="php-2">
<div class="php"><span style="color:#FF9933; font-style:italic;">// in PHP</span><br />
<span style="color:#000000; font-weight:bold;color: grey;">&lt;?php</span><br />
<span style="color:#000000; font-weight:bold;color: grey;">class</span> ContactForm extends sfForm<br />
<span style="color:#00AA00; font-weight:bold;">&#123;</span><br />
&nbsp; protected <a href="http://www.php.net/static"><span style="color:#000066;color: white;">static</span></a> <span style="color:#0088FF;">$subjects</span> = <a href="http://www.php.net/array"><span style="color:#000066;color: white;">array</span></a><span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#FF0000;color: #AF8E99;">'Subject A'</span>, <span style="color:#FF0000;color: #AF8E99;">'Subject B'</span>, <span style="color:#FF0000;color: #AF8E99;">'Subject C'</span><span style="color:#00AA00; font-weight:bold;">&#41;</span>;<br />
&nbsp;<br />
&nbsp; public <span style="color:#000000; font-weight:bold;color: grey;">function</span> configure<span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#00AA00; font-weight:bold;">&#41;</span><br />
&nbsp; <span style="color:#00AA00; font-weight:bold;">&#123;</span><br />
&nbsp; &nbsp; <span style="color:#0088FF;">$this</span>-&gt;<span style="color:#00AA00;">widgetSchema</span>-&gt;<span style="color:#00AA00;">setNameFormat</span><span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#FF0000;color: #AF8E99;">'contact[%s]'</span><span style="color:#00AA00; font-weight:bold;">&#41;</span>;<br />
&nbsp; &nbsp; <span style="color:#0088FF;">$this</span>-&gt;<span style="color:#00AA00;">widgetSchema</span>-&gt;<span style="color:#00AA00;">setIdFormat</span><span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#FF0000;color: #AF8E99;">'my_form_%s'</span><span style="color:#00AA00; font-weight:bold;">&#41;</span>;<br />
&nbsp; &nbsp; <span style="color:#0088FF;">$this</span>-&gt;<span style="color:#00AA00;">setWidgets</span><span style="color:#00AA00; font-weight:bold;">&#40;</span><a href="http://www.php.net/array"><span style="color:#000066;color: white;">array</span></a><span style="color:#00AA00; font-weight:bold;">&#40;</span><br />
&nbsp; &nbsp; &nbsp; <span style="color:#FF0000;color: #AF8E99;">'name'</span>&nbsp; &nbsp; =&gt; <span style="color:#000000; font-weight:bold;color: grey;">new</span> sfWidgetFormInput<span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#00AA00; font-weight:bold;">&#41;</span>,<br />
&nbsp; &nbsp; &nbsp; <span style="color:#FF0000;color: #AF8E99;">'email'</span>&nbsp; &nbsp;=&gt; <span style="color:#000000; font-weight:bold;color: grey;">new</span> sfWidgetFormInput<span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#00AA00; font-weight:bold;">&#41;</span>,<br />
&nbsp; &nbsp; &nbsp; <span style="color:#FF0000;color: #AF8E99;">'subject'</span> =&gt; <span style="color:#000000; font-weight:bold;color: grey;">new</span> sfWidgetFormSelect<span style="color:#00AA00; font-weight:bold;">&#40;</span><a href="http://www.php.net/array"><span style="color:#000066;color: white;">array</span></a><span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#FF0000;color: #AF8E99;">'choices'</span> =&gt; self::<span style="color:#0088FF;">$subjects</span><span style="color:#00AA00; font-weight:bold;">&#41;</span><span style="color:#00AA00; font-weight:bold;">&#41;</span>,<br />
&nbsp; &nbsp; &nbsp; <span style="color:#FF0000;color: #AF8E99;">'message'</span> =&gt; <span style="color:#000000; font-weight:bold;color: grey;">new</span> sfWidgetFormTextarea<span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#00AA00; font-weight:bold;">&#41;</span>,<br />
&nbsp; &nbsp; &nbsp; <span style="color:#FF0000;color: #AF8E99;">'file'</span>&nbsp; &nbsp; =&gt; <span style="color:#000000; font-weight:bold;color: grey;">new</span> sfWidgetFormInputFile<span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#00AA00; font-weight:bold;">&#41;</span>,<br />
&nbsp; &nbsp; <span style="color:#00AA00; font-weight:bold;">&#41;</span><span style="color:#00AA00; font-weight:bold;">&#41;</span>;<br />
&nbsp;<br />
&nbsp; &nbsp; <span style="color:#0088FF;">$this</span>-&gt;<span style="color:#00AA00;">setValidators</span><span style="color:#00AA00; font-weight:bold;">&#40;</span><a href="http://www.php.net/array"><span style="color:#000066;color: white;">array</span></a><span style="color:#00AA00; font-weight:bold;">&#40;</span><br />
&nbsp; &nbsp; &nbsp; <span style="color:#FF0000;color: #AF8E99;">'name'</span>&nbsp; &nbsp; =&gt; <span style="color:#000000; font-weight:bold;color: grey;">new</span> sfValidatorString<span style="color:#00AA00; font-weight:bold;">&#40;</span><a href="http://www.php.net/array"><span style="color:#000066;color: white;">array</span></a><span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#FF0000;color: #AF8E99;">'required'</span> =&gt; <span style="color:#000000; font-weight:bold;color: grey;">false</span><span style="color:#00AA00; font-weight:bold;">&#41;</span><span style="color:#00AA00; font-weight:bold;">&#41;</span>,<br />
&nbsp; &nbsp; &nbsp; <span style="color:#FF0000;color: #AF8E99;">'email'</span>&nbsp; &nbsp;=&gt; <span style="color:#000000; font-weight:bold;color: grey;">new</span> sfValidatorEmail<span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#00AA00; font-weight:bold;">&#41;</span>,<br />
&nbsp; &nbsp; &nbsp; <span style="color:#FF0000;color: #AF8E99;">'subject'</span> =&gt; <span style="color:#000000; font-weight:bold;color: grey;">new</span> sfValidatorChoice<span style="color:#00AA00; font-weight:bold;">&#40;</span><a href="http://www.php.net/array"><span style="color:#000066;color: white;">array</span></a><span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#FF0000;color: #AF8E99;">'choices'</span> =&gt; <a href="http://www.php.net/array_keys"><span style="color:#000066;color: white;">array_keys</span></a><span style="color:#00AA00; font-weight:bold;">&#40;</span>self::<span style="color:#0088FF;">$subjects</span><span style="color:#00AA00; font-weight:bold;">&#41;</span><span style="color:#00AA00; font-weight:bold;">&#41;</span><span style="color:#00AA00; font-weight:bold;">&#41;</span>,<br />
&nbsp; &nbsp; &nbsp; <span style="color:#FF0000;color: #AF8E99;">'message'</span> =&gt; <span style="color:#000000; font-weight:bold;color: grey;">new</span> sfValidatorString<span style="color:#00AA00; font-weight:bold;">&#40;</span><a href="http://www.php.net/array"><span style="color:#000066;color: white;">array</span></a><span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#FF0000;color: #AF8E99;">'min_length'</span> =&gt; <span style="color:#CC66CC;">4</span><span style="color:#00AA00; font-weight:bold;">&#41;</span>, <a href="http://www.php.net/array"><span style="color:#000066;color: white;">array</span></a><span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#FF0000;color: #AF8E99;">'min_length'</span> =&gt; <span style="color:#FF0000;color: #AF8E99;">'Your message is too short'</span><span style="color:#00AA00; font-weight:bold;">&#41;</span><span style="color:#00AA00; font-weight:bold;">&#41;</span>,<br />
&nbsp; &nbsp; &nbsp; <span style="color:#FF0000;color: #AF8E99;">'file'</span>&nbsp; &nbsp; =&gt; <span style="color:#000000; font-weight:bold;color: grey;">new</span> sfValidatorFile<span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#00AA00; font-weight:bold;">&#41;</span>,<br />
&nbsp; &nbsp; <span style="color:#00AA00; font-weight:bold;">&#41;</span><span style="color:#00AA00; font-weight:bold;">&#41;</span>;<br />
&nbsp; &nbsp; <span style="color:#0088FF;">$this</span>-&gt;<span style="color:#00AA00;">setDefault</span><span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#FF0000;color: #AF8E99;">'email'</span>, <span style="color:#FF0000;color: #AF8E99;">'me@example.com'</span><span style="color:#00AA00; font-weight:bold;">&#41;</span>;<br />
&nbsp; <span style="color:#00AA00; font-weight:bold;">&#125;</span><br />
<span style="color:#00AA00; font-weight:bold;">&#125;</span><br />
<span style="color:#000000; font-weight:bold;color: grey;">?&gt;</span></div>
</div></div>

<p><br /></p>

<p>And:</p>

<pre><code># in YAML
&amp;subjects:     [Subject A, Subject B, Subject C]
name_format:  contact[%s]
id_format:    my_form_%s
widgets:
  name:       text
  email:      { type: text, default: me@example.com }
  subject:    { type: select, choices: *subjects }
  message:    textarea
  file:       file
validators:
  name:       { type: string, required: false }
  email:      email
  subject:    { type: choice, choices: *subjects }
  message:    { type: string, min_length: 4, errors: { min_length: Your message is too short } }
  file:       file
</code></pre>

<p>According to Fabien, using associative arrays to make the YAML syntax easier to explain is of no use, since YAML is bad and shall be dropped altogether. "Learn from our mistakes" means that symfony should never had used YAML in the first place, despite the fact that it appealed numerous users to it and that it's only a simplicity layer. That means that the symfony 1.2 admin generator will not be controlled from a YAML file at all - defining form widgets in YAML is an indispensable brick to a YAML syntax for an administration interface. So be prepared to use XML or plain PHP for your database schemas, configuration files, generated modules, etc.</p>

<p>"Learn from our mistakes" also means not using strings to define HTML attributes anymore. I suggested to keep on using the abilities of symfony 1.0 to output clean XHTML attributes from a string looking like <code>'id=contact_subject class=bar'</code>. But that is something else you should forget about. Apparently, this brings no benefit over <code>array('id' =&gt; 'contact_subject', 'class' =&gt; 'bar')</code>. Once again, I can understand it's a matter of preference, but I'm convinced that this kind of syntactic sugar is what appealed many users to symfony in the first place.</p>

<p>In the documentation I wrote, I introduced a way to bind a form object to the request object (<code>$form-&gt;bind($request)</code>). I explained later in the chapter why it's better to do otherwise, but at least it shows that a form can be used without necessarily using the array syntax for widget names. Fabien explains why this is wrong (as I did) and fails to see the interest of introducing the <code>setNameFormat()</code> in the documentation only when it becomes necessary. However, the current symfony book uses this technique several times (think of in Chapter 2 for instance), because it is easier to know why a practice is wrong once you've seen the advantages of the good practice in comparison. His revised version of the Chapter 10 doesn't solve the problem, either.</p>

<p>But honestly, I don't care much about all these points. If if was just for Fabien's remarks on the technical side of things, I'd be more than willing to continue working on a modified Chapter 10 to make it worthy of The Guide.</p>

<h2>The Ugly</h2>

<p>But Fabien is really going to a nasty place with his post.</p>

<p>Does he bring a response to my request for comment? No, he replies to a commenter of my post, who challenged him to give his opinion. He never actually addresses me directly - I'm a <em>persona non grata</em>.</p>

<p>Does he agree on the DDD experiment? Of course not, DDD is the "Biggest problem" according to him (not sure why). How could documentation and teaching influence a developer's design decisions? The forms API is "good enough" as is, and its lack of documentation is not a problem to be solved by modifying the code. If someone (but not me) wants to write something to "help us improve the current documentation", he can still try again.</p>

<p>Is he grateful that I did in a week a work (Chapter 10) that he couldn't do in a year? Not at all. Not a word of thanks, he just feels insulted that someone dared to question parts of his work. What a childish reaction to someone who offers to help widen the symfony adoption.</p>

<p>Does he react in the interest of the community, proposing to use his own version of the Chapter 10 for the current guide? Not even that. He prefers no documentation at all rather than an equivalent to the symfony 1.0 documentation updated for symfony 1.1.</p>

<p>Does he care about the newcomers to symfony, those who don't know the 1.0 API by heart, don't follow the Trac timeline every day, don't read the framework code, and don't accept an <code>UPGRADE</code> text file for a documentation? No. Not a word about them in his post. Only the <a href="http://groups.google.com/group/symfony-devs/msg/041f13b99b8b8b9e">developers who already know good practices of web development</a> can start using symfony. You can no longer learn these practices by learning symfony.</p>

<p>Does he write that he's been implementing some of my ideas? No, that would be giving me too much credit. The goal, here, is to show that I am a bad developer, and nothing else. Well, I'm not even a developer, so why all the hate?</p>

<p>Is he trying to be constructive? No. He writes, in bad faith: "[Francois'] API is so unintuitive that we must explain a lot of things to describe the way it works". God, I thought that I managed to explain in 1 hour what he needs a day to teach in an expensive training, and it's my API that is unintuitive?</p>

<h2>Conclusion</h2>

<p>All in all, Fabien reacts with pride rather than reason. He does as much as he can do discredit my work, while my purpose has always been to help leverage the symfony adoption. He probably dreams that, with a single blog post, I'd leave the symfony community completely, because he finally demonstrated that none of my work is worthy to his eyes.</p>

<p>Too bad, Fabien, you have taken the wrong path. I'll be a pain in your ass for a long time. Count me in to constantly
remind people that symfony is a one-man work, and that this is a very high risk for enterprise projects, given the man.</p>
]]></content:encoded>
			<wfw:commentRss>http://redotheweb.com/2008/10/08/the-good-the-bad-and-the-ugly/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Chapter 10 - Forms</title>
		<link>http://redotheweb.com/2008/09/27/chapter-10-forms/</link>
		<comments>http://redotheweb.com/2008/09/27/chapter-10-forms/#comments</comments>
		<pubDate>Sat, 27 Sep 2008 09:21:25 +0000</pubDate>
		<dc:creator>Francois Zaninotto</dc:creator>
		
		<category><![CDATA[documentation]]></category>

		<category><![CDATA[framework]]></category>

		<category><![CDATA[php]]></category>

		<category><![CDATA[plugins]]></category>

		<category><![CDATA[symfony]]></category>

		<guid isPermaLink="false">http://redotheweb.com/?p=78</guid>
		<description><![CDATA[Dealing with the display of form inputs, the validation of a form submission, and all the particular cases of forms is one of the most complex tasks in web development. Luckily, symfony provides a simple interface to a very powerful form sub-framework, and helps you to design and handle forms of any level of complexity [...]]]></description>
			<content:encoded><![CDATA[<p>Dealing with the display of form inputs, the validation of a form submission, and all the particular cases of forms is one of the most complex tasks in web development. Luckily, symfony provides a simple interface to a very powerful form sub-framework, and helps you to design and handle forms of any level of complexity in just a few lines of code.</p>

<blockquote>
  <p><strong>NOTICE</strong>: This document is the first draft of a methodology experiment explained <a href="http://redotheweb.com/2008/09/23/document-driven-development-in-practice-rethinking-sfforms/">earlier in this blog</a>. It documents the sfForm framework found in symfony 1.1, but with some changes in the API and usage. As such, it describes a library that is not yet written (like that) and cannot be used to learn the usage of the current sfForm implementation. It is quite long, so you might prefer to <a href="http://redotheweb.com/wp-content/uploads/2008/09/forms_easy2.mdown">download the Markdown version</a> and read it offline. Being a first draft, this document is a call for comments, both about its structure and its content. And if you are interested in implementing the differences between what this document describes and what is currently implemented in the symfony framework, please contact me.</p>
</blockquote>

<p><span id="more-78"></span></p>

<h2>Displaying a Form</h2>

<p>A simple contact form featuring a name, an email, a subject and a message fields typically renders as follows:</p>

<p><img src="http://www.symfony-project.org/images/forms_book/en/01_07.png" alt="Contact form" /></p>

<p>In symfony, a form is an object defined in the action and passed to the template. In order to display a form, you must first define the fields it contains - symfony uses the term "widget". The simplest way to do it is to create a new <code>sfForm</code> object in the action method.</p>

<pre><code>[php]
// in modules/foo/actions/actions.class.php
public function executeContact($request)
{
  $this-&gt;form = new sfForm();
  $this-&gt;form-&gt;setWidgets(array(
    'name'    =&gt; 'text',
    'email'   =&gt; array('type' =&gt; 'text', 'default' =&gt; 'me@example.com'),
    'subject' =&gt; array('type' =&gt; 'choice', 'choices' =&gt; array('Subject A', 'Subject B', 'Subject C')),
    'message' =&gt; 'textarea'
  ));
}
</code></pre>

<p><code>sfForm::setWidgets()</code> expects an associative array of widget name / widget definition. The definition must at least contain the widget type. <code>text</code>, <code>choice</code>, and <code>textarea</code> are some one the numerous widget types offered by symfony; you will find a complete list further in this chapter.</p>

<p>For simple widgets, specifying the widget type name is enough. If you need to define more than just the widget type, use an associative array with at least a <code>type</code> key. The previous example shows two widget options you can use: <code>default</code> sets the widget value, and is available for all widgets. <code>choices</code> is an option specific to the <code>choice</code> widget (which renders as a drop-down list): it defines the available options the user can select.</p>

<p>So the <code>foo/contact</code> action defines a form object, and then handles it to the <code>contactSuccess</code> template in a <code>$form</code> variable. The template can use this object to render the various parts of the form in HTML. The simplest way to do it is to call <code>echo $form</code>, and this will render all the fields as form controls with labels. You can also use the form object to generate the form tag:</p>

<pre><code>[php]
// in modules/foo/templates/contactSuccess.php
&lt;?php echo $form-&gt;renderFormTag('foo/contact') ?&gt;
  &lt;table&gt;
    &lt;?php echo $form ?&gt;
    &lt;tr&gt;
      &lt;td colspan="2"&gt;
        &lt;input type="submit" /&gt;
      &lt;/td&gt;
    &lt;/tr&gt;
  &lt;/table&gt;
&lt;/form&gt;
</code></pre>

<p>With the parameters passed to <code>setWidgets()</code>, symfony has enough information to display the form correctly. The resulting HTML renders exactly like the screenshot above, with this underlying code:</p>

<pre><code>[php]
&lt;form action="/frontend_dev.php/foo/contact" method="POST"&gt;
  &lt;table&gt;
    &lt;tr&gt;
      &lt;th&gt;&lt;label for="name"&gt;Name&lt;/label&gt;&lt;/th&gt;
      &lt;td&gt;&lt;input type="text" name="name" id="name" /&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;th&gt;&lt;label for="email"&gt;Email&lt;/label&gt;&lt;/th&gt;
      &lt;td&gt;&lt;input type="text" name="email" id="email" value="me@example.com" /&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;th&gt;&lt;label for="subject"&gt;Subject&lt;/label&gt;&lt;/th&gt;
      &lt;td&gt;
        &lt;select name="subject" id="subject"&gt;
          &lt;option value="0"&gt;Subject A&lt;/option&gt;
          &lt;option value="1"&gt;Subject B&lt;/option&gt;
          &lt;option value="2"&gt;Subject C&lt;/option&gt;
        &lt;/select&gt;
      &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;th&gt;&lt;label for="message"&gt;Message&lt;/label&gt;&lt;/th&gt;
      &lt;td&gt;&lt;textarea rows="4" cols="30" name="message" id="message"&gt;&lt;/textarea&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td colspan="2"&gt;
        &lt;input type="submit" /&gt;
      &lt;/td&gt;
    &lt;/tr&gt;
  &lt;/table&gt;
&lt;/form&gt;
</code></pre>

<p>Each widget results in a table row containing a <code>&lt;label&gt;</code> tag, and a form input tag. Symfony deduces the label name from the widget name by uppercasing it (the <code>subject</code> widget name gives the 'Subject' label ). As for the input tag, it depends on the widget type. Symfony adds an <code>id</code> attribute to each widget, based on its name. Lastly, the rendering of the form is always XHTML-compliant.</p>

<h2>Customizing the Form Display</h2>

<p>Using <code>echo $form</code> is great for prototyping, but you probably want to control exactly the resulting HTML code. The form object contains an array of fields, and calling <code>echo $form</code> actually iterates over the fields and renders them one by one. To get more control, you can iterate over the fields manually, and call the <code>renderRow()</code> on each field. The following listing produces exactly the same HTML code as previously, but the template echoes each field individually:</p>

<pre><code>[php]
// in modules/foo/templates/contactSuccess.php
&lt;?php echo $form-&gt;renderFormTag('foo/contact') ?&gt;
  &lt;table&gt;
    &lt;?php echo $form['name']-&gt;renderRow() ?&gt;
    &lt;?php echo $form['email']-&gt;renderRow() ?&gt;
    &lt;?php echo $form['subject']-&gt;renderRow() ?&gt;
    &lt;?php echo $form['message']-&gt;renderRow() ?&gt;
    &lt;tr&gt;
      &lt;td colspan="2"&gt;
        &lt;input type="submit" /&gt;
      &lt;/td&gt;
    &lt;/tr&gt;
  &lt;/table&gt;
&lt;/form&gt;
</code></pre>

<p>Rendering fields one by one allows you to change the order in which they are displayed, and also to customize their appearance. <code>renderRow()</code> expects an a list of HTML attributes as first argument, so you can define a custom <code>class</code>, <code>id</code>, or JavaScript event handler for instance. This list can be set as an associative array, or as a string with <code>key=value</code> pairs separated by spaces; symfony will understand both syntax. The second argument of <code>renderRow()</code> is an optional label that overrides the one deduced from the widget name. Here is an example of customization for the contact form:</p>

<pre><code>[php]
// in modules/foo/templates/contactSuccess.php
&lt;?php echo $form-&gt;renderFormTag('foo/contact') ?&gt;
  &lt;table&gt;
    &lt;!-- HTML attributes can be set by an associative array... --&gt;
    &lt;?php echo $form['name']-&gt;renderRow(array('size' =&gt; 25, 'class' =&gt; 'foo'), 'Your Name') ?&gt;
    &lt;?php echo $form['email']-&gt;renderRow(array('onclick' =&gt; 'this.value = "";'), 'Your Email') ?&gt;
    &lt;!-- ...or by a string --&gt;
    &lt;?php echo $form['subject']-&gt;renderRow('id=contact_subject class=bar') ?&gt;
    &lt;?php echo $form['message']-&gt;renderRow('size=20x5') ?&gt;
    &lt;tr&gt;
      &lt;td colspan="2"&gt;
        &lt;input type="submit" /&gt;
      &lt;/td&gt;
    &lt;/tr&gt;
  &lt;/table&gt;
&lt;/form&gt;
</code></pre>

<p>But maybe you need to output the label and the input of each field in <code>&lt;li&gt;</code> tags rather than in a <code>&lt;tr&gt;</code> tags. A field "row" is made of a label, an optional error message (added by the validation system, explained later in this chapter), a help text, and a widget (note that the widget can consist of more than one form control). Just as you can output the various fields of a form one by one, you can also render the various parts of a form independently. Instead of using <code>renderRow()</code>, use any of <code>render()</code> (for the widget), <code>renderError()</code>, <code>renderLabel()</code>, and <code>renderHelp()</code>. For instance, if you want to render the whole form with <code>&lt;li&gt;</code> tags, write the template as follows:</p>

<pre><code>[php]
// in modules/foo/templates/contactSuccess.php
&lt;?php echo $form-&gt;renderFormTag('foo/contact') ?&gt;
  &lt;ul&gt;
    &lt;?php foreach($form as $field): ?&gt;
    &lt;li&gt;
      &lt;?php echo $field-&gt;renderLabel() ?&gt;
      &lt;?php echo $field-&gt;render()&gt;
    &lt;/li&gt;
    &lt;?php endforeach; ?&gt;
    &lt;li&gt;
      &lt;input type="submit" /&gt;
    &lt;/li&gt;
  &lt;/ul&gt;
&lt;/form&gt;
</code></pre>

<p>This renders to HTML as follows:</p>

<pre><code>[php]
&lt;form action="/frontend_dev.php/foo/contact" method="POST"&gt;
  &lt;ul&gt;
    &lt;li&gt;
      &lt;label for="name"&gt;Name&lt;/label&gt;
      &lt;input type="text" name="name" id="name" /&gt;
    &lt;/li&gt;
    &lt;li&gt;
      &lt;label for="email"&gt;Email&lt;/label&gt;
      &lt;input type="text" name="email" id="email" /&gt;
    &lt;/li&gt;
    &lt;li&gt;
      &lt;label for="subject"&gt;Subject&lt;/label&gt;
      &lt;select name="subject" id="subject"&gt;
        &lt;option value="0"&gt;Subject A&lt;/option&gt;
        &lt;option value="1"&gt;Subject B&lt;/option&gt;
        &lt;option value="2"&gt;Subject C&lt;/option&gt;
      &lt;/select&gt;
    &lt;/li&gt;
    &lt;li&gt;
      &lt;label for="message"&gt;Message&lt;/label&gt;
      &lt;textarea rows="4" cols="30" name="message" id="message"&gt;&lt;/textarea&gt;
    &lt;/li&gt;
    &lt;li&gt;
      &lt;input type="submit" /&gt;
    &lt;/li&gt;
  &lt;/ul&gt;
&lt;/form&gt;
</code></pre>

<blockquote>
  <p><strong>Tip</strong>: A field row is the representation of all the elements of a form field (label, error message, help text, form input) by a formatter. By default, symfony uses a <code>table</code> formatter, and that's why <code>renderRow()</code> returns a set of <code>&lt;tr&gt;</code>, <code>&lt;th&gt;</code> and <code>&lt;td&gt;</code> tags. Alternatively, you can obtain the same HTML code as above by simply specifying an alternative <code>list</code> formatter for the form, as follows:</p>
</blockquote>

<pre><code>    [php]
    // in modules/foo/templates/contactSuccess.php
    &lt;?php echo $form-&gt;renderFormTag('foo/contact') ?&gt;
      &lt;ul&gt;
        &lt;?php $form-&gt;setFormatterName('list') ?&gt;
        &lt;?php echo $form ?&gt;
        &lt;li&gt;
          &lt;input type="submit" /&gt;
        &lt;/li&gt;
      &lt;/ul&gt;
    &lt;/form&gt;
</code></pre>

<blockquote>
  <p>Check the API documentation of the <code>sfWidgetFormSchemaFormatter</code> class to learn how to create your own formatter.</p>
</blockquote>

<h2>Form Widgets</h2>

<p>There are many available form widgets at your disposal to compose your forms. All widgets accept at least the <code>type</code> parameter and the <code>default</code> parameter.</p>

<p>You can also define the label of a widget, and even its HTML attributes, when you create the form:</p>

<pre><code>[php]
$this-&gt;form = new sfForm();
$this-&gt;form-&gt;setWidgets(array(
  'name'    =&gt; array('type' =&gt; 'text', 'label' =&gt; 'Your Name', 'attributes' =&gt; array('size' =&gt; 25, 'class' =&gt; 'foo')),
  'email'   =&gt; array('type' =&gt; 'text', 'default' =&gt; 'me@example.com', 'label' =&gt; 'Your Email', 'attributes' =&gt; array('onclick' =&gt; 'this.value = "";')),
  'subject' =&gt; array('type' =&gt; 'select', 'choices' =&gt; array('Subject A', 'Subject B', 'Subject C'), 'attributes' =&gt; 'id=contact_subject class=bar'),
  'message' =&gt; array('type' =&gt; 'textarea', 'attributes' =&gt; 'size=20x5')),
));
</code></pre>

<p>Symfony uses these parameters to display the widget, and you can still override them by passing custom parameters to <code>renderRow()</code> in the template.</p>

<blockquote>
  <p><strong>TIP</strong>: As an alternative to calling <code>setWidgets()</code> with an associative array, you can call the <code>setWidget($name, $properties)</code> method several times.</p>
</blockquote>

<h3>Standard Widgets</h3>

<p>Here is a list of available widget types, and how they translate into HTML (via <code>renderRow()</code>):</p>

<pre><code>[php]
// Text input
$form-&gt;setWidget('full_name', array('type' =&gt; 'text', 'default' =&gt; 'John Doe'));
  &lt;label for="full_name"&gt;FullName&lt;/label&gt;
  &lt;input type="text" name="full_name" id="full_name" value="John Doe" /&gt;

// Textarea
$form-&gt;setWidget('address', array('type' =&gt; 'textarea', 'attributes' =&gt; 'size=20x5', 'default' =&gt; 'Enter your address here'));
  &lt;label for="address"&gt;Address&lt;/label&gt;
  &lt;textarea name="address" id="address" cols="20" row="5"&gt;Enter your address here&lt;/textarea&gt;

// Password input
// Note that 'password' type widgets don't take a 'default' parameter for security reasons
$form-&gt;setWidget('pwd', array('type' =&gt; 'password'));
  &lt;label for="pwd"&gt;Pwd&lt;/label&gt;
  &lt;input type="password" name="pwd" id="pwd" /&gt;

// Hidden input
$form-&gt;setWidget('id', array('type' =&gt; 'hidden', 'default' =&gt; 1234));
  &lt;input type="hidden" name="id" id="id" value="1234" /&gt;

// Checkbox
$form-&gt;setWidget('single', array('type' =&gt; 'checkbox', 'value_attribute_value' =&gt; 'single', 'default' =&gt; true));
  &lt;label for="single"&gt;Single&lt;/label&gt;
  &lt;input type="checkbox" name="single" id="single" value="true" checked="checked" /&gt;
</code></pre>

<p>There are more options available for each widget than what is exposed here. Check the widget API documentation for a complete description of what each widget expects and how it renders.</p>

<h3>List Widgets</h3>

<p>Whenever users have to make a choice in a list of values, and whether they can select one or many option in this list, a single widget answers all the needs: the <code>choice</code> widget. According to two optional parameters (<code>multiple</code> and <code>expanded</code>), this widget renders in a different way:</p>

<pre><code>                  | multiple=false       | multiple=true
                  | (default)            |
  ----------------|----------------------|---------------------
  expanded=false  |    Dropdown list     |    Dropdown box
  (default)       |    (`&lt;select&gt;`)      | (`&lt;select multiple&gt;`)
  ----------------|----------------------|----------------------
  expanded=true   | List of Radiobuttons | List of checkboxes
                  |                      |
</code></pre>

<p>The <code>choice</code> widget expects at least a <code>choices</code> parameter with an array of the possible options. +Using an associative array instead of a simple array allows to define both the value and the text of each option. Here is an example of each syntax:</p>

<pre><code>[php]
// Dropdown list (select)
$form-&gt;setWidget('country', array(
  'type'      =&gt; 'choice',
  'choices'   =&gt; array('us' =&gt; 'USA', 'ca' =&gt; 'Canada', 'uk' =&gt; 'UK', 'other'),
  'default'   =&gt; 'uk',
  'add_empty' =&gt; 'Select from the list'
));
// symfony renders the widget in HTML as
&lt;label for="country"&gt;Country&lt;/label&gt;
&lt;select id="country" name="country"&gt;
  &lt;option value=""&gt;Select from the list&lt;/option&gt;
  &lt;option value="us"&gt;USA&lt;/option&gt;
  &lt;option value="ca"&gt;Canada&lt;/option&gt;
  &lt;option value="uk" selected="selected"&gt;UK&lt;/option&gt;
  &lt;option value="0"&gt;other&lt;/option&gt;
&lt;/select&gt;

// Dropdown list with multiple choices
$form-&gt;setWidget('languages', array(
  'type'     =&gt; 'choice',
  'multiple' =&gt; 'true',
  'choices'  =&gt; array('en' =&gt; 'English', 'fr' =&gt; 'French', 'other'),
  'default'  =&gt; array('en', 0)
));
// symfony renders the widget in HTML as
&lt;label for="languages"&gt;Language&lt;/label&gt;
&lt;select id="languages" multiple="multiple" name="languages[]"&gt;
  &lt;option value="en" selected="selected"&gt;English&lt;/option&gt;
  &lt;option value="fr"&gt;French&lt;/option&gt;
  &lt;option value="0" selected="selected"&gt;other&lt;/option&gt;
&lt;/select&gt;

// List of Radio buttons
$form-&gt;setWidget('gender', array(
  'type'     =&gt; 'choice',
  'expanded' =&gt; 'true,
  'choices'  =&gt; array('m' =&gt; 'Male', 'f' =&gt; 'Female'),
  'class'    =&gt; 'gender_list'
));
// symfony renders the widget in HTML as
&lt;label for="gender"&gt;Gender&lt;/label&gt;
&lt;ul class="gender_list"&gt;
  &lt;li&gt;&lt;input type="radio" name="gender" id="gender_m" value="m"&gt;&lt;label for="gender_m"&gt;Male&lt;/label&gt;&lt;/li&gt;
  &lt;li&gt;&lt;input type="radio" name="gender" id="gender_f" value="f"&gt;&lt;label for="gender_f"&gt;Female&lt;/label&gt;&lt;/li&gt;
&lt;/ul&gt;

// List of checkboxes
$form-&gt;setWidget('interests', array(
  'type' =&gt; 'choice',
  'multiple' =&gt; 'true',
  'expanded' =&gt; true,
  'choices' =&gt; array('Programming', 'Other')
));
// symfony renders the widget in HTML as
&lt;label for="interests"&gt;Interests&lt;/label&gt;
&lt;ul class="interests_list"&gt;
  &lt;li&gt;&lt;input type="checkbox" name="interests[]" id="interests_0" value="0"&gt;&lt;label for="interests_0"&gt;Programming&lt;/label&gt;&lt;/li&gt;
  &lt;li&gt;&lt;input type="checkbox" name="interests[]" id="interests_1" value="1"&gt;&lt;label for="interests_1"&gt;Other&lt;/label&gt;&lt;/li&gt;
&lt;/ul&gt;
</code></pre>

<blockquote>
  <p><strong>Tip</strong>: You probably noticed that symfony automatically defines an <code>id</code> attribute for each form input, based on a combination of the name and value of the widget. You can override the <code>id</code> attribute widget by widget, or alternatively set a global rule for the whole form with the <code>setIdFormat()</code> method:</p>
</blockquote>

<pre><code>[php]
// in modules/foo/actions/actions.class.php
$this-&gt;form = new sfForm();
$this-&gt;form-&gt;setIdFormat('my_form_%s');
</code></pre>

<h3>Foreign Key Widgets</h3>

<p>When editing Model objects through a form, a particular list of choices always comes up: the list of objects that can be related to the current one. This happens when models are related by a many-to-one relationship, or a many-to-many. Fortunately, the <code>sfPropelPlugin</code> bundled with symfony provides a <code>propel_choice</code> widget specifically for these cases (and <code>sfDoctrinePlugin</code> offers a similar <code>doctrine_choice</code> widget).</p>

<p>For instance, if a <code>Section</code> has many <code>Articles</code>, you should be able to choose a section among the list of existing ones when editing an article. To do so, an ArticleForm should use the <code>propel_choice</code> widget:</p>

<pre><code>[php]
$articleForm = new sfForm();
$articleForm-&gt;setWidgets(array(
  'id' =&gt; 'hidden',
  'title' =&gt; 'text',
  'section_id' =&gt; array(
    'type'   =&gt; 'propel_choice',
    'model'  =&gt; 'Section',
    'column' =&gt; 'name'
  )
));
</code></pre>

<p>This will display a list of existing sections... provided you defined a <code>__toString()</code> method in the <code>Section</code> model class. That's because symfony first retrieves the available <code>Section</code> objects, and populates a <code>choice</code> widget with them by trying to <code>echo</code> each object. So the <code>Section</code> model should at least feature the following method:</p>

<pre><code>[php]
// in lib/model/Section.php
public function __toString()
{
  return $this-&gt;getName();
}
</code></pre>

<p>The <code>propel_choice</code> widget is an extension of the <code>choice</code> widget, so you can use the <code>multiple</code> option to deal with many-to-many relationships, and the <code>expanded</code> option to change the way the widget is rendered.</p>

<p>If you want to order the list of choices in a special way, or filter it so that is displays only a portion of the available choices, use the <code>criteria</code> option to  pass a <code>Criteria</code> object to the <code>propel_choice</code> widget. Doctrine supports the same kind of customization: you can pass a <code>Doctrine_Query</code> object to the <code>doctrine_choice</code> widget with the <code>query</code> option.</p>

<h3>Date Widgets</h3>

<p>Date and time widgets output a set of dropdown lists, populated with the available values for the day, month, year, hour or minute.</p>

<pre><code>[php]
// Date
$form-&gt;setWidget('dob', array(
  'type' =&gt; 'date',
  'label' =&gt; 'Date of birth',
  'default' =&gt; '01/01/1950',  // can be a timestamp or a string understandable by strtotime()
  'years' =&gt; array(1950, 1951, .., 1990)
));
// symfony renders the widget in HTML as
&lt;label for="dob"&gt;Date of birth&lt;/label&gt;
&lt;select id="dob_month" name="dob[month]"&gt;
  &lt;option value=""/&gt;
  &lt;option selected="selected" value="1"&gt;01&lt;/option&gt;
  &lt;option value="2"&gt;02&lt;/option&gt;
  ...
  &lt;option value="12"&gt;12&lt;/option&gt;
&lt;/select&gt; /
&lt;select id="dob_day" name="dob[day]"&gt;
  &lt;option value=""/&gt;
  &lt;option selected="selected" value="1"&gt;01&lt;/option&gt;
  &lt;option value="2"&gt;02&lt;/option&gt;
  ...
  &lt;option value="31"&gt;31&lt;/option&gt;
&lt;/select&gt; /
&lt;select id="dob_year" name="dob[year]"&gt;
  &lt;option value=""/&gt;
  &lt;option selected="selected" value="1950"&gt;1950&lt;/option&gt;
  &lt;option value="1951"&gt;1951&lt;/option&gt;
  ...
  &lt;option value="1990"&gt;1990&lt;/option&gt;
&lt;/select&gt;

// Time
$form-&gt;setWidget('start', array('type' =&gt; 'time', 'default' =&gt; '12:00'));
// symfony renders the widget in HTML as
&lt;label for="start"&gt;Start&lt;/label&gt;
&lt;select id="start_hour" name="start[hour]"&gt;
  &lt;option value=""/&gt;
  &lt;option value="0"&gt;00&lt;/option&gt;
  ...
  &lt;option selected="selected" value="12"&gt;12&lt;/option&gt;
  ...
  &lt;option value="23"&gt;23&lt;/option&gt;
&lt;/select&gt; :
&lt;select id="start_minute" name="start[minute]"&gt;
  &lt;option value=""/&gt;
  &lt;option selected="selected" value="0"&gt;00&lt;/option&gt;
  &lt;option value="1"&gt;01&lt;/option&gt;
  ...
  &lt;option value="59"&gt;59&lt;/option&gt;
&lt;/select&gt;

// Date and time
$form-&gt;setWidget('end', array('type' =&gt; 'datetime', 'default' =&gt; '01/01/2008 12:00'))
// symfony renders the widget in HTML as 5 dropdown lists for month, day, year, hour, minute
</code></pre>

<p>Of course, you can customize the date <code>format</code>, to display it in European style instead of International style (<code>%day%/%month%/%year%</code> instead of <code>%month%/%day%/%year%</code>), you can switch to 2x12 hours per day instead of 24 hours, you can define custom values for the first option of each dropdown box, and you can define limits to the possible values. Once again, check the API documentation for more details about the options of the date and time widgets.</p>

<p>Date widgets are a good example of the power of widgets in symfony. A widget is not a simple form input. It can be a combination of several inputs, that symfony can render and read from in a transparent way.</p>

<h3>I18n Widgets</h3>

<p>In multilingual applications, dates must be displayed in a format according to the user culture (see Chapter 13 for details about culture and localization). To facilitate this localization in forms, symfony provides an <code>i18n_date</code> widget, which expects a user <code>culture</code> to decide of the date formatting parameters. You can also specify a <code>month_format</code> to have the month drop-down display month names (in the user language) instead of numbers.</p>

<pre><code>[php]
// Date
$form-&gt;setWidget('dob', array(
  'type'         =&gt; 'i18n_date',
  'culture'      =&gt; $this-&gt;getUser()-&gt;getCulture(),
  'month_format' =&gt; 'name',   // Use any of 'name' (default), 'short_name', and 'number' 
  'label'        =&gt; 'Date of birth',
  'default'      =&gt; '01/01/1950',
  'years'        =&gt; array(1950, 1951, .., 1990)
));
// For an English-speaking user, symfony renders the widget in HTML as
&lt;label for="dob"&gt;Date of birth&lt;/label&gt;
&lt;select id="dob_month" name="dob[month]"&gt;
  &lt;option value=""/&gt;
  &lt;option selected="selected" value="1"&gt;January&lt;/option&gt;
  &lt;option value="2"&gt;February&lt;/option&gt;
  ...
  &lt;option value="12"&gt;December&lt;/option&gt;
&lt;/select&gt; /
&lt;select id="dob_day" name="dob[day]"&gt;...&lt;/select&gt; /
&lt;select id="dob_year" name="dob[year]"&gt;...&lt;/select&gt;
// For an French-speaking user, symfony renders the widget in HTML as
&lt;label for="dob"&gt;Date of birth&lt;/label&gt;
&lt;select id="dob_day" name="dob[day]"&gt;...&lt;/select&gt; /
&lt;select id="dob_month" name="dob[month]"&gt;
  &lt;option value=""/&gt;
  &lt;option selected="selected" value="1"&gt;Janvier&lt;/option&gt;
  &lt;option value="2"&gt;Février&lt;/option&gt;
  ...
  &lt;option value="12"&gt;Décembre&lt;/option&gt;
&lt;/select&gt; /
&lt;select id="dob_year" name="dob[year]"&gt;...&lt;/select&gt;
</code></pre>

<p>Similar widgets exist for time (<code>i18n_time</code>), and datetime (<code>i18n_datetime</code>).</p>

<p>There are two dropdown lists that occur in many forms and that also rely on the user culture: country and language selectors. Symfony provides two widgets especially for this purpose. You don't need to define the <code>choices</code> in these widgets, as symfony will populate them with the list of countries and languages in the language of the user (provided the user speaks any of the 250 referenced languages in symfony).</p>

<pre><code>// Country list
$form-&gt;setWidget('country', array(
  'type'      =&gt; 'i18n_country_choice',
  'culture'   =&gt; $this-&gt;getUser()-&gt;getCulture(),
  'default'   =&gt; 'UK'
));
// For an English-speaking user, symfony renders the widget in HTML as
&lt;label for="country"&gt;Country&lt;/label&gt;
&lt;select id="country" name="country"&gt;
  &lt;option value=""/&gt;
  &lt;option value="AD"&gt;Andorra&lt;/option&gt;
  &lt;option value="AE"&gt;United Arab Emirates&lt;/option&gt;
  ...
  &lt;option value="ZWD"&gt;Zimbabwe&lt;/option&gt;
&lt;/select&gt;

// Language list
$form-&gt;setWidget('language', array(
  'type'      =&gt; 'i18n_language_choice',
  'culture'   =&gt; $this-&gt;getUser()-&gt;getCulture(),
  'languages' =&gt; array('en', 'fr', 'de'),  // optional restriced list of languages
  'default'   =&gt; 'en'
));
// For an English-speaking user, symfony renders the widget in HTML as
&lt;label for="language"&gt;Language&lt;/label&gt;
&lt;select id="language" name="language"&gt;
  &lt;option value=""/&gt;
  &lt;option value="de"&gt;German&lt;/option&gt;
  &lt;option value="en" selected="selected"&gt;English&lt;/option&gt;
  &lt;option value="fr"&gt;French&lt;/option&gt;
&lt;/select&gt;
</code></pre>

<h3>File Widgets</h3>

<p>Dealing with file input tags is not more complicated than dealing with other wigets:</p>

<pre><code>[php]
// Input file
$form-&gt;setWidget('picture', array('type' =&gt; 'file'));
// symfony renders the widget in HTML as
&lt;label for="picture"&gt;Picture&lt;/label&gt;
&lt;input id="picture" type="file" name="picture"/&gt;
// Whenever a form has a file widget, renderFormTag() outputs a &lt;form&gt; tag with the multipart option

// Editable input file
$form-&gt;setWidget('picture', array('type' =&gt; 'file_editable', 'default' =&gt; '/images/foo.png'));
// symfony renders the widget in HTML as a file input tag, together with a preview of the current file
</code></pre>

<blockquote>
  <p><strong>TIP</strong>: Third-party plugins provide many additional widgets. You can easily find a rich text editor widget, a calendar widget, or other "rich UI" widgets for various JavaScript libraries. Check the <a href="http://www.symfony-project.org/plugins/">Plugins repository</a> for more details.</p>
</blockquote>

<h2>Handling a Form Submission</h2>

<p>When users fill a form and submit it, the web application server needs to rerieve the data from the request and do some stuff with it. The <code>sfForm</code> class provides all the necessary methods to do that in a couple lines of code.</p>

<h3>Simple Form Handling</h3>

<p>Since widgets output as regular HTML form fields, getting their value in the action that handles the form submission is as easy as checking the related request parameters. Symfony recommends the use of the same action to display and to handle the submission of a form. For the example contact form, the action could look like this:</p>

<pre><code>[php]
// in modules/foo/actions/actions.class.php
public function executeContact($request)
{
  // Define the form
  $this-&gt;form = new sfForm();
  $this-&gt;form-&gt;setWidgets(array(
    'name'    =&gt; 'text',
    'email'   =&gt; array('type' =&gt; 'text', 'default' =&gt; 'me@example.com'),
    'subject' =&gt; array('type' =&gt; 'select', 'choices' =&gt; array('Subject A', 'Subject B', 'Subject C')),
    'message' =&gt; 'textarea'),
  ));

  // Deal with the request
  if(!$request-&gt;isMethod('post'))
  {
    // Handle the form submission
    $name = $request-&gt;getParameter('name');
    // Do stuff
    // ...
    $this-&gt;redirect('foo/bar');
  }
}
</code></pre>

<p>If the request method is 'GET', this action will terminate over a <code>sfView::SUCCESS</code> and therefore render the <code>contactSuccess</code> template to display the form. If the request method is 'POST', then the action handles the form submission and redirects to another action. For this to work, the <code>&lt;form&gt;</code> target action must be the same as the one displaying it. That explains why the previous examples used <code>foo/contact</code> as a form target:</p>

<pre><code>[php]
// in modules/foo/templates/contactSuccess.php
&lt;?php echo $form-&gt;renderFormTag('foo/contact') ?&gt;
...
</code></pre>

<h3>Form Handling With Data Validation</h3>

<p>In practice, there is much more to form submission handling than just getting the values entered by the user. For most form submissions, the application controller needs to:</p>

<ol>
<li>Check that the data is conform to a set of predefined rules (required fields, format of the email, etc.)</li>
<li>Optionally transform some of the input data to make it understandable (trim whitespaces, convert dates to PHP format, etc)</li>
<li>If the data is not valid, display the form again, with error messages where applicable</li>
<li>If the data is correct, do some stuff and then redirect to another action</li>
</ol>

<p>Symfony provides an automatic way to validate the submitted data against a set of predefined rules. First, define a set of validators for each field. Second, when the form is submitted, "bind" the form object with the request object (i.e., retrieve the values submitted by the user and put them in the form). Lastly, ask the form to check that the data is valid. The following example shows how to check that the value retrieved from the <code>email</code> widget is, indeed, an email address, and to check that the <code>message</code> has a minimum size of 4 characters:</p>

<pre><code>[php]
// in modules/foo/actions/actions.class.php
public function executeContact($request)
{
  // Define the form
  $this-&gt;form = new sfForm();
  $this-&gt;form-&gt;setWidgets(array(
    'name'    =&gt; 'text',
    'email'   =&gt; array('type' =&gt; 'text', 'default' =&gt; 'me@example.com'),
    'subject' =&gt; array('type' =&gt; 'select', 'choices' =&gt; array('Subject A', 'Subject B', 'Subject C')),
    'message' =&gt; 'textarea'),
  ));
  $this-&gt;form-&gt;setValidators(array(
    'email'   =&gt; 'email',
    'message' =&gt; array('type' =&gt; 'string', 'min_length' =&gt; 4))
  ));

  // Deal with the request
  if(!$request-&gt;isMethod('post'))
  {
    $this-&gt;form-&gt;bind($request);
    if ($this-&gt;form-&gt;isValid())
    {
      // Handle the form submission
      $name = $request-&gt;getParameter('name');
      // Do stuff
      // ...
      $this-&gt;redirect('foo/bar');
    }
  }
}
</code></pre>

<p><code>setValidators()</code> uses a similar syntax to the <code>setWidgets()</code> method: for each form field, you can set either a validator name (like <code>email</code>), or an array of parameters with a required <code>type</code> parameter. <code>email</code> and <code>string</code> are two of the numerous symfony validators, listed further in this chapter. Naturally, <code>sfForm</code> also provide a <code>setValidator()</code> method to add validators one by one.</p>

<p>To put the request data into the form and bind them together, use the <code>sfForm::bind()</code> method. A form must be bound with some data to check its validity.</p>

<p><code>isValid()</code> checks that all the registered validators pass. If this is the case, <code>isValid()</code> returns <code>true</code>, and the action can proceed with the form submission. If the form is not valid, then the action terminates with the default <code>sfView::SUCCESS</code> and displays the form again. But the form isn't just displayed with the default values, as the first time it was displayed. The form inputs show up filled with the data previously entered by the user, and error messages appear wherever the validators didn't pass.</p>

<p><img src="http://www.symfony-project.org/images/forms_book/en/02_01.png" alt="Invalid form" /></p>

<blockquote>
  <p><strong>TIP</strong>: The validation process doesn't stop when the form meets an invalid field. <code>isValid()</code> processes the whole form data and checks all the fields for errors, to avoid displaying new error messages as the user corrects its mistakes and submits the form again.</p>
</blockquote>

<h3>Using Clean Form Data</h3>

<p>In the previous listing, the form receives the whole request object during the binding process. The problem is that the request contains more than just the form data. It also contains headers, cookies, parameters passed as GET arguments, and all this might pollute the binding process. A good practice is to pass only the form data to the <code>bind()</code> method.</p>

<p>Fortunately, symfony offers a way to name all form inputs using an array syntax. Define the name attribute format width the <code>setNameFormat()</code> method in the action when you define the form, as follows:</p>

<pre><code>[php]
// in modules/foo/actions/actions.class.php
// Define the form
$this-&gt;form-&gt;setNameFormat('contact[%s]');
</code></pre>

<p>That way, all the generated form inputs render with a name like <code>form[WIDGET_NAME]</code> instead of just <code>WIDGET_NAME</code>:</p>

<pre><code>[php]
&lt;label for="contact_name"&gt;Name&lt;/label&gt;
&lt;input type="text" name="contact[name]" id="contact_name" /&gt;
...
&lt;label for="contact_email"&gt;Email&lt;/label&gt;
&lt;input type="text" name="contact[email]" id="contact_email" value="me@example.com" /&gt;
...
&lt;label for="contact_subject"&gt;Subject&lt;/label&gt;
&lt;select name="contact[subject]" id="contact_subject"&gt;
  &lt;option value="0"&gt;Subject A&lt;/option&gt;
  &lt;option value="1"&gt;Subject B&lt;/option&gt;
  &lt;option value="2"&gt;Subject C&lt;/option&gt;
&lt;/select&gt;
...
&lt;label for="contact_message"&gt;Message&lt;/label&gt;
&lt;textarea rows="4" cols="30" name="contact[message]" id="contact_message"&gt;&lt;/textarea&gt;
</code></pre>

<p>The action can now retrieve the <code>contact</code> request parameter into a single variable. This variable contains an array of all the data entered by the user in the form:</p>

<pre><code>[php]
// in modules/foo/actions/actions.class.php
// Deal with the request
if(!$request-&gt;isMethod('post'))
{
  $this-&gt;form-&gt;bind($request-&gt;getParameter('contact'));
  if ($this-&gt;form-&gt;isValid())
  {
    // Handle the form submission
    $contact = $this-&gt;form-&gt;getValues();
    $name = $contact['name'];
    // Do stuff
    // ...
    $this-&gt;redirect('foo/bar');
  }
}
</code></pre>

<p>When the <code>bind()</code> method receives an array of parameters rather than a request object, symfony automatically enables an special check on the bound fields, to avoid injection of additional fields on the client side. This security feature will make the form validation fail if the array of <code>contact</code> parameters contains a field that is not in the original form definition.</p>

<p>You will notice one more difference in the action code above with the one written previously. The action uses the array of values passed by the form object (<code>$form-&gt;getValues()</code>) rather than the one from the request. This is because the validators have the ability to filter the input and clean it, so it's always better to rely on the data retrieved from the form object (by way of <code>getValues()</code>) than the data from the request. And for composite fields (like date widgets), the data returned by <code>getValues()</code> is already recomposed into the original names:</p>

<pre><code>[php]
// When submitted, the form controls of a 'date' widget...
&lt;label for="contact_dob"&gt;Date of birth&lt;/label&gt;
&lt;select id="contact_dob_month" name="contact[dob][month]"&gt;...&lt;/select&gt; /
&lt;select id="contact_dob_day" name="contact[dob][day]"&gt;...&lt;/select&gt; /
&lt;select id="contact_dob_year" name="contact[dob][year]"&gt;...&lt;/select&gt;
// ...result in three request parameters in the action
$contact = $request-&gt;getParameter('contact');
$month = $contact['dob']['month'];
$day = $contact['dob']['day'];
$year = $contact['dob']['year'];
$dateOfBirth = mktime(0, 0, 0, $month, $day, $year);
// But if you use getValues(), you can retrieve directly a correct date
$contact = $this-&gt;form-&gt;getValues();
$dateOfBirth = $contact['dob'];
</code></pre>

<p>Make it a habit to:</p>

<ul>
<li>Always use array syntax for your form fields (using <code>$this-&gt;form-&gt;setNameFormat('contact[%s]')</code>)</li>
<li>Always use the clean post-validation version of the user’s form input (using <code>$this-&gt;form-&gt;getValues()</code>).</li>
</ul>

<p>Where do the error messages shown in the screenshot above come from? You know that a widget is made of four components, and the error message is one of them. In fact, the default (table) formatter renders a field row as follows:</p>

<pre><code>[php]
&lt;?php if($field-&gt;hasError()): ?&gt;
&lt;tr&gt;
  &lt;td colspan="2"&gt;
    &lt;?php echo $field-&gt;renderError() ?&gt;           // List of errors
  &lt;/td&gt;
&lt;/tr&gt;
&lt;?php endif; ?&gt;
&lt;tr&gt;
  &lt;th&gt;&lt;?php echo $field-&gt;renderLabel() ?&gt;&lt;/th&gt;    // Label
  &lt;td&gt;
    &lt;?php echo $field-&gt;render() ?&gt;                // Widget
    &lt;?php if ($field-&gt;hasHelp()): ?&gt;
    &lt;br /&gt;&lt;?php echo $field-&gt;renderHelp() ?&gt;      // Help
    &lt;?php endif; ?&gt;
  &lt;/td&gt;
&lt;/tr&gt;
</code></pre>

<p>Using any of the methods above, you can customize where and how the error messages appear for each field. In addition, you can display a global error message on top of the form is if is not valid:</p>

<pre><code>[php]
&lt;?php if ($form-&gt;hasErrors()): ?&gt;
  The form has some errors you need to fix.
&lt;?php endif; ?&gt;
</code></pre>

<h3>Customizing Validators</h3>

<p>In a form, all fields that do not carry a validator are optional, and all the fields that carry a validator are required. If you need to make an optional field required, just add an empty <code>string</code> validator on this field. On the other hand, if you need to set a field that already has a validator as optional, pass the <code>optional</code> parameter to the validator. For instance, the following listing shows how to make the <code>name</code> field required and the <code>email</code> field optional:</p>

<pre><code>[php]
$this-&gt;form-&gt;setValidators(array(
  'name'    =&gt; 'string',    // simple string validator, to make the name required
  'email'   =&gt; array('type' =&gt; 'email', 'optional' =&gt; true),
  'message' =&gt; array('type' =&gt; 'string', 'min_length' =&gt; 4)
));
</code></pre>

<p>You can apply more than one validator on a field. For instance, you may want to check that the <code>email</code> field satisfies both the <code>email</code> and the <code>string</code> validators with a minimum size of 4 characters. In such a case, use the <code>and</code> validator to combine two validators, and put the two <code>email</code> and <code>string</code> validators into its <code>validators</code>:</p>

<pre><code>[php]
$this-&gt;form-&gt;setValidators(array(
  'name'    =&gt; 'string',    // simple string validator, to make the name required
  'email'   =&gt; array('type' =&gt; 'and', 'optional' =&gt; true, 'validators' =&gt; array(
    array('type' =&gt; 'email'),
    array('type' =&gt; 'string', 'min_length' =&gt; 4)
  )
  'message' =&gt; array('type' =&gt; 'string', 'min_length' =&gt; 4)
));
</code></pre>

<p>If both validators are valid, then the <code>email</code> field is declared valid. Similarly, you can use the <code>or</code> validator to combine several validators. If one of the validators is valid, then the field is declared valid.</p>

<p>Each invalid validator results into an error message in the field. These error messages are in English but use the symfony internationalization helpers; if your project uses other languages, you can easily translate the error messages with an i18n dictionary. Alternatively, every validator provides an <code>errors</code> parameter to customize its error messages. Each validator has at least two error messages: the <code>required</code> message and the <code>invalid</code> message. Some validators can display error messages for a different purpose, and will always support the overriding of the error messages through their <code>errors</code> parameter:</p>

<pre><code>[php]
[php]
// in modules/foo/actions/actions.class.php
$this-&gt;form-&gt;setValidators(array(
  'name'    =&gt; 'string',    // simple string validator, to make the name required
  'email'   =&gt; array('type' =&gt; 'email', 'errors' =&gt; array(
    'required'   =&gt; 'Please provide an email',
    'invalid'    =&gt; 'Please provide a valid email address (me@example.com)'
    )),
  'message' =&gt; array('type' =&gt; 'string', 'min_length' =&gt; 4, 'errors' =&gt; array(
    'required'   =&gt; 'Please provide a message',
    'min_length' =&gt; 'Please provide a longer message (at least 4 characters)'
    ))
));
</code></pre>

<p>Naturally, these custom messages will render in the templates through i18n helpers, so any multilingual application can also translate custom error messages in a dictionary (see Chapter 13 for details).</p>

<h3>Applying a Validator To Several Fields</h3>

<p>The syntax used above to define validators on a form does not allow to validate that two fields are valid <em>together</em>. For instance, in a registration form, there are often two <code>password</code> fields that must match, otherwise the registration is refused. Each password field is not valid on its own, it is only valid when associated with the other field.</p>

<p>That's why <code>setValidators()</code> accepts a <code>multiple</code> key to set the validators that work on several values. A typical registration form definition would look like this:</p>

<pre><code>[php]
// in modules/foo/actions/actions.class.php
// Define the form
$this-&gt;form = new sfForm();
$this-&gt;form-&gt;setWidgets(array(
  'login'     =&gt; 'text',
  'password1' =&gt; 'text'
  'password2' =&gt; 'text'
);
$this-&gt;form-&gt;setValidators(array(
  'login'     =&gt; 'string', // login is required
  'password1' =&gt; 'string'  // password1 is required
  'password2' =&gt; 'string'  // password2 is required
  'multiple'  =&gt; array('type' =&gt; 'compare', 'left' =&gt; 'password1', 'right' =&gt; 'password2')
));
</code></pre>

<p>The <code>compare</code> validator is a special multiple validator that receives all the form input values and can pick up two of them for comparison. Naturally, you can define more than one <code>multiple</code> validator by using the <code>and</code> and the <code>or</code> validators.</p>

<h2>Validators</h2>

<p>Symfony offers quite a lot of validators. Remember that each validator expects at least a <code>type</code> parameter, and accepts an <code>errors</code> parameter where you can at least customize the <code>required</code> and <code>invalid</code> error messages.</p>

<pre><code>[php]
// String validator
$form-&gt;setValidator('message', array(
  'type'       =&gt; 'string',
  'min_length' =&gt; 4,
  'max_length' =&gt; 50,
  'errors' =&gt; array(
    'min_length' =&gt; 'Please post a longer message',
    'max_length' =&gt; 'Please be less verbose',
  )
));

// Number validator
$form-&gt;setValidator('age', array(
  'type' =&gt; 'number',    // use the 'int' type instead if you want to force integer values
  'min'  =&gt; 18,
  'max'  =&gt; 99.99,
  'errors' =&gt; array(
    'min' =&gt; 'You must be 18 or more to use this service',
    'max' =&gt; 'Are you kidding me? People over 30 can\'t even use the Internet',
  )
)):

// Email validator
$form-&gt;setValidator('email', array('type' =&gt; 'email'));

// URL validator
$form-&gt;setValidator('website', array('type' =&gt; 'url'));

// Regular expression validator
$form-&gt;setValidator('IP', array(
  'type'    =&gt; 'regex',
  'pattern' =&gt; '^[0-9]{3}\.[0-9]{3}\.[0-9]{2}\.[0-9]{3}$'
));
</code></pre>

<p>Even though some form controls (like dropdown lists, checkboxes, radiobutton groups) restrict the possible choices, a malicious user can always try to hack your forms by manipulating the page with Firebug or submitting a query with a scripting language. Consequently, you should also validate fields that only accept a limited array of values:</p>

<pre><code>[php]
// Boolean validator
$form-&gt;setValidator('has_signed_terms_of_service', array('type' =&gt; 'boolean'));

// Choice validator (to restrict values in a list)
$form-&gt;setValidator('subject', array(
  'type'    =&gt; 'choice',
  'choices' =&gt; array('Subject A', 'Subject B', 'Subject C')
));

// Multiple choice validator
$form-&gt;setValidator('languages', array(
  'type'    =&gt; 'choice_many',
  'choices' =&gt; array('en' =&gt; 'English', 'fr' =&gt; 'French', 'other')
));
</code></pre>

<p>I18n choice validators exist for country lists (<code>i18n_country_choice</code>) and language lists (<code>i18_language_choice</code>). These validators require a <code>culture</code> parameter, and accept a restricted list of <code>countries</code> and <code>languages</code> if you want to limit the possible options.</p>

<p>The <code>choice</code> validators are often used to validate <code>choice</code> widgets. And since you can use the <code>choice</code> widget for foreign key columns, so symfony also provides a validator to check that the foreign key value exists in the foreign table:</p>

<pre><code>[php]
// Propel choice validator
$form-&gt;setValidator('section_id', array(
  'type'   =&gt; 'propel_choice',
  'model'  =&gt; 'Section',
  'column' =&gt; 'name'
));
</code></pre>

<p>Another useful Model-related validator is the <code>unique</code> validator, which checks that a new value entered via a form doesn't conflict with an existing value in a database column with a unique index. For instance, two users cannot have the same <code>login</code>, so when editing a <code>User</code> object with a form, you must add a <code>unique</code> validator on this column:</p>

<pre><code>[php]
// Propel unique validator
$form-&gt;setValidator('nickname', array(
  'type'   =&gt; 'propel_unique',
  'model'  =&gt; 'User', 
  'column' =&gt; 'login'
));
</code></pre>

<p>To make your forms even more secure and avoid <a href="http://en.wikipedia.org/wiki/Cross-site_request_forgery">Cross-Site Request Forgery</a> attacks, you can customize the <code>CSRF_Token</code> validator, which is enabled by default. Customizing this validator prevents the form from being submitted if it wasn't displayed before:</p>

<pre><code>[php]
// CSRF_Token validator - set the 'token' parameter to a random string that nobody knows
$form-&gt;setValidator('_csrf_token', array(
  'type'  =&gt; 'CSRF_token',
  'token' =&gt; 'flkd445rvvrGV34G'
));
</code></pre>

<blockquote>
  <p><strong>TIP</strong>: You can set the CSRF token once for the whole site in the <code>settings.yml</code> file:</p>
</blockquote>

<pre><code>[yml]
# in apps/myapp/config/settings.yml
all:
  .settings:
    # Form security secret (CSRF protection)
    csrf_secret:       ##CSRF_SECRET##     # Unique secret to enable CSRF protection or false to disable
</code></pre>

<p>The "multiple" validators work with the whole form, rather than a single input. You must register these validators under the <code>multiple</code> key. Here is a list of available multiple validators:</p>

<pre><code>[php]
// compare validator - compare two fields 
$form-&gt;setValidator('multiple', array(
  'type'       =&gt; 'compare',
  'left'       =&gt; 'password1',
  'comparator' =&gt; '=',    // default value
  'right'      =&gt; 'password2'
));

// Extra field validator: looks for fields in the request not present in the form
// This validator is enabled by default as soon as you bind a form to an array rather than a request
$form-&gt;setValidator('multiple', array(
  'type'                =&gt; 'extra_field',
  'allow_extra_fields'  =&gt; false,
  'filter_extra_fields' =&gt; true
));

// Unique validator for models with a composite unique key
$form-&gt;setValidator('nickname', array(
  'type'   =&gt; 'propel_unique_composite',
  'model'  =&gt; 'User',
  'column' =&gt; array('login', 'email')
));
</code></pre>

<h2>Alternative Ways to Use a Form</h2>

<h3>Using YAML for Fast Form Definition</h3>

<p>With all the widget options, validators and form parameters, the contact form definition written in the actions class looks quite messy:</p>

<pre><code>[php]
// in modules/foo/actions/actions.class.php
// Define the form
$this-&gt;form = new sfForm();
$this-&gt;form-&gt;setNameFormat('contact[%s]');
$this-&gt;form-&gt;setIdFormat('my_form_%s');
$this-&gt;form-&gt;setWidgets(array(
  'name'    =&gt; 'text',
  'email'   =&gt; array('type' =&gt; 'text', 'default' =&gt; 'me@example.com'),
  'subject' =&gt; array('type' =&gt; 'select', 'choices' =&gt; array('Subject A', 'Subject B', 'Subject C')),
  'message' =&gt; 'textarea'
));
$this-&gt;form-&gt;setValidators(array(
  'name'    =&gt; 'string', 
  'email'   =&gt; array('type' =&gt; 'email', 'errors' =&gt; array(
    'required'   =&gt; 'please provide an email',
    'invalid'    =&gt;  'please provide a valid email address (me@example.com)'
  )),
  'message' =&gt; array('type' =&gt; 'string', 'min_length' =&gt; 4, 'errors' =&gt; array(
    'min_length' =&gt; 'Please provide a longer message (at least 4 characters)'
  ))
));
</code></pre>

<p>Not only is this set of associative arrays difficult to read, it is also error-prone. But there is a more readable equivalent to PHP associative arrays that you already know in symfony: YAML. And <code>sfForm</code> offers a <code>fromYaml()</code> method to populate a form object with the parameters found in a YAML form definition:</p>

<pre><code>[php]
// in modules/foo/actions/actions.class.php
// Define the form
$this-&gt;form = new sfForm();
$this-&gt;form-&gt;fromYaml(dirname(__FILE__).'../config/contact_form.yml');

[yml]
# in modules/foo/config/contact_form.yml
name_format:  contact[%s]
id_format:    my_form_%s
widgets:
  name:       text
  email:      { type: text, default: me@example.com }
  subject:    { type: select, choices: [Subject A, Subject B, Subject C] }
  message:    textarea
validators:
  name:       string
  email:
    type:     email
    errors:
      required: please provide an email
      invalid:  please provide a valid email address (me@example.com)
  message:
    type:     string
    min_length: 4
    errors:   { min_length: Please provide a longer message (at least 4 characters) }
</code></pre>

<p>The syntax of the YAML form definition follows the <code>sfForm</code> object API. The first time symfony parses this file, it transforms the definition into an associative array equivalent and stores it in the cache. So using the YAML form definition is both fast to write and fast to execute.</p>

<h3>Altering a Form Object</h3>

<p>When you use YAML form definition, the form is defined outside the action. That makes dynamic default value assignment quite difficult. That's why the form object supports a <code>setDefault($field, $default)</code> and a <code>setDefaults($array)</code> methods that you can call in the action after importing the form from the YAML file:</p>

<pre><code>[php]
// in modules/foo/actions/actions.class.php
// Define the form
$this-&gt;form = new sfForm();
$this-&gt;form-&gt;fromYaml(dirname(__FILE__).'../config/contact_form.yml');
$this-&gt;form-&gt;setDefaults(array('email' =&gt; 'me@example.com'));
</code></pre>

<p>You can also override existing widget or validator settings by calling <code>setWidget()</code> or <code>setValidator()</code> on an existing field name. Symfony will merge the new parameters with the existing ones - unless you change the <code>type</code> parameter, in which case the previous settings are erased. Also, if you set an empty widget or validator on a field, it erases the current widget/validator.</p>

<p>However, widgets and validators are objects in symfony, and offer a clean API to modify their properties. Your best option to alter a form defined in a YAML file is to use this API, which is pretty self-explanatory:</p>

<pre><code>[php]
// in modules/foo/actions/actions.class.php
// Define the form
$this-&gt;form = new sfForm();
$this-&gt;form-&gt;fromYaml(dirname(__FILE__).'../config/contact_form.yml');

// Add an empty option to the list of choices of a 'choice' widget
$form-&gt;getWidget('language')-&gt;setOption('add_empty', 'Please choose a language);
// Add a 'gender' list of options widget
$form-&gt;setWidget('gender', array('type' =&gt; 'choice', 'expanded' =&gt; true, 'choices' =&gt; array('m' =&gt; 'Male', 'f' =&gt; 'Female'), 'class' =&gt; 'gender_list'));
// Change the HTML attributes of the 'subject' widget
$form-&gt;getWidget('subject')-&gt;setAttribute('disabled', 'disabled');
// Remove the 'subject' widget
$form-&gt;removeWidget('subject');
// Note: removing a widget will also remove the related validators
// Change the 'email' widget type to 'textarea'
$form-&gt;setWidget('email', array('type' =&gt; 'textarea'));

// Change the 'min_length' error in the 'message' validator
$form-&gt;getValidator('message')-&gt;setError('min_length', 'Message too short');
// Make the 'name' field required
$form-&gt;setValidator('name', 'string');
// Remove the 'email' validator
$form-&gt;removeValidator('email');
</code></pre>

<h3>Widget and Validator classes</h3>

<p>When you set a widget or a validator, instead of passing an array of parameters, you can pass directly a widget object or a validator object. Widget class names are prefixed by 'sfWidgetForm', and validator classes are prefixed by 'sfValidator'.</p>

<pre><code>[php]
$choices = array('m' =&gt; 'Male', 'f' =&gt; 'Female');
$form-&gt;setWidget('gender', new sfWidgetFormSelectRadio(array('choices' =&gt; $choices, 'class' =&gt; 'gender_list')));
// Same as
$form-&gt;setWidget('gender', array('type' =&gt; 'choice', 'choices' =&gt; $choices, 'class' =&gt; 'gender_list'));

$form-&gt;addValidator('name', new sfValidatorString());
// Same as
$form-&gt;addValidator('name', 'string');
</code></pre>

<p>Symfony uses this alternative syntax internally. This syntax allows you to use your own widgets and validators. A custom widget is simply a class extending <code>sfWidgetForm</code>, and providing a <code>configure()</code> and a <code>render()</code> methods. Check the code of existing widget classes for a deeper understanding of the widgets system. The next listing exposes the code of the <code>text</code> widget to illustrate the widget structure:</p>

<pre><code>[php]
class sfWidgetFormInput extends sfWidgetForm
{
  /**
   * Configures the current widget.
   * This method allows each widget to add options or HTML attributes during widget creation.
   * Available options:
   *  * type: The widget type (text by default)
   *
   * @param array $options     An array of options
   * @param array $attributes  An array of default HTML attributes
   * @see sfWidgetForm
   */
  protected function configure($options = array(), $attributes = array())
  {
    $this-&gt;addOption('type', 'text');
    $this-&gt;setOption('is_hidden', false);
  }

  /**
   * Renders the widget as HTML
   *
   * @param  string $name        The element name
   * @param  string $value       The value displayed in this widget
   * @param  array  $attributes  An array of HTML attributes to be merged with the default HTML attributes
   * @param  array  $errors      An array of errors for the field
   * @return string An HTML tag string
   * @see sfWidgetForm
   */
  public function render($name, $value = null, $attributes = array(), $errors = array())
  {
    return $this-&gt;renderTag('input', array_merge(
      array('type' =&gt; $this-&gt;getOption('type'), 'name' =&gt; $name, 'value' =&gt; $value), 
      $attributes
    ));
  }
}
</code></pre>

<p>A validator class must extend <code>sfValidatorBase</code> and provide a <code>configure()</code> and a <code>doClean()</code> methods. Why <code>doClean()</code> and not <code>validate()</code>? Because validators do two things: they check that the input fulfills a set of rules, and they optionally clean the input (for instance by forcing the type, trimming, converting date strings to timestamp, etc.). So the <code>doClean()</code> method must return the cleaned input, or throw a <code>sfValidatorError</code> exception if the input doesn't satisfy any of the validator rules. Here is an illustration of this concept, with the code of the <code>int</code> validator.</p>

<pre><code>[php]
class sfValidatorInteger extends sfValidatorBase
{
  /**
   * Configures the current validator.
   * This method allows each validator to add options and error messages during validator creation.
   * Available options:
   *  * max: The maximum value allowed
   *  * min: The minimum value allowed
   * Available error codes:
   *  * max
   *  * min
   *
   * @param array $options   An array of options
   * @param array $messages  An array of error messages
   * @see sfValidatorBase
   */
  protected function configure($options = array(), $messages = array())
  {
    $this-&gt;addOption('min');
    $this-&gt;addOption('max');
    $this-&gt;addMessage('max', '"%value%" must be less than %max%.');
    $this-&gt;addMessage('min', '"%value%" must be greater than %min%.');
    $this-&gt;setMessage('invalid', '"%value%" is not an integer.');
  }

  /**
   * Cleans the input value.
   *
   * @param  mixed $value  The input value
   * @return mixed The cleaned value
   * @throws sfValidatorError
   */
  protected function doClean($value)
  {
    $clean = intval($value);
    if (strval($clean) != $value)
    {
      throw new sfValidatorError($this, 'invalid', array('value' =&gt; $value));
    }
    if ($this-&gt;hasOption('max') &amp;&amp; $clean &gt; $this-&gt;getOption('max'))
    {
      throw new sfValidatorError($this, 'max', array('value' =&gt; $value, 'max' =&gt; $this-&gt;getOption('max')));
    }
    if ($this-&gt;hasOption('min') &amp;&amp; $clean &lt; $this-&gt;getOption('min'))
    {
      throw new sfValidatorError($this, 'min', array('value' =&gt; $value, 'min' =&gt; $this-&gt;getOption('min')));
    }

    return $clean;
  }
}
</code></pre>

<p>Check the symfony API documentation for widget and validator classes names and syntax.</p>

<h3>Form Classes</h3>

<p>In a similar fashion, if you want to reuse a form object at several parts of your project, you should create a form class with the same properties and instantiate it in all the actions using it. For instance, here is how you could create a class for the contact form:</p>

<pre><code>[php]
// in lib/form/ContactForm.class.php
class ContactForm extends sfForm
{
  protected static $subjects = array('Subject A', 'Subject B', 'Subject C');

  public function configure()
  {
    $this-&gt;setNameFormat('contact[%s]');
    $this-&gt;setIdFormat('my_form_%s');
    $this-&gt;setWidgets(array(
      'name'    =&gt; 'text',
      'email'   =&gt; 'text',
      'subject' =&gt; (array('type' =&gt; 'choice', 'choices' =&gt; self::$subjects)),
      'message' =&gt; 'textarea',
    ));
    $this-&gt;setValidators(array(
      'name'    =&gt; 'string', 
      'email'   =&gt; array('type' =&gt; 'email', 'errors' =&gt; array(
        'required'   =&gt; 'please provide an email',
        'invalid'    =&gt;  'please provide a valid email address (me@example.com)'
      )),
      'message' =&gt; array('type' =&gt; 'string', 'min_length' =&gt; 4, 'errors' =&gt; array(
        'min_length' =&gt; 'Please provide a longer message (at least 4 characters)'
      ))
    ));
    $this-&gt;setDefaults(array(
      'email' =&gt; 'me@example.com'
    ));
  }
}
</code></pre>

<p>Now getting a contact form object in the action has never been easier:</p>

<pre><code>[php]
// in modules/foo/actions/actions.class.php
// Define the form
$this-&gt;form = new ContactForm();
</code></pre>

<p>You can alter this form the same way as the form created from the YAML definition.</p>

<h2>Forms Based on a Model</h2>

<p>Forms are the primary way to edit database records in web applications. And most forms in symfony applications allow the editing of a Model object. But the information necessary to build a form to edit a model already exists: it is in the schema. So symfony provides a form generator for model objects, that makes the creation of model-editing forms a snap.</p>

<blockquote>
  <p><strong>Note</strong>: Similar features to the ones described below exist for Doctrine.</p>
</blockquote>

<h3>Generating Model Forms</h3>

<p>Symfony can deduce the widget types and the validators to use for a model editing form, based on the schema. Take the following schema, for instance:</p>

<pre><code>[yml]
// config/schema.yml
propel:
  article:
    id:           ~
    title:        { type: varchar(255), required: true }
    slug:         { type: varchar(255), required: true, index: unique }
    content:      longvarchar
    is_published: { type: boolean, required: true }
    author_id:    { type: integer, required: true, foreignTable: author, foreignReference: id, OnDelete: cascade }
    created_at:   ~

  author:
    id:           ~
    first_name:   varchar(20)
    last_name:    varchar(20)
    email:        { type: varchar(255), required: true, index: unique }
    active:       boolean
</code></pre>

<p>A form to edit an <code>Article</code> object should use a hidden widget for the <code>id</code>, a text widget for the <code>title</code>, a string validator for the <code>title</code>, etc. Symfony generates the form for you, provided that you call the <code>propel:build-forms</code> task:</p>

<pre><code>$ php symfony propel:build-forms
</code></pre>

<p>For each table in the model, this command creates two files under the <code>lib/form/</code> directory: a <code>BaseXXXForm</code> class, overridden each time you call the <code>propel:build-forms</code> task, and an empty <code>XXXForm</code> class, extending the previous one. It is the same system as the Propel model classes generation.</p>

<p>The generated <code>lib/form/base/BaseArticleForm.class.php</code> contains the translation into widgets and validators of the columns defined for the <code>article</code> table in the <code>schema.yml</code>:</p>

<pre><code>[php]
class BaseArticleForm extends BaseFormPropel
{
  public function setup()
  {
    $this-&gt;setWidgets(array(
      'id'           =&gt; 'hidden',
      'title'        =&gt; 'text',
      'slug'         =&gt; 'text',
      'content'      =&gt; 'textarea',
      'is_published' =&gt; 'checkbox',
      'author_id'    =&gt; array('type' =&gt; 'choice', 'model' =&gt; 'Author', 'add_empty' =&gt; false),
      'created_at'   =&gt; 'datetime',
    ));
    $this-&gt;setValidators(array(
      'id'           =&gt; array('type' =&gt; 'propel_choice', 'model' =&gt; 'Article', 'column' =&gt; 'id', 'required' =&gt; false),
      'title'        =&gt; array('type' =&gt; 'string', 'max_length' =&gt; 255),
      'slug'         =&gt; array('type' =&gt; 'string', 'max_length' =&gt; 255),
      'content'      =&gt; array('type' =&gt; 'string', 'required' =&gt; false),
      'is_published' =&gt; 'boolean',
      'author_id'    =&gt; array('type' =&gt; 'propel_choice', 'model' =&gt; 'Author', 'column' =&gt; 'id'),
      'created_at'   =&gt; array('type' =&gt; 'datetime', 'required' =&gt; false),
    ));
    $this-&gt;setMultipleValidator(
      array('type' =&gt; 'propel_unique', 'model' =&gt; 'Article', 'column' =&gt; array('slug'))
    );
    $this-&gt;setNameFormat('article[%s]');
    parent::setup();
  }

  public function getModelName()
  {
    return 'Article';
  }
}
</code></pre>

<p>Notice that even though the <code>id</code> column is an Integer, symfony checks that the submitted id exists in the table using a <code>propel_choice</code> validator. The form generator always sets the strongest validation rules, to ensure the cleanest data in the database.</p>

<h3>Using Model Forms</h3>

<p>You can customize generated form classes for your entire project by adding code to the empty <code>ArticleForm::configure()</code> method. If your modifications concern only one action, create an instance of the form in the action, and use the public form methods to override the default form settings in the action, as explained previously in this chapter.</p>

<p>Here is an example of model form handling in an action. In this form, the <code>slug</code> validator is modified to make it optional, and the <code>author_id</code> widget is customized to display only a subset of authors - the 'active' ones.</p>

<pre><code>[php]
// in modules/foo/actions/actions.class.php
public function executeEditArticle($request)
{
  $this-&gt;form = new ArticleForm();
  $c = new Criteria();
  $c-&gt;add(AuthorPeer::ACTIVE, true);
  $this-&gt;form-&gt;getWidget('author_id')-&gt;setOption('criteria', $c);
  $this-&gt;form-&gt;getValidator('slug')-&gt;setOption('required', false);

  $this-&gt;form-&gt;setObject(ArticlePeer::retrieveByPk($request-&gt;getParameter('id')));

  if ($request-&gt;isMethod('post'))
  {
    $this-&gt;form-&gt;bind($request-&gt;getParameter('article'));
    if ($this-&gt;form-&gt;isValid())
    {
      $article = $this-&gt;form-&gt;save();

      $this-&gt;redirect('article/edit?id='.$author-&gt;getId());
    }
  }
}
</code></pre>

<p>Instead of setting default values through an associative array (using <code>setDefaults()</code>), Model forms use a Model object to initialize the widget values (with <code>setObject()</code>). To display an empty form, just pass a new Model object.</p>

<p>The form submission handling is greatly simplified by the fact that the form object has an embedded Model object. Calling <code>$this-&gt;form-&gt;bind($request-&gt;getParameter('article'))</code> updates the embedded <code>Article</code> object with the request values (the data submitted by the user). Then, calling <code>$this-&gt;form-&gt;save()</code> on a valid form triggers the <code>save()</code> method on the <code>Article</code> object, as well as on the related objects if they exist.</p>

<blockquote>
  <p><strong>TIP</strong>: The action code required to deal with a form is pretty much always the same, but that's not a reason to copy it from one module to the other. Symfony provides a module generator that creates the whole action and template code to manipulate a Model object through symfony forms. Check the Chapter 14 for more details about the CRUD generator.</p>
</blockquote>

<h2>Conclusion</h2>

<p>The symfony form component is an entire framework on its own. It facilitates the display of forms in the view through widgets, it facilitates validation and handling of forms in the controller through validators, and it facilitates the edition of Model objects through Model forms. Although designed with a clear MVC separation, the form sub-framework is always easy to use. Most of the time, code generation will reduce your custom form code to a few lines.</p>

<p>There is much more in symfony form classes than what this chapter exposes. In fact, there is an <a href="http://www.symfony-project.org/book/forms/1_1/en/">entire book</a> describing all its features through usage examples. And if the form framework itself doesn't provide the widget or the validator you need, it is designed in such an extensible way that you will only need to write a single class to get exactly what you need.</p>
]]></content:encoded>
			<wfw:commentRss>http://redotheweb.com/2008/09/27/chapter-10-forms/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Sorting By Custom Column in the Symfony Admin Generator</title>
		<link>http://redotheweb.com/2008/09/25/sorting-by-custom-column-in-the-symfony-admin-generator/</link>
		<comments>http://redotheweb.com/2008/09/25/sorting-by-custom-column-in-the-symfony-admin-generator/#comments</comments>
		<pubDate>Thu, 25 Sep 2008 13:55:03 +0000</pubDate>
		<dc:creator>Francois Zaninotto</dc:creator>
		
		<category><![CDATA[DbFinder]]></category>

		<category><![CDATA[plugins]]></category>

		<category><![CDATA[symfony]]></category>

		<guid isPermaLink="false">http://redotheweb.com/?p=69</guid>
		<description><![CDATA[Did you ever wish you could sort by a partial column in the admin generator? Using DbFinder and a few lines of code, it is now possible.

The symfony admin generator allows you to select which properties of a model you want to display. You can include foreign key fields, or even a partial field to [...]]]></description>
			<content:encoded><![CDATA[<p>Did you ever wish you could sort by a partial column in the admin generator? Using DbFinder and a few lines of code, it is now possible.</p>

<p>The symfony admin generator allows you to select which properties of a model you want to display. You can include foreign key fields, or even a partial field to display pretty much everything you want in the <code>list</code> view. The following example uses this ability to display the name of article authors, based on the fact that the <code>Article</code> model has a many to one relationship to the <code>User</code> model:</p>

<pre><code># in mymodule/config/generator.yml
generator:
  class:          sfPropelAdminGenerator
  param:
    model_class:  Blog
    theme:        default

list:
  display:        [=title, user, category, _nb_posts, created_at]
  fields: 
    user:         { name: Author }
</code></pre>

<p>This generator configuration includes a partial field that counts the number of blog posts for each blog:</p>

<div class="syntax_hilite"><div id="php-7">
<div class="php"><span style="color:#FF9933; font-style:italic;">// in mymodule/templates/_nb_posts.php</span><br />
<span style="color:#000000; font-weight:bold;color: grey;">&lt;?php</span> <a href="http://www.php.net/echo"><span style="color:#000066;color: white;">echo</span></a> <span style="color:#0088FF;">$blog</span>-&gt;<span style="color:#00AA00;">countBlogPosts</span><span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#00AA00; font-weight:bold;">&#41;</span> <span style="color:#000000; font-weight:bold;color: grey;">?&gt;</span></div>
</div></div>

<p><br /></p>

<p>The problem is that only the "True" fields (that is, the ones that correspond to a column in the main table) are sortable. The result is that, with the following example, only the <code>title</code> column is sortable.</p>

<p><a href="http://redotheweb.com/wp-content/uploads/2008/09/2008-09-25_1437.png"><img src="http://redotheweb.com/wp-content/uploads/2008/09/2008-09-25_1437.png" alt="" title="Custom column offer no ordering" width="500" height="143" class="alignnone size-full wp-image-72" /></a></p>

<p>With symfony alone, there is no way to make the other columns sortable except overriding the whole <code>_list_th_tabular.php</code> partial in your module, overriding the <code>addSortCriteria()</code> method in the action, and losing the ability to add or remove columns in the future.</p>

<p>Enters <a href="http://redotheweb.com/category/dbfinder/">DbFinderPlugin</a>. You probably know from this blog that DbFinder offers a very powerful and yet simple way to replace Propel Criteria queries. What you might not know is that the DbFinder plugin bundles a full admin generator theme. It has the exact same features and syntax as the standard symfony admin generator, but it is entirely written with <code>DbFinder</code> queries. And to make this generator theme very usable, it includes the <code>batch_actions</code> extension from symfony 1.1 (that's what allows to display the checkboxes on the left side of the list to perform an action on several records at a time), and the ability to sort by any type of column.</p>

<p>To use the DbFinder admin generator, no need to switch your entire project to DbFinder. Just install the plugin, edit the <code>generator.yml</code> of one of your generated modules, and change the <code>class</code> property from <code>sfPropelAdminGenerator</code> (or <code>sfDoctrineAdminGenerator</code>, if you use Doctrine) to <code>DbFinderAdminGenerator</code>. Refresh the page in your browser, and you should normally see no change. That's good news: despite the fact that all the generator code has been rewritten to work with DbFinder instead of Propel, it is completely backwards compatible.</p>

<p>And once a generated module uses DbFinder, you gain access to the new <code>sort_method</code> option for custom fields:</p>

<pre><code># in mymodule/config/generator.yml
generator:
  class:          DbFinderAdminGenerator
  param:
    model_class:  Blog
    theme:        default

list:
  display:        [=title, user, category, _nb_posts, created_at]
  fields: 
    user:         { name: Author, sort_method: orderByUsername }
    category:     { sort_method: orderByCategory }
    nb_posts:     { sort_method: orderByNbPosts }
</code></pre>

<p>Refresh the <code>list</code> view, and voila, the column headers are now clickable.</p>

<p><a href="http://redotheweb.com/wp-content/uploads/2008/09/2008-09-25_1424.png"><img src="http://redotheweb.com/wp-content/uploads/2008/09/2008-09-25_1424.png" alt="" title="Custom columns are now clickable" width="500" height="137" class="alignnone size-full wp-image-74" /></a></p>

<p>Don't click the new links yet: you've defined three methods for custom ordering, and you still have to write them. To do so, you need to create a <code>BlogFinder</code>, which is a finder class specific to the <code>Blog</code> model class. So create a <code>lib/model/BlogFinder.php</code> class with the following content:</p>

<div class="syntax_hilite"><div id="php-8">
<div class="php"><span style="color:#FF9933; font-style:italic;">// in lib/model/BlogFinder.php</span><br />
<span style="color:#000000; font-weight:bold;color: grey;">class</span> BlogFinder extends DbFinder<br />
<span style="color:#00AA00; font-weight:bold;">&#123;</span><br />
&nbsp; protected <span style="color:#0088FF;">$class</span> = <span style="color:#FF0000;color: #AF8E99;">'Blog'</span>;<br />
&nbsp; <br />
&nbsp; public <span style="color:#000000; font-weight:bold;color: grey;">function</span> orderByUsername<span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#0088FF;">$order</span> = <span style="color:#FF0000;color: #AF8E99;">'asc'</span><span style="color:#00AA00; font-weight:bold;">&#41;</span><br />
&nbsp; <span style="color:#00AA00; font-weight:bold;">&#123;</span><br />
&nbsp; &nbsp; <span style="color:#616100;color: grey;">return</span> <span style="color:#0088FF;">$this</span>-&gt;<span style="color:#00AA00;">orderBy</span><span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#FF0000;color: #AF8E99;">'User.Name'</span>, <span style="color:#0088FF;">$order</span><span style="color:#00AA00; font-weight:bold;">&#41;</span>;<br />
&nbsp; <span style="color:#00AA00; font-weight:bold;">&#125;</span><br />
<br />
&nbsp; public <span style="color:#000000; font-weight:bold;color: grey;">function</span> orderByCategory<span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#0088FF;">$order</span> = <span style="color:#FF0000;color: #AF8E99;">'asc'</span><span style="color:#00AA00; font-weight:bold;">&#41;</span><br />
&nbsp; <span style="color:#00AA00; font-weight:bold;">&#123;</span><br />
&nbsp; &nbsp; <span style="color:#616100;color: grey;">return</span> <span style="color:#0088FF;">$this</span>-&gt;<span style="color:#00AA00;">orderBy</span><span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#FF0000;color: #AF8E99;">'Category.Name'</span>, <span style="color:#0088FF;">$order</span><span style="color:#00AA00; font-weight:bold;">&#41;</span>;<br />
&nbsp; <span style="color:#00AA00; font-weight:bold;">&#125;</span><br />
&nbsp; <br />
&nbsp; public <span style="color:#000000; font-weight:bold;color: grey;">function</span> orderByNbPosts<span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#0088FF;">$order</span> = <span style="color:#FF0000;color: #AF8E99;">'asc'</span><span style="color:#00AA00; font-weight:bold;">&#41;</span><br />
&nbsp; <span style="color:#00AA00; font-weight:bold;">&#123;</span><br />
&nbsp; &nbsp; <span style="color:#616100;color: grey;">return</span> <span style="color:#0088FF;">$this</span>-&gt;<br />
&nbsp; &nbsp; &nbsp; <span style="color:#00AA00;">leftJoin</span><span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#FF0000;color: #AF8E99;">'BlogPost'</span><span style="color:#00AA00; font-weight:bold;">&#41;</span>-&gt;<br />
&nbsp; &nbsp; &nbsp; <span style="color:#00AA00;">groupBy</span><span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#FF0000;color: #AF8E99;">'Blog.Id'</span><span style="color:#00AA00; font-weight:bold;">&#41;</span>-&gt;<br />
&nbsp; &nbsp; &nbsp; <span style="color:#00AA00;">withColumn</span><span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#FF0000;color: #AF8E99;">'COUNT(BlogPost.Id)'</span>, <span style="color:#FF0000;color: #AF8E99;">'nbPosts'</span><span style="color:#00AA00; font-weight:bold;">&#41;</span>-&gt;<br />
&nbsp; &nbsp; &nbsp; <span style="color:#00AA00;">orderBy</span><span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#FF0000;color: #AF8E99;">'nbPosts'</span>, <span style="color:#0088FF;">$order</span><span style="color:#00AA00; font-weight:bold;">&#41;</span>;<br />
&nbsp; <span style="color:#00AA00; font-weight:bold;">&#125;</span><br />
<span style="color:#00AA00; font-weight:bold;">&#125;</span></div>
</div></div>

<p><br /></p>

<p>The finder is smart enough to guess the relationship between the <code>Blog</code> and the <code>User</code> model, as well as the relationship with the <code>Category</code> model, because the YAML schema defines foreign keys between the related tables.</p>

<p>Clear the cache (to allow the autoloading to find the new finder class), refresh your list, and enjoy fully sortable columns.</p>

<p>To finish, here is a small trick to drastically improve your backend performance. Every time the <code>_nb_posts</code> partial is called (and that's once per row in the list), symfony issues a <code>COUNT</code> query. That means that the current configuration will run n+1 queries, n being the number of results per page (typically 20). That's pretty bad for performance. What if you could hydrate an additional column in the main query and use this column in the <code>_nb_posts</code> partial? With DbFinder, that's very easy. Just add a <code>finder_methods</code> setting to your <code>list</code> configuration, as follows:</p>

<pre><code># in mymodule/config/generator.yml
list:
  display:        [=title, user, category, _nb_posts, created_at]
  fields: 
    user:         { name: Author, sort_method: orderByUsername }
    category:     { sort_method: orderByCategory }
    nb_posts:     { sort_method: orderByNbPosts }
    finder_methods: [withNbPosts]
</code></pre>

<p>Symfony executes all the methods defined in the <code>finder_methods</code> before displaying the list. It allows you to define a default ordering, to filter out some records, or, like here, to add custom column to the main query.</p>

<p>Now it's time to create this <code>BlogFinder::withNbPosts()</code> method. Since it contains part of the code of <code>orderByNbPosts()</code>, and that the finder generator executes sort methods at the end of the action, you can reduce the <code>orderByNbPosts()</code> code accordingly:</p>

<div class="syntax_hilite"><div id="php-9">
<div class="php"><span style="color:#FF9933; font-style:italic;">// in lib/model/BlogFinder.php</span><br />
public <span style="color:#000000; font-weight:bold;color: grey;">function</span> withNbPosts<span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#0088FF;">$order</span> = <span style="color:#FF0000;color: #AF8E99;">'asc'</span><span style="color:#00AA00; font-weight:bold;">&#41;</span><br />
<span style="color:#00AA00; font-weight:bold;">&#123;</span><br />
&nbsp; <span style="color:#616100;color: grey;">return</span> <span style="color:#0088FF;">$this</span>-&gt;<br />
&nbsp; &nbsp; <span style="color:#00AA00;">leftJoin</span><span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#FF0000;color: #AF8E99;">'BlogPost'</span><span style="color:#00AA00; font-weight:bold;">&#41;</span>-&gt;<br />
&nbsp; &nbsp; <span style="color:#00AA00;">groupBy</span><span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#FF0000;color: #AF8E99;">'Blog.Id'</span><span style="color:#00AA00; font-weight:bold;">&#41;</span>-&gt;<br />
&nbsp; &nbsp; <span style="color:#00AA00;">withColumn</span><span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#FF0000;color: #AF8E99;">'COUNT(BlogPost.Id)'</span>, <span style="color:#FF0000;color: #AF8E99;">'nbPosts'</span><span style="color:#00AA00; font-weight:bold;">&#41;</span>;<br />
<span style="color:#00AA00; font-weight:bold;">&#125;</span><br />
<br />
public <span style="color:#000000; font-weight:bold;color: grey;">function</span> orderByNbPosts<span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#0088FF;">$order</span> = <span style="color:#FF0000;color: #AF8E99;">'asc'</span><span style="color:#00AA00; font-weight:bold;">&#41;</span><br />
<span style="color:#00AA00; font-weight:bold;">&#123;</span><br />
&nbsp; <span style="color:#616100;color: grey;">return</span> <span style="color:#0088FF;">$this</span>-&gt;<span style="color:#00AA00;">orderBy</span><span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#FF0000;color: #AF8E99;">'nbPosts'</span>, <span style="color:#0088FF;">$order</span><span style="color:#00AA00; font-weight:bold;">&#41;</span>;<br />
<span style="color:#00AA00; font-weight:bold;">&#125;</span></div>
</div></div>

<p><br /></p>

<p>Now the main list query includes the call for the calculated <code>nbPosts</code> column, and you can change the <code>_nb_posts</code> partial to use it:</p>

<div class="syntax_hilite"><div id="php-10">
<div class="php"><span style="color:#FF9933; font-style:italic;">// in mymodule/templates/_nb_posts.php</span><br />
<span style="color:#000000; font-weight:bold;color: grey;">&lt;?php</span> <a href="http://www.php.net/echo"><span style="color:#000066;color: white;">echo</span></a> <span style="color:#0088FF;">$blog</span>-&gt;<span style="color:#00AA00;">getColumn</span><span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#FF0000;color: #AF8E99;">'nbPosts'</span><span style="color:#00AA00; font-weight:bold;">&#41;</span> <span style="color:#000000; font-weight:bold;color: grey;">?&gt;</span></div>
</div></div>

<p><br /></p>

<p>Refresh the list view: Ta-da, the result is the same, but using a single query instead of n+1.</p>

<p>So the DbFinder generator offers the same features as the current symfony 1.1 generator, except more. Don't wait until you upgrade your project to symfony 1.2 to enhance your generated modules. Read the <a href="http://trac.symfony-project.org/browser/plugins/DbFinderPlugin/README_generator">DbFinder admin generator documentation</a>, and <a href="http://www.symfony-project.org/plugins/DbFinderPlugin">download the plugin</a> right away.</p>
]]></content:encoded>
			<wfw:commentRss>http://redotheweb.com/2008/09/25/sorting-by-custom-column-in-the-symfony-admin-generator/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Document-Driven Development in Practice: Rethinking sfForms</title>
		<link>http://redotheweb.com/2008/09/23/document-driven-development-in-practice-rethinking-sfforms/</link>
		<comments>http://redotheweb.com/2008/09/23/document-driven-development-in-practice-rethinking-sfforms/#comments</comments>
		<pubDate>Tue, 23 Sep 2008 18:17:40 +0000</pubDate>
		<dc:creator>Francois Zaninotto</dc:creator>
		
		<category><![CDATA[documentation]]></category>

		<category><![CDATA[framework]]></category>

		<category><![CDATA[php]]></category>

		<category><![CDATA[plugins]]></category>

		<category><![CDATA[symfony]]></category>

		<category><![CDATA[usability]]></category>

		<guid isPermaLink="false">http://redotheweb.com/?p=65</guid>
		<description><![CDATA[If you've watched or read my presentation on Documentation-Driven Development, you may wonder how to put that new methodology into action. A practical example is often better than a long explanation, so let's see ho to apply it to the new Forms sub-framework introduced by symfony 1.1.

Not DDD

In order to use the new sfForm library, [...]]]></description>
			<content:encoded><![CDATA[<p>If you've watched or read my presentation on <a href="http://redotheweb.com/2008/09/15/developing-for-developers-my-symfonycamp08-presentation/">Documentation-Driven Development</a>, you may wonder how to put that new methodology into action. A practical example is often better than a long explanation, so let's see ho to apply it to the new Forms sub-framework introduced by symfony 1.1.</p>

<h2>Not DDD</h2>

<p>In order to use the new sfForm library, you must either read a <a href="http://www.symfony-project.org/book/forms/1_1/en/">book</a> (not yet completely written) or dive into the source code and guess how to use it. To my mind, this is pretty much the contrary of what leads to a large adoption.</p>

<p>The Form framework was designed with power in mind, and reaches this goal very well: you can use it to create forms of any level of complexity, including forms embedding other forms, forms with a variable number of fields, forms split into several steps ("wizards"), etc. It is very much object oriented, so everything can be reused or overridden.</p>

<p>But unfortunately, in order to create a simple form, you need to learn a lot more and write a lot more code than what you used to do in symfony 1.1. The current Forms documentation describes the API and justifies its implementation. It goes very much into the details of each part of the sub-framework, and quite early in the learning process. The result - for me, at least - is that the reader feels overwhelmed by the huge amount of classes, features and options, and dismisses the whole sub-framework for being too complex.</p>

<p>"Let's use that new Form stuff for complex forms and keep the current form helpers and YAML validation for everyday forms", I hear. That's a pity, because once you understand how the new Forms sub-framework works and accept its verbosity, there is no good reason to stick with the old system.</p>

<h2>An Ideal sfForm Documentation</h2>

<p>I think that a piece of documentation is missing. This piece is probably an introduction to the Form sub-framework.</p>

<p>In symfony 1.0, a single chapter of the book was enough to master forms for most use cases. Even if the new form sub-framework is more powerful than the 1.0 one, it should not be more complicated to learn and use in similar cases. So the sfForms introduction should be short, requiring at most one hour to read it.</p>

<p>After reading this documentation, an average developer should be able to use sfForms in 80% of the cases. That includes at least all the features described in the original <a href="http://www.symfony-project.org/book/1_0/10-Forms">Forms chapter</a> of the symfony book:</p>

<ul>
<li>Displaying a form</li>
<li>Available form helpers</li>
<li>Displaying a model-based form</li>
<li>Dealing with Foreign keys</li>
<li>Handling a form submission</li>
<li>Validating a form</li>
<li>Available validators</li>
<li>Repopulating a form</li>
<li>Complex use cases</li>
</ul>

<p>The target audience would be people knowing some concepts about symfony, but not yet everything. In fact, they should know what the Chapters 1 to 9 of the symfony guide cover, not more. So some advanced concepts should probably be skipped, or explained only after the fundamental usage is clear.</p>

<p>This introduction should not require additional lookup in the Forms book. That means that it should be self-sufficient. It probably also means not including the justifications of the Forms implementation that you can find in the current Forms book. The reasons why the API was designed the way it is should become obvious at the end of the introduction. Expert customization and rare use cases should probably also be left aside.</p>

<p>The symfony 1.0 documentation introduces concepts and features in a certain order, with a precise purpose: not loading too much information into the reader's mind at a time. In a similar fashion, the forms introduction should be a linear piece of documentation, not a set of articles that you can read in any order with hyperlinks everywhere to break the reading flow.</p>

<p>The forms framework is powerful, but the current form book somehow translates that into length, and verbosity. On the contrary, I think the reader should feel exalted: the documentation should put him in a rush to start using the new forms. So the forms introduction should "tell a story", and gently lead the reader to a point where he feels he can grab the steering wheel and drive the car by himself.</p>

<h2>API enhancements</h2>

<p>The problem is that explaining the current API takes much longer than a single piece of documentation. That's because of the many options available, because of the many objects to learn, and because even the simplest things (like a list of form controls) look complicated (<code>sfWidgetFormSchema</code>).</p>

<p>There is not much choice to overcome this problem. In order to write a short and readable guide to the forms sub-framework, its API must be adapted. That's right, the API must be changed so that the documentation can be made shorter, and more usable. This is one of the principles of the Documentation-Driven Development methodology.</p>

<p>These API enhancements should be completely backward compatible, so that any existing application using the current sfForms implementation can continue to work seamlessly with the modified implementation. In a way, that qualifies the API enhancements as a simplicity layer on top if the existing code. As a side note, the current Forms book still remains indispensable for advanced usage.</p>

<p>Note that the API enhancements don't need to be implemented before the new documentation is published. The implementation comes second, after the documentation. That's another of the DDD principles: explain first, make it work afterwards. After all, project managers write requirements for web applications before they exist, all the time.</p>

<h2>Do As I Do, Not As I Say</h2>

<p>Some people are getting sick of reading me criticizing parts of the symfony framework. Well, I'm not criticizing: I'm actively improving.</p>

<p>Rethinking sfForms is a good example for a Documentation-Driven Development. To illustrate this methodology, I'm going to <em>rewrite the Chapter 10 of the symfony book for symfony 1.1</em>. That's right, the current Chapter 10, which describes the "old way" of doing forms, can be rewritten in a similar fashion and serve for symfony 1.1.</p>

<p>But since the current API requires too much explanation to be used, I'm going to <em>introduce the necessary API changes</em> to the sfForms library. I'll create and manage forms in a way slightly different from what the current API allows, to make it simpler to use - and to explain.</p>

<p>When the new Chapter 10 is published here in this very blog, this piece of documentation will be of no use since the features it describes won't be implemented yet. But I know that writing documentation is not enough to convince people (yet), so I will <em>Implement the API changes</em> as a second step to the exercise. As I'm not a very good developer, any help will be welcome during that phase (contact me If you want to give me a hand after the documentation is published).</p>

<p>If everything goes well, the implementation of the API changes will be be released as a symfony plugin - maybe called <code>sfSimpleForms</code>. I hope it can lead more developers to adopt the greatest open-source Forms framework around.</p>
]]></content:encoded>
			<wfw:commentRss>http://redotheweb.com/2008/09/23/document-driven-development-in-practice-rethinking-sfforms/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Designing a CMS Architecture</title>
		<link>http://redotheweb.com/2008/09/19/designing-a-cms-architecture/</link>
		<comments>http://redotheweb.com/2008/09/19/designing-a-cms-architecture/#comments</comments>
		<pubDate>Fri, 19 Sep 2008 12:26:57 +0000</pubDate>
		<dc:creator>Francois Zaninotto</dc:creator>
		
		<category><![CDATA[framework]]></category>

		<category><![CDATA[php]]></category>

		<category><![CDATA[symfony]]></category>

		<category><![CDATA[web]]></category>

		<guid isPermaLink="false">http://redotheweb.com/?p=63</guid>
		<description><![CDATA[When faced with the alternative between an off-the-shelf CMS or a custom development, many companies pick solutions like ezPublish or Drupal. In addition to being free, these CMS seem to fulfill all possible requirements. But while choosing an open-source solution is a great idea, going for a full-featured CMS may prove more expensive than designing [...]]]></description>
			<content:encoded><![CDATA[<p>When faced with the alternative between an off-the-shelf CMS or a custom development, many companies pick solutions like ezPublish or Drupal. In addition to being free, these CMS seem to fulfill all possible requirements. But while choosing an open-source solution is a great idea, going for a full-featured CMS may prove more expensive than designing and developing your own Custom Management System.</p>

<h2>Hidden Costs</h2>

<p>What does it cost to integrate and deploy a website based on an open-source CMS? At first sight, not much. As for every CMS, you have to design your own templates and fill your website with initial data. But there are additional costs that pop up as soon as you need a little more than just plain content management.</p>

<p>Think about adding a blog or a forum to a website managed by a CMS. There are modules or plugins for that, but they never provide the same flexibility as plain blogging engines such as <a href="http://wordpress.org/">Wordpress</a>, or plain forum engines like <a href="http://www.phpbb.com/">phpBB</a>. So even if the basic requirement is fulfilled by a module, you will always need - always - to adapt its code.</p>

<p>And this is where it gets ugly. The code base of open source CMS engines and their plugin is nowhere as good as what you can see in RAD frameworks these days. Most of them are based on a very old architecture (PHP4, no object orientation, no proper error handling, direct access to the database, etc.). That means that changing something will be very painful, and very expensive. You will encounter numerous bugs, change the blogging plugin three times because neither of the ones you tested are capable of doing what you need, you will upgrade your CMS to the latest version to benefit from this single bug fix that should save your life but then you need to change all your existing configuration...</p>

<p>This is as bad as it sounds. Start changing one single line of code in an application build on top of Drupal or ezPublish, to name only the two major ones, and you are in trouble. The moment you need something that is not natively supported, you enter the Dark Zone of CMS hell. You are going to spend a lot of money on development. You will never see the end of the tunnel. That is, until someone says, a few years from now, "Do we need all that crap? Let's build something that fits our needs and that actually works".</p>

<h2>Making Your Own CMS</h2>

<p>Given number of available open-source CMS solutions, building one on your own sounds like a stupid idea. But if your website is 50% content management and 50% something else, you probably need to start with a web application framework like <a href="http://www.symfony-project.org/">symfony</a> or <a href="http://www.djangoproject.com/">Django</a>, rather than a CMS. These frameworks provide plugins that do part of the Content Management job already, so creating a CMS today is like assembling Lego bricks to build something that exactly fits your needs.</p>

<p>Take symfony, for instance. It provides native support, or support through plugins, for:</p>

<ul>
<li><a href="http://www.symfony-project.org/book/1_1/09-Links-and-the-Routing-System">Pretty URLs</a></li>
<li><a href="http://www.symfony-project.org/screencast/admin-generator">Generated forms and administration</a></li>
<li><a href="http://www.symfony-project.org/book/1_1/12-Caching">Caching</a></li>
<li><a href="http://www.symfony-project.org/book/1_1/07-Inside-the-View-Layer">Complex templates</a></li>
<li><a href="http://www.symfony-project.org/book/1_1/05-Configuring-Symfony#Environments">Multiple environments</a></li>
<li><a href="http://www.symfony-project.org/plugins/sfPropelActAsNestedSetBehaviorPlugin">Sections organized in a tree structure</a></li>
<li><a href="http://www.symfony-project.org/plugins/sfGuardPlugin">User authentication, groups and permissions</a></li>
<li><a href="http://www.symfony-project.org/plugins/sfLucenePlugin">Search and indexing</a></li>
<li><a href="http://www.symfony-project.org/plugins/sfAssetsLibraryPlugin">Image and other assets management</a></li>
<li><a href="http://www.symfony-project.org/plugins/sfPropelActAsTaggableBehaviorPlugin">Content tags and tag clouds</a></li>
<li><a href="http://www.symfony-project.org/plugins/sfPropelActAsCommentableBehaviorPlugin">User comments</a></li>
<li><a href="http://www.symfony-project.org/plugins/sfModerationPlugin">Moderation for user contributions</a></li>
<li><a href="http://www.symfony-project.org/plugins/sfPropelVersionableBehaviorPlugin">Content history and versionning</a></li>
<li><a href="http://www.symfony-project.org/plugins/sfFeed2Plugin">RSS feeds</a></li>
<li><a href="http://www.symfony-project.org/plugins/sfJobQueuePlugin">Tasks management and job queues</a></li>
<li><a href="http://www.symfony-project.org/plugins/sfStatsPlugin">Usage statistics</a></li>
<li><a href="http://www.symfony-project.org/plugins/sfSimpleForumPlugin">Forum</a></li>
<li><a href="http://www.symfony-project.org/plugins/sfSimpleBlogPlugin">Blog</a></li>
</ul>

<p>Symfony doesn't yet provide an Access Control List or a Workflow plugin, but you can already put all of the above together and have a pretty powerful CMS engine.</p>

<p>A tailor-made CMS will always have less code and show better performance than any of the existing full-featured solutions. Also, you will be able to tweak it completely, since all the components are decoupled, and built with extensibility in mind.</p>

<p>Your custom CMS will cost you more during the first year, but if you expect your website(s) to live longer than that, then the benefit will become obvious after a year and a half. Plugging the CMS features into other parts of the website, adding features unrelated to content management, scaling to a larger audience, replacing the database engine or the caching backend, all that will be painless.</p>

<p>That is, if you design your custom CMS carefully, and with the future in mind.</p>

<h2>Environments</h2>

<p>When you add features to an application, you need a testing environment - a place where you can check that the additions work and don't kill the rest of the application. That means that developers have a version of the website on their desktop computer, where they change stuff. Then, they upload the application to a test server, check that everything is OK, and only then can they deploy the application to the production server. This is a very common practice, often backed up by source version control and continuous integration tools.</p>

<p>But what happens when a new feature is not made of code, but of data? In ezPublish, for instance, in order to define a new type of content (they call it a "Class"), you have to use the backend web interface and fill in a few forms. The properties of the new type of content are stored in the database. In order to deploy this new type of content from the testing environment to the production environment, the developers need to transfer data from one database to another - without wiping off unrelated information on the production database, such as user comments, statistics, etc.</p>

<p>Deploying new features in this context means executing some SQL code on each server. This is much more dangerous than just pushing a new version of the codebase, especially when the data model is made of many tables glued together in complex joins. That's why, in many websites based on ezPublish, developers add features directly on the production environment, or repeat the configuration using the backend interface on every environment. This is either a high risk or a large waste of time.</p>

<h2>Data, or Code?</h2>

<p>This environment drawback tends to be a major influence over the choice of features a CMS should provide. For almost every CMS feature, you should wonder: Can the user do that through the backend interface, or do we need a programmer to add a new element? In other terms, is the feature made of data, or code?</p>

<p>Off-the-shelf CMS engines will almost always answer 'Data'. My personal opinion is that it is wrong in many cases. Content types are just one example, but think about workflows or page layouts for instance. They define a complex logic that always translates to code, and giving the user the ability to change them via a backend interface means storing code in the database and evaluating it at runtime. Then you can't use op-code cache engines like APC incriease your website performance. And deploying that to production is a nightmare.</p>

<p>Some companies think that most of the CMS features should be accessible via a backend interface in order to be able to enhance the application without additional developments. But this is an illusion. For one, the configuration of content classes in ezPublish is so complex that it does indeed require a PHP developer, and an expensive one, since experience with ezPublish is one of the most demanded skills in the IT market (at least in France). More features mean more development, and there is no CMS out there that replaces the power of a programming language with a web interface.</p>

<p>So that leads to one good rule of thumb: Design your features so that they can be made of code rather than data. That applies to elements that can be modified by a graphical user interface, or programatically:</p>

<ul>
<li>Content classes</li>
<li>"Widgets" or "Components" for pages</li>
<li>Page layouts or "templates"</li>
<li>Content validation workflow</li>
<li>Tasks</li>
</ul>

<h2>Fundamental questions</h2>

<p>The complexity of a CMS engine depends greatly on the answer you give to a few fundamental questions:</p>

<ul>
<li>Can contents exist independently of a page?</li>
<li>Can contents exist at more than one place in the website?</li>
<li>Are there several views for a single piece of content?</li>
<li>Can contents have different versions simultaneously?</li>
<li>Can contents be modified in the backend and keep unchanged in the frontend?</li>
<li>Can users compose a page with "widgets" or "components" in a WYSIWYG interface?</li>
<li>Can predefined zones in a template contain more than one "widget" or "component"?</li>
<li>Can section pages have different templates?</li>
<li>Can section pages have different versions simultaneously?</li>
<li>Can users program the publishing of a section page, or of contents, in advance?</li>
<li>Can the CMS remember previous URLs for a content that changed title?</li>
</ul>

<p>If the answer to the first question is no, then the concept of "page" and "content" coincide. You probably don't need to develop anything, since your CMS will be <a href="http://www.symfony-project.org/plugins/sfSimpleCMSPlugin">quite simple</a>.</p>

<p>If you answer yes to all these questions, then the CMS might take three times longer to develop than what it would be otherwise.</p>

<p>That's why the idea of a tailor-made CMS is not that stupid. No existing CMS will be able to answer these questions in every possible way. But designing your own relational schema based on the answer to these questions makes sense, economically speaking. Don't make it complex if you don't need do, or, to put it otherwise, <a href="http://en.wikipedia.org/wiki/KISS_principle">Keep It Simple, Stupid</a>.</p>

<h2>Bootstrapping the reflection</h2>

<p>Now that you're trying to imagine what you actually need for your own CMS, here is a glimpse of the kind of technical challenge you will face all the time.</p>

<p>The question turns around the concept of <em>content types</em>. In a CMS, you mostly deal with "articles". This type of content has a title, an author, a summary, a body, and a few other attributes. But you probably also need to deal with some other content types, like movies, slide shows, quiz games, polls, or recipes. These content types are defined by properties distinct from that of an article. Some of them can fit in a single structure, others require several structures related to each other. For instance, quiz games require a structure for the quiz itself, one for the questions, one for the answers to each question, and one for the quiz results.</p>

<p>The question is: Do you store the data for all these content types in a single table, or do you create a table for each content type? The most "normalized" choice is probably to create one data structure for each. You could have an "article" table, a "recipe" table, and even a "quiz" table with foreign keys to a "quiz_question" and a "quiz_result" table. That would allow you to make queries on some specific attributes of a specific content type. You could build a custom search engine for your recipes and look for ingredients, foreign cuisine and preparation time.</p>

<p>But then, if each content type has its own table(s), what do you do when you have to list all the contents of a section, or worse (that happens in the backend) all the contents of the website? Does that mean that, in order to display a list of contents, you must query several tables and aggregate the results together? This solution simply doesn't scale, and a CMS built like that will become slower and slower as you add new content types.</p>

<p>So that probably means that you should store a reference to each content in a separate table, with a copy of the data that is generic to all content types (like title, publication date, section, etc.). Pages displaying a list of contents would use this aggregate table, while pages displaying content details would use the specific tables.</p>

<p>And that means that you must find a way to synchronize the specific tables and the generic tables whenever data changes in content. That's not a big deal, but it gives you an idea of the kind of complexity you will encounter in a large scale CMS.</p>

<h2>A Challenging Exercise</h2>

<p>Designing a CMS is difficult and fun, and you'll probably do it more than once. Every CMS is different, because every content management need is different, and mostly because every customer wants more than just plain content management.</p>

<p>If you are a developer, whenever you meet a client that asks you for a Drupal integration, try to sell your knowledge of CMS architectures rather than a few hours of developer time. Raise the important questions, talk about the possible problems of using off-the-shelf solutions. If you ever used one of those before, you will have plenty of issues to talk about. Then, try to convince your customer to trust you into a custom development. Make it small at the beginning, so that the customer can start using it right away and refine its requirements incrementally.</p>

<p>This will be a very satisfying experience, and the client will thank you later for leading him on the right path. And this will give you a lot to talk about for the next CMS you build...</p>
]]></content:encoded>
			<wfw:commentRss>http://redotheweb.com/2008/09/19/designing-a-cms-architecture/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Developing for Developers: my SymfonyCamp08 Presentation</title>
		<link>http://redotheweb.com/2008/09/15/developing-for-developers-my-symfonycamp08-presentation/</link>
		<comments>http://redotheweb.com/2008/09/15/developing-for-developers-my-symfonycamp08-presentation/#comments</comments>
		<pubDate>Mon, 15 Sep 2008 07:30:02 +0000</pubDate>
		<dc:creator>Francois Zaninotto</dc:creator>
		
		<category><![CDATA[documentation]]></category>

		<category><![CDATA[php]]></category>

		<category><![CDATA[symfony]]></category>

		<category><![CDATA[usability]]></category>

		<guid isPermaLink="false">http://redotheweb.com/2008/09/15/developing-for-developers-my-symfonycamp08-presentation/</guid>
		<description><![CDATA[Did you attend this year's Symfony Camp? It was a great event, the unique occasion to meet the core team of the symfony framework. I had the opportunity to give a talk there, and you can now watch it online:

Developing for DevelopersView SlideShare presentation or Upload your own. (tags: symfony php)

Don't hesitate to comment on [...]]]></description>
			<content:encoded><![CDATA[<p>Did you attend this year's <a href="http://www.symfonycamp.com/">Symfony Camp</a>? It was a great event, the unique occasion to meet the core team of the symfony framework. I had the opportunity to give a talk there, and you can now watch it online:</p>

<div style="width:425px;text-align:left" id="__ss_598599"><a style="font:14px Helvetica,Arial,Sans-serif;display:block;margin:12px 0 3px 0;text-decoration:underline;" href="http://www.slideshare.net/francoisz/developing-for-developers-presentation?type=powerpoint" title="Developing for Developers">Developing for Developers</a><object style="margin:0px" width="425" height="355"><param name="movie" value="http://static.slideshare.net/swf/ssplayer2.swf?doc=code-usability-1221462076989491-9&#038;stripped_title=developing-for-developers-presentation" /><param name="allowFullScreen" value="true"/><param name="allowScriptAccess" value="always"/><embed src="http://static.slideshare.net/swf/ssplayer2.swf?doc=code-usability-1221462076989491-9&#038;stripped_title=developing-for-developers-presentation" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="355"></embed></object><div style="font-size:11px;font-family:tahoma,arial;height:26px;padding-top:2px;">View SlideShare <a style="text-decoration:underline;" href="http://www.slideshare.net/francoisz/developing-for-developers-presentation?type=powerpoint" title="View Developing for Developers on SlideShare">presentation</a> or <a style="text-decoration:underline;" href="http://www.slideshare.net/upload?type=powerpoint">Upload</a> your own. (tags: <a style="text-decoration:underline;" href="http://slideshare.net/tag/symfony">symfony</a> <a style="text-decoration:underline;" href="http://slideshare.net/tag/php">php</a>)</div></div>

<p>Don't hesitate to comment on this presentation on SlideShare.</p>

<p>Thanks to all the great people that I met there who gave me a feedback on my work, encouragements or advice. Thanks to <a href="http://www.dop.nu/">Dutch Open Projects</a> for the organization - and for inviting me. It was a great pleasure to exchange about symfony, its past, present, and future, with so many enthusiastic people.</p>

<p><strong>Update</strong>: It seems that my slideshow has been featured on the <a href="http://www.slideshare.net/">SlideShare homepage</a> by the SlideShare editorial team.</p>
]]></content:encoded>
			<wfw:commentRss>http://redotheweb.com/2008/09/15/developing-for-developers-my-symfonycamp08-presentation/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Validating a YAML file against a schema in PHP</title>
		<link>http://redotheweb.com/2008/09/09/validating-a-yaml-file-against-a-schema-in-php/</link>
		<comments>http://redotheweb.com/2008/09/09/validating-a-yaml-file-against-a-schema-in-php/#comments</comments>
		<pubDate>Tue, 09 Sep 2008 15:42:43 +0000</pubDate>
		<dc:creator>Francois Zaninotto</dc:creator>
		
		<category><![CDATA[framework]]></category>

		<category><![CDATA[php]]></category>

		<category><![CDATA[symfony]]></category>

		<guid isPermaLink="false">http://redotheweb.com/2008/09/09/validating-a-yaml-file-against-a-schema-in-php/</guid>
		<description><![CDATA[As of today, there is no simple way to validate the syntax of a YAML file in PHP. But with two simple tricks, it takes only a few dozens of lines of code to build a robust validator capable of checking the syntax of any YAML file against a given schema.

The problem

YAML is much easier [...]]]></description>
			<content:encoded><![CDATA[<p>As of today, there is no simple way to validate the syntax of a YAML file in PHP. But with two simple tricks, it takes only a few dozens of lines of code to build a robust validator capable of checking the syntax of any YAML file against a given schema.</p>

<h2>The problem</h2>

<p>YAML is much easier to write and read than XML, but YAML has no schema validation capabilities. With DTD and XSD, you can check that an XML file is correctly formatted before actually using it, and it helps debugging a great lot. Modern web application frameworks like <a href="http://www.symfony-project.com/">symfony</a> encourage the use of YAML for configuration files, but the lack of validation tool sometimes make YAML a poor choice in a professional environment.</p>

<p>Such a validation tool exists in Ruby, it's called <a href="http://www.kuwata-lab.com/kwalify/">kwalify</a>. But unless you want to spend a huge amount of time translating the 6,000+ lines of code of the library from Ruby into PHP, or to run Ruby code inside your PHP application, you're basically stuck.</p>

<h2>First Idea</h2>

<p>Did you just read that XML allows validation by way of XSD? Well, why not use this mechanism to validate a YAML file? After all, PHP has a great <a href="http://fr2.php.net/manual/en/refs.xml.php">XML manipulation library</a>, installed by default, and capable of validating any XML file against a DTD or an XSD. Actually, this mechanism is already in use in symfony, since the Propel <code>schema.yml</code> is transformed into an XML counterpart that has an XSD.</p>

<p>It is trivial to transform a YAML file into a PHP associative array. Symfony 1.1 provides a class that does exactly that, and it's called <a href="http://trac.symfony-project.org/browser/branches/1.1/lib/yaml">sfYaml</a>. With a little bit of recursion and a few lines of PHP code, it is also quite easy to transform an associative array into a simple XML file.</p>

<p>Let's use the <a href="http://www.symfony-project.org/book/1_1/07-Inside-the-View-Layer#The%20view.yml%20File"><code>view.yml</code> configuration file</a> in symfony for example. In a typical module, it looks like the following:</p>

<pre><code># view.yml
default:
  http_metas:
    content-type:  text/html

  metas:
    title:         My symfony project
    robots:        index, follow
    description:   This is my first symfony project
    keywords:      symfony
    language:      en

  stylesheets:     [main.css, top.css]

  javascripts:     [jquery-1.2.6.js, main.js]

  has_layout:      on
  layout:          layout

indexSuccess:
  metas:
    title:        Welcome to my site
</code></pre>

<p>Now what does it take to transform this YAML into a simple XML equivalent? Not much. A bit of googling shows that <a href="http://snipplr.com/view/3491/convert-php-array-to-xml-or-simple-xml-object-if-you-wish/">someone already worked on transforming an associative array into XML</a>, and as it is not a good idea to reinvent the wheel, let's reuse this work.</p>

<div class="syntax_hilite"><div id="php-19">
<div class="php"><span style="color:#FF9933; font-style:italic;">// Transform YAML into XML</span><br />
<span style="color:#616100;color: grey;">include</span> <span style="color:#FF0000;color: #AF8E99;">'sfYaml.class.php'</span>;<br />
<span style="color:#0088FF;">$yamlString</span> = <a href="http://www.php.net/file_get_contents"><span style="color:#000066;color: white;">file_get_contents</span></a><span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#FF0000;color: #AF8E99;">'view.yml'</span><span style="color:#00AA00; font-weight:bold;">&#41;</span>;<br />
<span style="color:#0088FF;">$yamlArray</span> =&nbsp; sfYaml::<span style="color:#00AA00;">load</span><span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#0088FF;">$yamlString</span><span style="color:#00AA00; font-weight:bold;">&#41;</span>;<br />
<span style="color:#0088FF;">$xmlString</span> = ArrayToXml<span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#0088FF;">$yamlArray</span><span style="color:#00AA00; font-weight:bold;">&#41;</span>;<br />
<br />
<span style="color:#000000; font-weight:bold;color: grey;">function</span> ArrayToXml<span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#0088FF;">$data</span>, <span style="color:#0088FF;">$rootNodeName</span> = <span style="color:#FF0000;color: #AF8E99;">'root'</span>, <span style="color:#0088FF;">$xml</span> = <span style="color:#000000; font-weight:bold;color: grey;">null</span><span style="color:#00AA00; font-weight:bold;">&#41;</span><br />
<span style="color:#00AA00; font-weight:bold;">&#123;</span><br />
&nbsp; <span style="color:#616100;color: grey;">if</span> <span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#0088FF;">$xml</span> == <span style="color:#000000; font-weight:bold;color: grey;">null</span><span style="color:#00AA00; font-weight:bold;">&#41;</span><br />
&nbsp; <span style="color:#00AA00; font-weight:bold;">&#123;</span><br />
&nbsp; &nbsp; <span style="color:#0088FF;">$xml</span> = simplexml_load_string<span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#FF0000;color: #AF8E99;">"&lt;?xml version='1.0' encoding='utf-8'?&gt;&lt;$rootNodeName /&gt;"</span><span style="color:#00AA00; font-weight:bold;">&#41;</span>;<br />
&nbsp; <span style="color:#00AA00; font-weight:bold;">&#125;</span><br />
<br />
&nbsp; <span style="color:#FF9933; font-style:italic;">// loop through the data passed in.</span><br />
&nbsp; <span style="color:#616100;color: grey;">foreach</span><span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#0088FF;">$data</span> <span style="color:#616100;color: grey;">as</span> <span style="color:#0088FF;">$key</span> =&gt; <span style="color:#0088FF;">$value</span><span style="color:#00AA00; font-weight:bold;">&#41;</span><br />
&nbsp; <span style="color:#00AA00; font-weight:bold;">&#123;</span><br />
&nbsp; &nbsp; <span style="color:#FF9933; font-style:italic;">// no numeric keys in our xml please!</span><br />
&nbsp; &nbsp; <span style="color:#616100;color: grey;">if</span> <span style="color:#00AA00; font-weight:bold;">&#40;</span><a href="http://www.php.net/is_numeric"><span style="color:#000066;color: white;">is_numeric</span></a><span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#0088FF;">$key</span><span style="color:#00AA00; font-weight:bold;">&#41;</span><span style="color:#00AA00; font-weight:bold;">&#41;</span><br />
&nbsp; &nbsp; <span style="color:#00AA00; font-weight:bold;">&#123;</span><br />
&nbsp; &nbsp; &nbsp; <span style="color:#FF9933; font-style:italic;">// make string key...</span><br />
&nbsp; &nbsp; &nbsp; <span style="color:#0088FF;">$key</span> = <span style="color:#FF0000;color: #AF8E99;">"unknownNode_"</span>. <span style="color:#00AA00; font-weight:bold;">&#40;</span>string<span style="color:#00AA00; font-weight:bold;">&#41;</span> <span style="color:#0088FF;">$key</span>;<br />
&nbsp; &nbsp; <span style="color:#00AA00; font-weight:bold;">&#125;</span><br />
<br />
&nbsp; &nbsp; <span style="color:#FF9933; font-style:italic;">// replace anything not alpha numeric</span><br />
&nbsp; &nbsp; <span style="color:#0088FF;">$key</span> = <a href="http://www.php.net/preg_replace"><span style="color:#000066;color: white;">preg_replace</span></a><span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#FF0000;color: #AF8E99;">'/[^a-z]/i'</span>, <span style="color:#FF0000;color: #AF8E99;">''</span>, <span style="color:#0088FF;">$key</span><span style="color:#00AA00; font-weight:bold;">&#41;</span>;<br />
<br />
&nbsp; &nbsp; <span style="color:#FF9933; font-style:italic;">// if there is another array found recrusively call this function</span><br />
&nbsp; &nbsp; <span style="color:#616100;color: grey;">if</span> <span style="color:#00AA00; font-weight:bold;">&#40;</span><a href="http://www.php.net/is_array"><span style="color:#000066;color: white;">is_array</span></a><span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#0088FF;">$value</span><span style="color:#00AA00; font-weight:bold;">&#41;</span><span style="color:#00AA00; font-weight:bold;">&#41;</span><br />
&nbsp; &nbsp; <span style="color:#00AA00; font-weight:bold;">&#123;</span><br />
&nbsp; &nbsp; &nbsp; <span style="color:#0088FF;">$node</span> = <span style="color:#0088FF;">$xml</span>-&gt;<span style="color:#00AA00;">addChild</span><span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#0088FF;">$key</span><span style="color:#00AA00; font-weight:bold;">&#41;</span>;<br />
&nbsp; &nbsp; &nbsp; <span style="color:#FF9933; font-style:italic;">// recrusive call.</span><br />
&nbsp; &nbsp; &nbsp; ArrayToXml<span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#0088FF;">$value</span>, <span style="color:#0088FF;">$rootNodeName</span>, <span style="color:#0088FF;">$node</span><span style="color:#00AA00; font-weight:bold;">&#41;</span>;<br />
&nbsp; &nbsp; <span style="color:#00AA00; font-weight:bold;">&#125;</span><br />
&nbsp; &nbsp; <span style="color:#616100;color: grey;">else</span><br />
&nbsp; &nbsp; <span style="color:#00AA00; font-weight:bold;">&#123;</span><br />
&nbsp; &nbsp; &nbsp; <span style="color:#FF9933; font-style:italic;">// add single node.</span><br />
&nbsp; &nbsp; &nbsp; <span style="color:#0088FF;">$xml</span>-&gt;<span style="color:#00AA00;">addChild</span><span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#0088FF;">$key</span>, <span style="color:#0088FF;">$value</span><span style="color:#00AA00; font-weight:bold;">&#41;</span>;<br />
&nbsp; &nbsp; <span style="color:#00AA00; font-weight:bold;">&#125;</span><br />
&nbsp; <span style="color:#00AA00; font-weight:bold;">&#125;</span><br />
<br />
&nbsp; <span style="color:#FF9933; font-style:italic;">// pass back as string. or simple xml object if you want!</span><br />
&nbsp; <span style="color:#616100;color: grey;">return</span> <span style="color:#0088FF;">$xml</span>-&gt;<span style="color:#00AA00;">asXML</span><span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#00AA00; font-weight:bold;">&#41;</span>;<br />
<span style="color:#00AA00; font-weight:bold;">&#125;</span></div>
</div></div>

<p><br /></p>

<h2>Second idea</h2>

<p>The result of the simple YAML to XML transformation looks like this:</p>

<div class="syntax_hilite"><div id="xml-20">
<div class="xml"><span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;</span>?xml <span style="color: #999966;">version</span>=<span style="color: #ff0000;color: #AF8E99;">"1.0"</span> <span style="color: #999966;">encoding</span>=<span style="color: #ff0000;color: #AF8E99;">"utf-8"</span>?<span style="font-weight: bold; color: grey;">&gt;</span></span><br />
<span style="color: #009900;"><span style="color: #808080; font-style: italic;">&lt;!-- view.yml.xml --&gt;</span></span><br />
<span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;root<span style="font-weight: bold; color: grey;">&gt;</span></span></span><br />
&nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;default<span style="font-weight: bold; color: grey;">&gt;</span></span></span><br />
&nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;httpmetas<span style="font-weight: bold; color: grey;">&gt;</span></span></span><br />
&nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;contenttype<span style="font-weight: bold; color: grey;">&gt;</span></span></span>text/html<span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/contenttype<span style="font-weight: bold; color: grey;">&gt;</span></span></span><br />
&nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/httpmetas<span style="font-weight: bold; color: grey;">&gt;</span></span></span><br />
&nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;metas<span style="font-weight: bold; color: grey;">&gt;</span></span></span><br />
&nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;title<span style="font-weight: bold; color: grey;">&gt;</span></span></span>My symfony project<span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/title<span style="font-weight: bold; color: grey;">&gt;</span></span></span><br />
&nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;robots<span style="font-weight: bold; color: grey;">&gt;</span></span></span>index, follow<span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/robots<span style="font-weight: bold; color: grey;">&gt;</span></span></span><br />
&nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;description<span style="font-weight: bold; color: grey;">&gt;</span></span></span>This is my first symfony project<span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/description<span style="font-weight: bold; color: grey;">&gt;</span></span></span><br />
&nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;keywords<span style="font-weight: bold; color: grey;">&gt;</span></span></span>symfony<span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/keywords<span style="font-weight: bold; color: grey;">&gt;</span></span></span><br />
&nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;language<span style="font-weight: bold; color: grey;">&gt;</span></span></span>en<span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/language<span style="font-weight: bold; color: grey;">&gt;</span></span></span><br />
&nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/metas<span style="font-weight: bold; color: grey;">&gt;</span></span></span><br />
&nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;stylesheets<span style="font-weight: bold; color: grey;">&gt;</span></span></span><br />
&nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;unknownNode<span style="font-weight: bold; color: grey;">&gt;</span></span></span>main.css<span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/unknownNode<span style="font-weight: bold; color: grey;">&gt;</span></span></span><br />
&nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;unknownNode<span style="font-weight: bold; color: grey;">&gt;</span></span></span>top.css<span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/unknownNode<span style="font-weight: bold; color: grey;">&gt;</span></span></span><br />
&nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/stylesheets<span style="font-weight: bold; color: grey;">&gt;</span></span></span><br />
&nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;javascripts<span style="font-weight: bold; color: grey;">&gt;</span></span></span><br />
&nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;unknownNode<span style="font-weight: bold; color: grey;">&gt;</span></span></span>jquery-1.2.6.js<span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/unknownNode<span style="font-weight: bold; color: grey;">&gt;</span></span></span><br />
&nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;unknownNode<span style="font-weight: bold; color: grey;">&gt;</span></span></span>main.js<span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/unknownNode<span style="font-weight: bold; color: grey;">&gt;</span></span></span><br />
&nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/javascripts<span style="font-weight: bold; color: grey;">&gt;</span></span></span><br />
&nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;haslayout<span style="font-weight: bold; color: grey;">&gt;</span></span></span>1<span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/haslayout<span style="font-weight: bold; color: grey;">&gt;</span></span></span><br />
&nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;layout<span style="font-weight: bold; color: grey;">&gt;</span></span></span>layout<span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/layout<span style="font-weight: bold; color: grey;">&gt;</span></span></span><br />
&nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/default<span style="font-weight: bold; color: grey;">&gt;</span></span></span><br />
&nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;indexSuccess<span style="font-weight: bold; color: grey;">&gt;</span></span></span><br />
&nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;metas<span style="font-weight: bold; color: grey;">&gt;</span></span></span><br />
&nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;title<span style="font-weight: bold; color: grey;">&gt;</span></span></span>Welcome to my site<span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/title<span style="font-weight: bold; color: grey;">&gt;</span></span></span><br />
&nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/metas<span style="font-weight: bold; color: grey;">&gt;</span></span></span><br />
&nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/indexSuccess<span style="font-weight: bold; color: grey;">&gt;</span></span></span><br />
<span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/root<span style="font-weight: bold; color: grey;">&gt;</span></span></span></div>
</div></div>

<p><br /></p>

<p>The trouble here is that the <code>&lt;default&gt;</code> and <code>&lt;indexSuccess&gt;</code> tags are not real tags. That means, they do not define a class of content but a value. Same for the <code>&lt;unknownNode&gt;</code> nodes. To make sense, a real equivalent to the <code>view.yml</code> in XML should look like this:</p>

<div class="syntax_hilite"><div id="xml-21">
<div class="xml"><span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;</span>?xml <span style="color: #999966;">version</span>=<span style="color: #ff0000;color: #AF8E99;">"1.0"</span>?<span style="font-weight: bold; color: grey;">&gt;</span></span><br />
<span style="color: #009900;"><span style="color: #808080; font-style: italic;">&lt;!-- view.yml.xml, semantically correct --&gt;</span></span><br />
<span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;templates<span style="font-weight: bold; color: grey;">&gt;</span></span></span><br />
&nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;template</span> <span style="color: #999966;">name</span>=<span style="color: #ff0000;color: #AF8E99;">"default"</span><span style="font-weight: bold; color: grey;">&gt;</span></span><br />
&nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;httpmetas<span style="font-weight: bold; color: grey;">&gt;</span></span></span><br />
&nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;contenttype<span style="font-weight: bold; color: grey;">&gt;</span></span></span>text/html<span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/contenttype<span style="font-weight: bold; color: grey;">&gt;</span></span></span><br />
&nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/httpmetas<span style="font-weight: bold; color: grey;">&gt;</span></span></span><br />
&nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;metas<span style="font-weight: bold; color: grey;">&gt;</span></span></span><br />
&nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;title<span style="font-weight: bold; color: grey;">&gt;</span></span></span>My symfony project<span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/title<span style="font-weight: bold; color: grey;">&gt;</span></span></span><br />
&nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;robots<span style="font-weight: bold; color: grey;">&gt;</span></span></span>index, follow<span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/robots<span style="font-weight: bold; color: grey;">&gt;</span></span></span><br />
&nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;description<span style="font-weight: bold; color: grey;">&gt;</span></span></span>This is my first symfony project<span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/description<span style="font-weight: bold; color: grey;">&gt;</span></span></span><br />
&nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;keywords<span style="font-weight: bold; color: grey;">&gt;</span></span></span>symfony<span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/keywords<span style="font-weight: bold; color: grey;">&gt;</span></span></span><br />
&nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;language<span style="font-weight: bold; color: grey;">&gt;</span></span></span>en<span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/language<span style="font-weight: bold; color: grey;">&gt;</span></span></span><br />
&nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/metas<span style="font-weight: bold; color: grey;">&gt;</span></span></span><br />
&nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;stylesheets<span style="font-weight: bold; color: grey;">&gt;</span></span></span><br />
&nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;stylesheet<span style="font-weight: bold; color: grey;">&gt;</span></span></span>main.css<span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/stylesheet<span style="font-weight: bold; color: grey;">&gt;</span></span></span><br />
&nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;stylesheet<span style="font-weight: bold; color: grey;">&gt;</span></span></span>top.css<span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/stylesheet<span style="font-weight: bold; color: grey;">&gt;</span></span></span><br />
&nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/stylesheets<span style="font-weight: bold; color: grey;">&gt;</span></span></span><br />
&nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;javascripts<span style="font-weight: bold; color: grey;">&gt;</span></span></span><br />
&nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;javascript<span style="font-weight: bold; color: grey;">&gt;</span></span></span>jquery-1.2.6.js<span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/javascript<span style="font-weight: bold; color: grey;">&gt;</span></span></span><br />
&nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;javascript<span style="font-weight: bold; color: grey;">&gt;</span></span></span>main.js<span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/javascript<span style="font-weight: bold; color: grey;">&gt;</span></span></span><br />
&nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/javascripts<span style="font-weight: bold; color: grey;">&gt;</span></span></span><br />
&nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;haslayout<span style="font-weight: bold; color: grey;">&gt;</span></span></span>1<span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/haslayout<span style="font-weight: bold; color: grey;">&gt;</span></span></span><br />
&nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;layout<span style="font-weight: bold; color: grey;">&gt;</span></span></span>layout<span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/layout<span style="font-weight: bold; color: grey;">&gt;</span></span></span><br />
&nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/template<span style="font-weight: bold; color: grey;">&gt;</span></span></span><br />
&nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;template</span> <span style="color: #999966;">name</span>=<span style="color: #ff0000;color: #AF8E99;">"indexSuccess"</span><span style="font-weight: bold; color: grey;">&gt;</span></span><br />
&nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;metas<span style="font-weight: bold; color: grey;">&gt;</span></span></span><br />
&nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;title<span style="font-weight: bold; color: grey;">&gt;</span></span></span>Welcome to my site<span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/title<span style="font-weight: bold; color: grey;">&gt;</span></span></span><br />
&nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/metas<span style="font-weight: bold; color: grey;">&gt;</span></span></span><br />
&nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/template<span style="font-weight: bold; color: grey;">&gt;</span></span></span><br />
<span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/templates<span style="font-weight: bold; color: grey;">&gt;</span></span></span></div>
</div></div>

<p><br /></p>

<p>The difference is that main entries are <code>&lt;template&gt;</code> tags with a <code>name</code> attribute, and that children of the <code>&lt;javascripts&gt;</code> element are simple <code>&lt;javascript&gt;</code> elements. This second XML file is semantically correct, because it follows a simple grammar - and can be validated.</p>

<p>But how can you turn the first XML file into the second? The right tool for this job is called XSLT, or <a href="http://www.w3schools.com/xsl/">Extensible Stylesheet Language Transformations</a>. An XSLT file is a set of transformation rules described in XML. Applying these rules on an XML files transforms it into another XML file. That's exactly what you need here.</p>

<p>The XSLT file to turn the first <code>view.yml.xml</code> into the second one is quite simple:</p>

<div class="syntax_hilite"><div id="xml-22">
<div class="xml"><span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;</span>?xml <span style="color: #999966;">version</span>=<span style="color: #ff0000;color: #AF8E99;">'1.0'</span>?<span style="font-weight: bold; color: grey;">&gt;</span></span><br />
<span style="color: #009900;"><span style="color: #808080; font-style: italic;">&lt;!-- view.yml.xsl --&gt;</span></span><br />
<span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;xsl</span>:stylesheet xmlns:<span style="color: #999966;">xsl</span>=<span style="color: #ff0000;color: #AF8E99;">"http://www.w3.org/1999/XSL/Transform"</span> <span style="color: #999966;">version</span>=<span style="color: #ff0000;color: #AF8E99;">"1.0"</span><span style="font-weight: bold; color: grey;">&gt;</span></span><br />
&nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;xsl</span>:template <span style="color: #999966;">match</span>=<span style="color: #ff0000;color: #AF8E99;">"/root"</span><span style="font-weight: bold; color: grey;">&gt;</span></span><br />
&nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;templates<span style="font-weight: bold; color: grey;">&gt;</span></span></span><br />
&nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;xsl</span>:for-each <span style="color: #999966;">select</span>=<span style="color: #ff0000;color: #AF8E99;">"child::*"</span><span style="font-weight: bold; color: grey;">&gt;</span></span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;template<span style="font-weight: bold; color: grey;">&gt;</span></span></span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;xsl</span>:attribute <span style="color: #999966;">name</span>=<span style="color: #ff0000;color: #AF8E99;">"name"</span><span style="font-weight: bold; color: grey;">&gt;</span></span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;xsl</span>:value-of <span style="color: #999966;">select</span>=<span style="color: #ff0000;color: #AF8E99;">"local-name()"</span> <span style="font-weight: bold; color: grey;">/&gt;</span></span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/xsl</span>:attribute<span style="font-weight: bold; color: grey;">&gt;</span></span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;xsl</span>:apply-templates <span style="color: #999966;">select</span>=<span style="color: #ff0000;color: #AF8E99;">"child::*"</span><span style="font-weight: bold; color: grey;">/&gt;</span></span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/template<span style="font-weight: bold; color: grey;">&gt;</span></span></span><br />
&nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/xsl</span>:for-each<span style="font-weight: bold; color: grey;">&gt;</span></span><br />
&nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/templates<span style="font-weight: bold; color: grey;">&gt;</span></span></span><br />
&nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/xsl</span>:template<span style="font-weight: bold; color: grey;">&gt;</span></span><br />
&nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;xsl</span>:template <span style="color: #999966;">match</span>=<span style="color: #ff0000;color: #AF8E99;">"//stylesheets/unknownNode"</span><span style="font-weight: bold; color: grey;">&gt;</span></span><br />
&nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;stylesheet<span style="font-weight: bold; color: grey;">&gt;</span></span></span><br />
&nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;xsl</span>:value-of <span style="color: #999966;">select</span>=<span style="color: #ff0000;color: #AF8E99;">"text()"</span> <span style="font-weight: bold; color: grey;">/&gt;</span></span><br />
&nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/stylesheet<span style="font-weight: bold; color: grey;">&gt;</span></span></span><br />
&nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/xsl</span>:template<span style="font-weight: bold; color: grey;">&gt;</span></span><br />
&nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;xsl</span>:template <span style="color: #999966;">match</span>=<span style="color: #ff0000;color: #AF8E99;">"//javascripts/unknownNode"</span><span style="font-weight: bold; color: grey;">&gt;</span></span><br />
&nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;javascript<span style="font-weight: bold; color: grey;">&gt;</span></span></span><br />
&nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;xsl</span>:value-of <span style="color: #999966;">select</span>=<span style="color: #ff0000;color: #AF8E99;">"text()"</span> <span style="font-weight: bold; color: grey;">/&gt;</span></span><br />
&nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/javascript<span style="font-weight: bold; color: grey;">&gt;</span></span></span><br />
&nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/xsl</span>:template<span style="font-weight: bold; color: grey;">&gt;</span></span><br />
&nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;xsl</span>:template <span style="color: #999966;">match</span>=<span style="color: #ff0000;color: #AF8E99;">"*"</span><span style="font-weight: bold; color: grey;">&gt;</span></span><br />
&nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;xsl</span>:copy<span style="font-weight: bold; color: grey;">&gt;</span></span><br />
&nbsp; &nbsp; &nbsp; &nbsp;<span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;xsl</span>:apply-templates<span style="font-weight: bold; color: grey;">/&gt;</span></span><br />
&nbsp; &nbsp; &nbsp;<span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/xsl</span>:copy<span style="font-weight: bold; color: grey;">&gt;</span></span><br />
&nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/xsl</span>:template<span style="font-weight: bold; color: grey;">&gt;</span></span><br />
<span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/xsl</span>:stylesheet<span style="font-weight: bold; color: grey;">&gt;</span></span></div>
</div></div>

<p><br /></p>

<p>Basically, this XSL stylesheet copies most of the original tags (<code>&lt;xsl:copy&gt;</code>), but does special operations for elements that should be attributes (like <code>&lt;default&gt;</code>), or that should be renamed. This stylesheet defines a "semantical correction" for the automatically created XML translation of the YAML file, and is the first step of the validation. Of course, you need to define one XSLT file for each type of YAML file you want to validate.</p>

<p>How to apply this XSLT to the XML version of the YAML file in PHP? Using the powerful capabilities of PHP in XML, it is extremely simple:</p>

<div class="syntax_hilite"><div id="php-23">
<div class="php"><span style="color:#FF9933; font-style:italic;">// Transform the XML using XSLT</span><br />
<span style="color:#FF9933; font-style:italic;">// Load the simple XML transformation into a DOMDocument object</span><br />
<span style="color:#0088FF;">$xmlDoc</span> = <span style="color:#000000; font-weight:bold;color: grey;">new</span> DomDocument;<br />
<span style="color:#0088FF;">$xmlDoc</span>-&gt;<span style="color:#00AA00;">loadXML</span><span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#0088FF;">$xmlString</span><span style="color:#00AA00; font-weight:bold;">&#41;</span>;<br />
<span style="color:#FF9933; font-style:italic;">// Load the XSD stylesheet into another DOMDocument object</span><br />
<span style="color:#0088FF;">$xslDoc</span> = <span style="color:#000000; font-weight:bold;color: grey;">new</span> DomDocument;<br />
<span style="color:#0088FF;">$xslDoc</span>-&gt;<span style="color:#00AA00;">load</span><span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#FF0000;color: #AF8E99;">'view.yml.xsd'</span><span style="color:#00AA00; font-weight:bold;">&#41;</span>;<br />
<span style="color:#FF9933; font-style:italic;">// Proceed with transformation using an XsltProcessor object</span><br />
<span style="color:#0088FF;">$xsltp</span> = <span style="color:#000000; font-weight:bold;color: grey;">new</span> XsltProcessor<span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#00AA00; font-weight:bold;">&#41;</span>;<br />
<span style="color:#0088FF;">$xsltp</span>-&gt;<span style="color:#00AA00;">importStylesheet</span><span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#0088FF;">$xslDoc</span><span style="color:#00AA00; font-weight:bold;">&#41;</span>;<br />
<span style="color:#616100;color: grey;">if</span> <span style="color:#00AA00; font-weight:bold;">&#40;</span>!<span style="color:#0088FF;">$xmlTransformed</span> = <span style="color:#0088FF;">$xsltp</span>-&gt;<span style="color:#00AA00;">transformToDoc</span><span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#0088FF;">$xmlDoc</span><span style="color:#00AA00; font-weight:bold;">&#41;</span><span style="color:#00AA00; font-weight:bold;">&#41;</span><br />
<span style="color:#00AA00; font-weight:bold;">&#123;</span><br />
&nbsp; throw <span style="color:#000000; font-weight:bold;color: grey;">new</span> Exception<span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#FF0000;color: #AF8E99;">'XSL transformation failed.'</span><span style="color:#00AA00; font-weight:bold;">&#41;</span>;<br />
<span style="color:#00AA00; font-weight:bold;">&#125;</span></div>
</div></div>

<p><br /></p>

<h2>Validating</h2>

<p>Validating the semantically correct XML file is quite basic: write an <a href="http://www.w3schools.com/schema/default.asp">XML Schema</a>, or XSD, describing the syntax expected in a <code>view.yml.xml</code>. You could do it with a DTD instead of and XSD, but XSD is more powerful. Here is a simple schema defining a grammar to validate <code>view.yml.xml</code> files:</p>

<div class="syntax_hilite"><div id="xml-24">
<div class="xml"><span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;</span>?xml <span style="color: #999966;">version</span>=<span style="color: #ff0000;color: #AF8E99;">"1.0"</span>?<span style="font-weight: bold; color: grey;">&gt;</span></span><br />
<span style="color: #009900;"><span style="color: #808080; font-style: italic;">&lt;!-- view.yml.xsd --&gt;</span></span><br />
<span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;xs</span>:schema xmlns:<span style="color: #999966;">xs</span>=<span style="color: #ff0000;color: #AF8E99;">"http://www.w3.org/2001/XMLSchema"</span> <span style="color: #999966;">elementFormDefault</span>=<span style="color: #ff0000;color: #AF8E99;">"qualified"</span><span style="font-weight: bold; color: grey;">&gt;</span></span><br />
&nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;xs</span>:element <span style="color: #999966;">name</span>=<span style="color: #ff0000;color: #AF8E99;">"templates"</span><span style="font-weight: bold; color: grey;">&gt;</span></span><br />
&nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;xs</span>:complexType<span style="font-weight: bold; color: grey;">&gt;</span></span><br />
&nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;xs</span>:sequence<span style="font-weight: bold; color: grey;">&gt;</span></span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;xs</span>:element <span style="color: #999966;">name</span>=<span style="color: #ff0000;color: #AF8E99;">"template"</span> <span style="color: #999966;">maxOccurs</span>=<span style="color: #ff0000;color: #AF8E99;">"unbounded"</span><span style="font-weight: bold; color: grey;">&gt;</span></span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;xs</span>:complexType <span style="color: #999966;">mixed</span>=<span style="color: #ff0000;color: #AF8E99;">"true"</span><span style="font-weight: bold; color: grey;">&gt;</span></span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;xs</span>:all<span style="font-weight: bold; color: grey;">&gt;</span></span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;xs</span>:element <span style="color: #999966;">name</span>=<span style="color: #ff0000;color: #AF8E99;">"httpmetas"</span> <span style="color: #999966;">minOccurs</span>=<span style="color: #ff0000;color: #AF8E99;">"0"</span><span style="font-weight: bold; color: grey;">&gt;</span></span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;xs</span>:complexType<span style="font-weight: bold; color: grey;">&gt;</span></span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;xs</span>:all<span style="font-weight: bold; color: grey;">&gt;</span></span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;xs</span>:element <span style="color: #999966;">name</span>=<span style="color: #ff0000;color: #AF8E99;">"contenttype"</span> <span style="color: #999966;">type</span>=<span style="color: #ff0000;color: #AF8E99;">"xs:string"</span><span style="font-weight: bold; color: grey;">/&gt;</span></span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/xs</span>:all<span style="font-weight: bold; color: grey;">&gt;</span></span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/xs</span>:complexType<span style="font-weight: bold; color: grey;">&gt;</span></span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/xs</span>:element<span style="font-weight: bold; color: grey;">&gt;</span></span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;xs</span>:element <span style="color: #999966;">name</span>=<span style="color: #ff0000;color: #AF8E99;">"metas"</span> <span style="color: #999966;">minOccurs</span>=<span style="color: #ff0000;color: #AF8E99;">"0"</span><span style="font-weight: bold; color: grey;">&gt;</span></span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;xs</span>:complexType<span style="font-weight: bold; color: grey;">&gt;</span></span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;xs</span>:all<span style="font-weight: bold; color: grey;">&gt;</span></span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;xs</span>:element <span style="color: #999966;">name</span>=<span style="color: #ff0000;color: #AF8E99;">"title"</span> <span style="color: #999966;">type</span>=<span style="color: #ff0000;color: #AF8E99;">"xs:string"</span> <span style="color: #999966;">minOccurs</span>=<span style="color: #ff0000;color: #AF8E99;">"0"</span><span style="font-weight: bold; color: grey;">/&gt;</span></span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;xs</span>:element <span style="color: #999966;">name</span>=<span style="color: #ff0000;color: #AF8E99;">"robots"</span> <span style="color: #999966;">type</span>=<span style="color: #ff0000;color: #AF8E99;">"xs:string"</span> <span style="color: #999966;">minOccurs</span>=<span style="color: #ff0000;color: #AF8E99;">"0"</span><span style="font-weight: bold; color: grey;">/&gt;</span></span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;xs</span>:element <span style="color: #999966;">name</span>=<span style="color: #ff0000;color: #AF8E99;">"description"</span> <span style="color: #999966;">type</span>=<span style="color: #ff0000;color: #AF8E99;">"xs:string"</span> <span style="color: #999966;">minOccurs</span>=<span style="color: #ff0000;color: #AF8E99;">"0"</span><span style="font-weight: bold; color: grey;">/&gt;</span></span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;xs</span>:element <span style="color: #999966;">name</span>=<span style="color: #ff0000;color: #AF8E99;">"keywords"</span> <span style="color: #999966;">type</span>=<span style="color: #ff0000;color: #AF8E99;">"xs:string"</span> <span style="color: #999966;">minOccurs</span>=<span style="color: #ff0000;color: #AF8E99;">"0"</span><span style="font-weight: bold; color: grey;">/&gt;</span></span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;xs</span>:element <span style="color: #999966;">name</span>=<span style="color: #ff0000;color: #AF8E99;">"language"</span> <span style="color: #999966;">type</span>=<span style="color: #ff0000;color: #AF8E99;">"xs:string"</span> <span style="color: #999966;">minOccurs</span>=<span style="color: #ff0000;color: #AF8E99;">"0"</span><span style="font-weight: bold; color: grey;">/&gt;</span></span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/xs</span>:all<span style="font-weight: bold; color: grey;">&gt;</span></span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/xs</span>:complexType<span style="font-weight: bold; color: grey;">&gt;</span></span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/xs</span>:element<span style="font-weight: bold; color: grey;">&gt;</span></span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;xs</span>:element <span style="color: #999966;">name</span>=<span style="color: #ff0000;color: #AF8E99;">"stylesheets"</span> <span style="color: #999966;">minOccurs</span>=<span style="color: #ff0000;color: #AF8E99;">"0"</span><span style="font-weight: bold; color: grey;">&gt;</span></span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;xs</span>:complexType<span style="font-weight: bold; color: grey;">&gt;</span></span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;xs</span>:sequence<span style="font-weight: bold; color: grey;">&gt;</span></span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;xs</span>:element <span style="color: #999966;">name</span>=<span style="color: #ff0000;color: #AF8E99;">"stylesheet"</span> <span style="color: #999966;">type</span>=<span style="color: #ff0000;color: #AF8E99;">"xs:string"</span> <span style="color: #999966;">maxOccurs</span>=<span style="color: #ff0000;color: #AF8E99;">"unbounded"</span><span style="font-weight: bold; color: grey;">/&gt;</span></span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/xs</span>:sequence<span style="font-weight: bold; color: grey;">&gt;</span></span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/xs</span>:complexType<span style="font-weight: bold; color: grey;">&gt;</span></span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/xs</span>:element<span style="font-weight: bold; color: grey;">&gt;</span></span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;xs</span>:element <span style="color: #999966;">name</span>=<span style="color: #ff0000;color: #AF8E99;">"javascripts"</span> <span style="color: #999966;">minOccurs</span>=<span style="color: #ff0000;color: #AF8E99;">"0"</span><span style="font-weight: bold; color: grey;">&gt;</span></span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;xs</span>:complexType<span style="font-weight: bold; color: grey;">&gt;</span></span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;xs</span>:sequence<span style="font-weight: bold; color: grey;">&gt;</span></span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;xs</span>:element <span style="color: #999966;">name</span>=<span style="color: #ff0000;color: #AF8E99;">"javascript"</span> <span style="color: #999966;">type</span>=<span style="color: #ff0000;color: #AF8E99;">"xs:string"</span> <span style="color: #999966;">maxOccurs</span>=<span style="color: #ff0000;color: #AF8E99;">"unbounded"</span><span style="font-weight: bold; color: grey;">/&gt;</span></span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/xs</span>:sequence<span style="font-weight: bold; color: grey;">&gt;</span></span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/xs</span>:complexType<span style="font-weight: bold; color: grey;">&gt;</span></span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/xs</span>:element<span style="font-weight: bold; color: grey;">&gt;</span></span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;xs</span>:element <span style="color: #999966;">name</span>=<span style="color: #ff0000;color: #AF8E99;">"haslayout"</span> <span style="color: #999966;">type</span>=<span style="color: #ff0000;color: #AF8E99;">"xs:integer"</span> <span style="color: #999966;">minOccurs</span>=<span style="color: #ff0000;color: #AF8E99;">"0"</span><span style="font-weight: bold; color: grey;">/&gt;</span></span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;xs</span>:element <span style="color: #999966;">name</span>=<span style="color: #ff0000;color: #AF8E99;">"layout"</span> <span style="color: #999966;">type</span>=<span style="color: #ff0000;color: #AF8E99;">"xs:string"</span> <span style="color: #999966;">minOccurs</span>=<span style="color: #ff0000;color: #AF8E99;">"0"</span><span style="font-weight: bold; color: grey;">/&gt;</span></span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/xs</span>:all<span style="font-weight: bold; color: grey;">&gt;</span></span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;xs</span>:attribute <span style="color: #999966;">name</span>=<span style="color: #ff0000;color: #AF8E99;">"name"</span> <span style="color: #999966;">type</span>=<span style="color: #ff0000;color: #AF8E99;">"xs:string"</span> <span style="color: #999966;">use</span>=<span style="color: #ff0000;color: #AF8E99;">"required"</span><span style="font-weight: bold; color: grey;">/&gt;</span></span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/xs</span>:complexType<span style="font-weight: bold; color: grey;">&gt;</span></span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/xs</span>:element<span style="font-weight: bold; color: grey;">&gt;</span></span><br />
&nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/xs</span>:sequence<span style="font-weight: bold; color: grey;">&gt;</span></span><br />
&nbsp; &nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/xs</span>:complexType<span style="font-weight: bold; color: grey;">&gt;</span></span><br />
&nbsp; <span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/xs</span>:element<span style="font-weight: bold; color: grey;">&gt;</span></span><br />
<span style="color: #009900;"><span style="font-weight: bold; color: grey;">&lt;/xs</span>:schema<span style="font-weight: bold; color: grey;">&gt;</span></span></div>
</div></div>

<p><br /></p>

<p>Note: I know this doesn't cover all cases; it is mostly a proof of concept.</p>

<p>Now, you need to check the XML file against that schema. Once again, the powerful XML manipulation library of PHP makes it a piece of cake:</p>

<div class="syntax_hilite"><div id="php-25">
<div class="php"><span style="color:#FF9933; font-style:italic;">// validate the new XML against and XSD</span><br />
<span style="color:#FF9933; font-style:italic;">// $xmlTransformed is the semantically correct XML translation of the YAML file defined earlier</span><br />
<span style="color:#616100;color: grey;">if</span><span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#0088FF;">$xmlTransformed</span>-&gt;<span style="color:#00AA00;">schemaValidate</span><span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#FF0000;color: #AF8E99;">'view.yml.xsd'</span><span style="color:#00AA00; font-weight:bold;">&#41;</span><span style="color:#00AA00; font-weight:bold;">&#41;</span><br />
<span style="color:#00AA00; font-weight:bold;">&#123;</span><br />
&nbsp; <span style="color:#616100;color: grey;">return</span> <span style="color:#000000; font-weight:bold;color: grey;">true</span>;<br />
<span style="color:#00AA00; font-weight:bold;">&#125;</span><br />
<span style="color:#616100;color: grey;">else</span><br />
<span style="color:#00AA00; font-weight:bold;">&#123;</span><br />
&nbsp; <span style="color:#FF9933; font-style:italic;">// display errors</span><br />
<span style="color:#00AA00; font-weight:bold;">&#125;</span></div>
</div></div>

<p><br /></p>

<h2>Dealing with libxml errors</h2>

<p>By default, <code>DOMDocument::schemaValidate()</code> will only return <code>true</code> if the XML file is valid, and false otherwise. But a good validation utility needs to be more verbose than that, and display errors where a files doesn't validate. In order to do that, you need to manually fetch the libxml errors when the validation fails, as explained in the <a href="http://fr2.php.net/manual/en/domdocument.schemavalidate.php">PHP Manual</a>.</p>

<div class="syntax_hilite"><div id="php-26">
<div class="php">libxml_use_internal_errors<span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#000000; font-weight:bold;color: grey;">true</span><span style="color:#00AA00; font-weight:bold;">&#41;</span>;<br />
<span style="color:#FF9933; font-style:italic;">// validate the new XML against and XSD</span><br />
<span style="color:#FF9933; font-style:italic;">// $xmlTransformed is the semantically correct XML translation of the YAML file defined earlier</span><br />
<span style="color:#616100;color: grey;">if</span><span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#0088FF;">$xmlTransformed</span>-&gt;<span style="color:#00AA00;">schemaValidate</span><span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#FF0000;color: #AF8E99;">'view.yml.xsd'</span><span style="color:#00AA00; font-weight:bold;">&#41;</span><span style="color:#00AA00; font-weight:bold;">&#41;</span><br />
<span style="color:#00AA00; font-weight:bold;">&#123;</span><br />
&nbsp; <span style="color:#616100;color: grey;">return</span> <span style="color:#000000; font-weight:bold;color: grey;">true</span>;<br />
<span style="color:#00AA00; font-weight:bold;">&#125;</span><br />
<span style="color:#616100;color: grey;">else</span><br />
<span style="color:#00AA00; font-weight:bold;">&#123;</span><br />
&nbsp; <span style="color:#FF9933; font-style:italic;">// display errors</span><br />
&nbsp; <span style="color:#0088FF;">$errors</span> = libxml_get_errors<span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#00AA00; font-weight:bold;">&#41;</span>;<br />
&nbsp; <span style="color:#0088FF;">$message</span> = <span style="color:#FF0000;color: #AF8E99;">"n"</span>;<br />
&nbsp; <span style="color:#616100;color: grey;">foreach</span> <span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#0088FF;">$errors</span> <span style="color:#616100;color: grey;">as</span> <span style="color:#0088FF;">$error</span><span style="color:#00AA00; font-weight:bold;">&#41;</span><br />
&nbsp; <span style="color:#00AA00; font-weight:bold;">&#123;</span><br />
&nbsp; &nbsp; <span style="color:#0088FF;">$message</span> .= <a href="http://www.php.net/trim"><span style="color:#000066;color: white;">trim</span></a><span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#0088FF;">$error</span>-&gt;<span style="color:#00AA00;">message</span><span style="color:#00AA00; font-weight:bold;">&#41;</span> . <span style="color:#FF0000;color: #AF8E99;">' ('</span>;<br />
&nbsp; &nbsp; <span style="color:#616100;color: grey;">switch</span> <span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#0088FF;">$error</span>-&gt;<span style="color:#00AA00;">level</span><span style="color:#00AA00; font-weight:bold;">&#41;</span><br />
&nbsp; &nbsp; <span style="color:#00AA00; font-weight:bold;">&#123;</span><br />
&nbsp; &nbsp; &nbsp; <span style="color:#616100;color: grey;">case</span> LIBXML_ERR_WARNING:<br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color:#0088FF;">$return</span> .= <span style="color:#FF0000;color: #AF8E99;">"Warning $error-&gt;code"</span>;<br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color:#616100;color: grey;">break</span>;<br />
&nbsp; &nbsp; &nbsp; <span style="color:#616100;color: grey;">case</span> LIBXML_ERR_ERROR:<br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color:#0088FF;">$return</span> .= <span style="color:#FF0000;color: #AF8E99;">"Error $error-&gt;code"</span>;<br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color:#616100;color: grey;">break</span>;<br />
&nbsp; &nbsp; &nbsp; <span style="color:#616100;color: grey;">case</span> LIBXML_ERR_FATAL:<br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color:#0088FF;">$return</span> .= <span style="color:#FF0000;color: #AF8E99;">"Fatal Error $error-&gt;code"</span>;<br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color:#616100;color: grey;">break</span>;<br />
&nbsp; &nbsp; <span style="color:#00AA00; font-weight:bold;">&#125;</span><br />
&nbsp; &nbsp; <span style="color:#616100;color: grey;">if</span> <span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#0088FF;">$error</span>-&gt;<span style="color:#00AA00;">file</span><span style="color:#00AA00; font-weight:bold;">&#41;</span><br />
&nbsp; &nbsp; <span style="color:#00AA00; font-weight:bold;">&#123;</span><br />
&nbsp; &nbsp; &nbsp; <span style="color:#0088FF;">$message</span> .= <span style="color:#FF0000;color: #AF8E99;">" in $error-&gt;file"</span>;<br />
&nbsp; &nbsp; <span style="color:#00AA00; font-weight:bold;">&#125;</span><br />
&nbsp; &nbsp; <span style="color:#0088FF;">$message</span> .= <span style="color:#FF0000;color: #AF8E99;">" on line $error-&gt;line)n"</span>;<br />
&nbsp; <span style="color:#00AA00; font-weight:bold;">&#125;</span><br />
&nbsp; libxml_clear_errors<span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#00AA00; font-weight:bold;">&#41;</span>;<br />
<br />
&nbsp; throw <span style="color:#000000; font-weight:bold;color: grey;">new</span> Exception<span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#0088FF;">$message</span><span style="color:#00AA00; font-weight:bold;">&#41;</span>;<br />
<span style="color:#00AA00; font-weight:bold;">&#125;</span></div>
</div></div>

<p><br /></p>

<p>That's all. Now all it takes to validate any <code>view.yml</code> file are the XSLT and the XSD grammars. If a <code>view.yml</code> ever contains an incorrect setting, say:</p>

<pre><code>default:
  foo: bar
</code></pre>

<p>Then an exception will be raised with a meaningful error message:</p>

<pre><code>Element 'foo': This element is not expected. (Error 1871 on line 2)
</code></pre>

<h2>Wrapping it up</h2>

<p>The idea can be easily transposed to any YAML file. A YAML validator should:</p>

<ol>
<li>Turn a YAML file into a PHP associative array using <code>sfYaml</code></li>
<li>Turn this array into an XML structure, in a brute and blind way</li>
<li>Turn the XML structure into a second XML structure using a set of XSLT rules to make the structure semantically correct</li>
<li>Validate the second XML structure using an XML Schema</li>
<li>If errors appear, return them wrapped up in an exception</li>
</ol>

<p>To validate, say the <code>generator.yml</code> in symfony, all it takes is a <code>generator.yml.xsl</code> and a <code>generator.yml.xsd</code> to define the expected grammar in this file.</p>

<h2>Ironic, isn't it?</h2>

<p>You could say that the idea behind YAML is to avoid writing XML files. So using XML, XSD and XSLT in order to validate a YAML file may look a bit counter-intuitive, if not ironic.</p>

<p>But when you put it all together, the code necessary to validate <em>any</em> YAML file (not including, or course, the XSLT and XSD grammars, which depend on the file you validate) take only a few dozen lines. Besides, PHP is very good at handling XML, so it's better to use it for its strong points, instead of trying to mimic another language an end up writing thousands of lines of code. Actually, the 'K.I.S.S.' principle that encourages the use of YAML for configuration files should also apply here: XML manipulation is the simplest way to validate a YAML file, so it's the right tool for the job.</p>

<p>Last but not least, revolutions sometimes look backwards - think about the Renaissance. So using XML to validate YAML is probably not as dumb as it sounds.</p>

<p>The full YAML validator code is attached below, together with the example YAML file for your testing pleasure. Once again, I'm not a developer, so the code is just there to prove that the idea works. It could probably be much improved.</p>

<p><a href='http://redotheweb.com/wp-content/uploads/2008/09/yaml_validation.zip' title='Source code + example YAML file and validator schemas'>Source code + example YAML file and validator schemas</a></p>

<p>Including the YAML validation system in a web application framework that uses YAML is a must. Validation should only be done in development environment, of course, and only when the YAML files change. Symfony uses a configuration cache system with a set of configuration handlers that would make validation very easy and efficient. Let alone other frameworks in PHP, or in other languages, who could also take advantage of a similar approach.</p>

<p>Oh, and there is one more thing: The semantically correct XML file and its XSD syntax define a perfect XML equivalent to YAML files in symfony. If you want to use XML instead of YAML, and write your own configuration handlers, you should probably follow this kind of syntax.</p>
]]></content:encoded>
			<wfw:commentRss>http://redotheweb.com/2008/09/09/validating-a-yaml-file-against-a-schema-in-php/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Everybody Goes to Symfony Camp</title>
		<link>http://redotheweb.com/2008/08/29/everybody-goes-to-symfony-camp/</link>
		<comments>http://redotheweb.com/2008/08/29/everybody-goes-to-symfony-camp/#comments</comments>
		<pubDate>Fri, 29 Aug 2008 19:20:28 +0000</pubDate>
		<dc:creator>Francois Zaninotto</dc:creator>
		
		<category><![CDATA[DbFinder]]></category>

		<category><![CDATA[events]]></category>

		<category><![CDATA[framework]]></category>

		<category><![CDATA[symfony]]></category>

		<guid isPermaLink="false">http://redotheweb.com/2008/08/29/everybody-goes-to-symfony-camp/</guid>
		<description><![CDATA[And that includes me. I will be giving a presentation there, in two weeks from now, called "Developing for Developers - Usability Applied to Programming", and illustrated by my recent work on DbFinderPlugin.

If you'd like to meet the best PHP5 developers in the world, or me, you should definitely go to the Symfony Camp on [...]]]></description>
			<content:encoded><![CDATA[<p>And that includes me. I will be giving a presentation there, in two weeks from now, called "Developing for Developers - Usability Applied to Programming", and illustrated by my recent work on <a href="http://redotheweb.com/category/dbfinder/">DbFinderPlugin</a>.</p>

<p>If you'd like to meet the best PHP5 developers in the world, or me, you should definitely go to the <a href="http://www.symfonycamp.com/cms/home">Symfony Camp</a> on September 12th and 13th. The conference is in The Netherlands, not far from Amsterdam - that means not far from anybody in Europe. I heard there are some tickets left for the conference, but it won't last long. The price is not free, but with the great people talking there, the tasty barbecue, the unique atmosphere and a huge lawn to put your tent in, it's a bargain. Besides, they may fill the swimming pool this year.</p>

<p>We'll have plenty of time to speak about symfony, plugins, documentation, the future and everything else. It's a unique opportunity to meet in person all those who lead the symfony community. Also, there are one or two seats left for the training session, so if you want to become operational in symfony quickly, <a href="http://www.symfonycamp.com/registration/create">dive in</a>.</p>

<p>One last world for those who expect drama: There Won't Be Blood.</p>
]]></content:encoded>
			<wfw:commentRss>http://redotheweb.com/2008/08/29/everybody-goes-to-symfony-camp/feed/</wfw:commentRss>
		</item>
		<item>
		<title>sfPropelFinder becomes DbFinder - Announcing 1.0 release</title>
		<link>http://redotheweb.com/2008/08/28/sfpropelfinder-becomes-dbfinder-announcing-10-release/</link>
		<comments>http://redotheweb.com/2008/08/28/sfpropelfinder-becomes-dbfinder-announcing-10-release/#comments</comments>
		<pubDate>Thu, 28 Aug 2008 14:22:00 +0000</pubDate>
		<dc:creator>Francois Zaninotto</dc:creator>
		
		<category><![CDATA[DbFinder]]></category>

		<category><![CDATA[plugins]]></category>

		<category><![CDATA[symfony]]></category>

		<guid isPermaLink="false">http://redotheweb.com/2008/08/28/sfpropelfinder-becomes-dbfinder-announcing-10-release/</guid>
		<description><![CDATA[The sfPropelFinder plugin, which I've told you about a lot lately, has recently been renamed to DbFinder. This emphasizes the fact that the plugin is not Propel-specific anymore, and that you can use it with Doctrine without any change in the API.

Also, I have released a version 0.9 of the plugin today, which marks the [...]]]></description>
			<content:encoded><![CDATA[<p>The <a href="http://redotheweb.com/category/dbfinder/">sfPropelFinder</a> plugin, which I've told you about a lot lately, has recently been renamed to <a href="http://www.symfony-project.org/plugins/DbFinderPlugin">DbFinder</a>. This emphasizes the fact that the plugin is not Propel-specific anymore, and that you can use it with Doctrine <em>without any change in the API</em>.</p>

<p>Also, I have released a version 0.9 of the plugin today, which marks the <strong>100% coverage of the API with both the Propel and the Doctrine adapters</strong>. That's right, now any piece of code using <code>DbFinder</code> will work seamlessly, whatever the ORM you use in symfony.</p>

<p>Take the following code, for instance:</p>

<div class="syntax_hilite"><div id="php-28">
<div class="php"><span style="color:#FF9933; font-style:italic;">// Look in the Article model</span><br />
<span style="color:#FF9933; font-style:italic;">// For objects where the author object related to the article has $nickname for nickname</span><br />
<span style="color:#FF9933; font-style:italic;">// Hydrated with related translation in the current culture and category</span><br />
<span style="color:#FF9933; font-style:italic;">// And put the result into a pager implementing sfPager for easy display in a web page</span><br />
<span style="color:#0088FF;">$pager</span> = DbFinder::<span style="color:#00AA00;">from</span><span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#FF0000;color: #AF8E99;">'Article'</span><span style="color:#00AA00; font-weight:bold;">&#41;</span>-&gt;<br />
&nbsp; <span style="color:#00AA00;">where</span><span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#FF0000;color: #AF8E99;">'Author.Nickname'</span>, <span style="color:#0088FF;">$nickname</span><span style="color:#00AA00; font-weight:bold;">&#41;</span>-&gt;<br />
&nbsp; <span style="color:#00AA00;">with</span><span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#FF0000;color: #AF8E99;">'I18n'</span>, <span style="color:#FF0000;color: #AF8E99;">'Category'</span><span style="color:#00AA00; font-weight:bold;">&#41;</span>-&gt;<br />
&nbsp; <span style="color:#00AA00;">paginate</span><span style="color:#00AA00; font-weight:bold;">&#40;</span><span style="color:#0088FF;">$currentPage</span> = <span style="color:#CC66CC;">1</span>, <span style="color:#0088FF;">$maxResultsPerPage</span> = <span style="color:#CC66CC;">10</span><span style="color:#00AA00; font-weight:bold;">&#41;</span>;</div>
</div></div>

<p><br /></p>

<p>Getting the same result with either Propel or Doctrine takes considerably more code.</p>

<p>To be honest, the Doctrine coverage is only 99%, since there is still an issue with <code>sfDoctrineFinder::withColumn()</code> when dealing with a calculated column - and this is something that requires Doctrine 1.0 to be fixed. The current Doctrine adapter is based on <a href="http://www.symfony-project.org/plugins/sfDoctrinePlugin">sfDoctrinePlugin</a> and <a href="http://www.doctrine-project.org/documentation/manual/0_11">Doctrine 0.11</a>. But as soon as Doctrine 1.0 is released, <code>withColumn()</code> will be updated to work exactly the same as with Propel.</p>

<p>This release can be considered as a 1.0 beta 1 - meaning I'll probably not add more features before releasing a stable version. I'll work on performance and edge cases if bugs are reported, so you are encouraged to download the plugin, test it, and give me as much feedback as you can.</p>
]]></content:encoded>
			<wfw:commentRss>http://redotheweb.com/2008/08/28/sfpropelfinder-becomes-dbfinder-announcing-10-release/feed/</wfw:commentRss>
		</item>
	</channel>
</rss>

