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

<channel>
	<title>-= n8v =- &#187; symfony</title>
	<atom:link href="http://n8v.enteuxis.org/tags/symfony/feed/" rel="self" type="application/rss+xml" />
	<link>http://n8v.enteuxis.org</link>
	<description>Just another WordPress weblog</description>
	<lastBuildDate>Tue, 27 Sep 2011 00:52:53 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.2-beta2-18055</generator>
		<item>
		<title>PHPUnit for XAMPP on Windows with Cygwin</title>
		<link>http://n8v.enteuxis.org/2011/09/phpunit-for-xampp-on-windows-with-cygwin/</link>
		<comments>http://n8v.enteuxis.org/2011/09/phpunit-for-xampp-on-windows-with-cygwin/#comments</comments>
		<pubDate>Mon, 26 Sep 2011 23:30:41 +0000</pubDate>
		<dc:creator>nathan</dc:creator>
				<category><![CDATA[Figuring IT Out]]></category>
		<category><![CDATA[Cygwin]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[phpunit]]></category>
		<category><![CDATA[symfony]]></category>
		<category><![CDATA[Windows]]></category>
		<category><![CDATA[xampp]]></category>

		<guid isPermaLink="false">http://n8v.enteuxis.org/?p=510</guid>
		<description><![CDATA[Steps to get PHPUnit to run on my XAMPP setup with Cygwin, so I can write and run Symfony2 unit tests. 1. Upgrade PEAR Download http://pear.php.net/go-pear.phar to C:\xampp\php\go-pear.phar Run this in cmd.exe (cygwin prompts hosed somehow), taking all defaults c:\xampp\php&#62;go-pear.bat Yay. c:\xampp\php&#62;pear version PEAR Version: 1.9.4 PHP Version: 5.3.5 Zend Engine Version: 2.3.0 Running on: [...]]]></description>
			<content:encoded><![CDATA[<p>Steps to get PHPUnit to run on my XAMPP setup with Cygwin, so I can write and run Symfony2 unit tests.</p>

<h3>1. Upgrade PEAR</h3>

<ol>
<li><p>Download <code>http://pear.php.net/go-pear.phar</code>  to <var>C:\xampp\php\go-pear.phar</var></p></li>
<li><p>Run this <strong>in <var>cmd.exe</var></strong> (cygwin prompts hosed somehow), taking all defaults</p></li>
</ol>


<div class="wp_syntax"><div class="code"><pre class="dos">c:\xampp\php<span class="sy0">&gt;</span>go-pear.bat</pre></div></div>


<ol>
<li>Yay.</li>
</ol>


<div class="wp_syntax"><div class="code"><pre class="dos">c:\xampp\php<span class="sy0">&gt;</span>pear version
PEAR Version: 1.9.4
PHP Version: 5.3.5
Zend Engine Version: 2.3.0
Running on: Windows NT FAI1046162 6.1 build 7600 <span class="sy0">(</span>Unknow Windows version Enterpr
ise Edition<span class="sy0">)</span> i586</pre></div></div>


<h3>2. Install/upgrade PHPUnit</h3>

<p>Now we can use the cygwin shell.  Not sure <a href="http://forum.kohanaframework.org/discussion/7346/installing-phpunit-on-windows-xampp/p1">all these channels are needed</a>, I did this out of order.</p>


<div class="wp_syntax"><div class="code"><pre class="bash"><span class="kw3">cd</span> <span class="sy0">/</span>cygdrive<span class="sy0">/</span>c<span class="sy0">/</span>xampp<span class="sy0">/</span>php
pear update-channels
pear channel-discover components.ez.no
pear channel-discover pear.symfony-project.com
pear channel-discover pear.phpunit.de
&nbsp;
pear <span class="kw2">install</span> <span class="re5">--alldeps</span> phpunit<span class="sy0">/</span>PHPUnit</pre></div></div>


<h3>3. Use it</h3>

<p>XAMPP and/or PHPUnit come with a wrapper called <var>phpunit.bat</var>, which has now been upgraded, but you may need to set your <var>PHPBIN</var> environment var. Also I already have <var>c:\xampp\php</var> in my <var>$PATH</var>.</p>


<div class="wp_syntax"><div class="code"><pre class="bash"><span class="kw3">export</span> <span class="re2">PHPBIN</span>=c:<span class="sy0">/</span>xampp<span class="sy0">/</span>php<span class="sy0">/</span>php.exe
&nbsp;
<span class="kw3">cd</span> <span class="sy0">/</span>path<span class="sy0">/</span>to<span class="sy0">/</span>mysf2project
phpunit.bat <span class="re5">-c</span> app</pre></div></div>


<p>Now PHPUnit works. Make tests and make them work!</p>
]]></content:encoded>
			<wfw:commentRss>http://n8v.enteuxis.org/2011/09/phpunit-for-xampp-on-windows-with-cygwin/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Sorting, deleting from a PropelObjectCollection</title>
		<link>http://n8v.enteuxis.org/2011/06/sorting-deleting-propel-object-collectio/</link>
		<comments>http://n8v.enteuxis.org/2011/06/sorting-deleting-propel-object-collectio/#comments</comments>
		<pubDate>Thu, 30 Jun 2011 06:23:13 +0000</pubDate>
		<dc:creator>nathan</dc:creator>
				<category><![CDATA[Figuring IT Out]]></category>
		<category><![CDATA[orm]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[propel]]></category>
		<category><![CDATA[symfony]]></category>
		<category><![CDATA[symfony1.4]]></category>

		<guid isPermaLink="false">http://n8v.enteuxis.org/?p=503</guid>
		<description><![CDATA[Propel 1.6 (and Propel 1.5 before it) is pretty sweet (thank you François!). I had some confusion with my model&#8217;s array of related objects though, thinking it was a regular PHP array. Actually it&#8217;s a Collection, specifically a PropelObjectCollection, which implements PHP 5&#8242;s ArrayObject interface. You can do a lot of cool things with them. [...]]]></description>
			<content:encoded><![CDATA[<p>Propel 1.6 (and Propel 1.5 before it) is pretty sweet (thank you François!). I had some confusion with my model&#8217;s array of related objects though, thinking it was a regular PHP array. Actually it&#8217;s a <em>Collection</em>, specifically a PropelObjectCollection, which implements <a href="http://www.php.net/manual/en/class.arrayobject.php">PHP 5&#8242;s ArrayObject interface</a>. <a href="http://propel.posterous.com/propel-gets-collections">You can do a lot of cool things with them.</a></p>

<h2>Sorting</h2>

<p>Not immediately obvious, however, was how to sort them. This did the trick for my case (I have a Sequence field manually re-calculable through a <a href="http://jqueryui.com/demos/sortable/">jQueryUI sortable widget</a>. Also note the cool <a href="http://php.net/manual/en/functions.anonymous.php">inline anonymous function syntax</a> available since PHP 5.3. Incidentally, I&#8217;m not sure the terms <em>lambda</em> or <em>closure</em> are helpful because they&#8217;re not <em>quite</em> like Lisp lambdas or JavaScript closures.</p>


<div class="wp_syntax"><div class="code"><pre class="php"><span class="co1">// Re-sort them by Sequence, numerically</span>
<span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">collSegments</span><span class="sy0">-&gt;</span><span class="me1">uasort</span><span class="br0">&#40;</span><span class="kw2">function</span><span class="br0">&#40;</span><span class="re0">$a</span><span class="sy0">,</span> <span class="re0">$b</span><span class="br0">&#41;</span> <span class="br0">&#123;</span>
    <span class="kw1">return</span> <span class="re0">$a</span><span class="sy0">-&gt;</span><span class="me1">getSequence</span><span class="br0">&#40;</span><span class="br0">&#41;</span> <span class="sy0">-</span> <span class="re0">$b</span><span class="sy0">-&gt;</span><span class="me1">getSequence</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">;</span>
<span class="br0">&#125;</span><span class="br0">&#41;</span><span class="sy0">;</span></pre></div></div>



<div class="wp_syntax"><div class="code"><pre class="php"><span class="co1">// Re-sort them as strings, case-insensitively.</span>
<span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">collSegments</span><span class="sy0">-&gt;</span><span class="me1">uasort</span><span class="br0">&#40;</span><span class="kw2">function</span><span class="br0">&#40;</span><span class="re0">$a</span><span class="sy0">,</span> <span class="re0">$b</span><span class="br0">&#41;</span> <span class="br0">&#123;</span>
    <span class="kw1">return</span> <a href="http://www.php.net/strnatcasecmp"><span class="kw3">strnatcasecmp</span></a><span class="br0">&#40;</span><span class="re0">$a</span><span class="sy0">-&gt;</span>__toString<span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">,</span> <span class="re0">$b</span><span class="sy0">-&gt;</span>__toString<span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">;</span>
<span class="br0">&#125;</span><span class="br0">&#41;</span><span class="sy0">;</span></pre></div></div>


<h2>Deleting</h2>

<p>Thanks to the <a href="http://www.propelorm.org/docs/api/1.6/runtime/propel-runtime-collection/PropelArrayCollection.html">PropelArrayCollection API Documentation</a></p>


<div class="wp_syntax"><div class="code"><pre class="php">  <span class="coMULTI">/*
   * Remove the provided Segment object.
   *
   * @param Segment $s
   * @return Segment $s that was deleted.
   */</span>
  <span class="kw2">public</span> <span class="kw2">function</span> deleteSegment<span class="br0">&#40;</span>Segment <span class="re0">$s</span><span class="br0">&#41;</span> <span class="br0">&#123;</span>
      <span class="re0">$s</span><span class="sy0">-&gt;</span><span class="me1">delete</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">;</span>
      <span class="re0">$key</span> <span class="sy0">=</span> <span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">collSegments</span><span class="sy0">-&gt;</span><span class="me1">search</span><span class="br0">&#40;</span><span class="re0">$s</span><span class="br0">&#41;</span><span class="sy0">;</span>
      <span class="re0">$ret</span> <span class="sy0">=</span> <span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">collSegments</span><span class="sy0">-&gt;</span><span class="me1">remove</span><span class="br0">&#40;</span><span class="re0">$key</span><span class="br0">&#41;</span><span class="sy0">;</span>
      <span class="kw1">return</span> <span class="re0">$s</span><span class="sy0">;</span>
  <span class="br0">&#125;</span></pre></div></div>


<p>So elsewhere,</p>


<div class="wp_syntax"><div class="code"><pre class="php">	      <span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">deleteSegment</span><span class="br0">&#40;</span><span class="re0">$s</span><span class="br0">&#41;</span><span class="sy0">;</span></pre></div></div>

]]></content:encoded>
			<wfw:commentRss>http://n8v.enteuxis.org/2011/06/sorting-deleting-propel-object-collectio/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>sfPropel15Plugin error, &#8220;class &#8216;FooPeer&#8217; does not have a method &#8216;getUniqueColumnNames&#8217;&#8221;</title>
		<link>http://n8v.enteuxis.org/2011/05/sfpropel15plugin-error-class-foopeer-does-not-have-a-method-getuniquecolumnnames/</link>
		<comments>http://n8v.enteuxis.org/2011/05/sfpropel15plugin-error-class-foopeer-does-not-have-a-method-getuniquecolumnnames/#comments</comments>
		<pubDate>Thu, 26 May 2011 19:26:25 +0000</pubDate>
		<dc:creator>nathan</dc:creator>
				<category><![CDATA[Figuring IT Out]]></category>
		<category><![CDATA[propel]]></category>
		<category><![CDATA[symfony]]></category>
		<category><![CDATA[symfony 1.4]]></category>

		<guid isPermaLink="false">http://n8v.enteuxis.org/?p=425</guid>
		<description><![CDATA[Stupid me made the same mistake twice in a row so I&#8217;m documenting it for humanity. I&#8217;m updating an old Symfony project to use Symfony 1.3/1.4 and Propel 1.5 through François Zaninotto&#8217;s sfPropel15Plugin. Problem: I followed the README, right? But&#8230; $ ./symfony propel:build --forms &#62;&#62; schema converting &#34;C:/web/myproject/config/schema.yml&#34; to XML &#62;&#62; schema putting C:/web/myproject/config/generated-schema.xml &#62;&#62; [...]]]></description>
			<content:encoded><![CDATA[<p>Stupid me made the same mistake twice <em>in a row</em> so I&#8217;m documenting it for humanity.</p>

<p>I&#8217;m updating an old Symfony project to use Symfony 1.3/1.4 and Propel 1.5 through François Zaninotto&#8217;s <a href="http://www.symfony-project.org/plugins/sfPropel15Plugin">sfPropel15Plugin</a>.</p>

<h2>Problem:</h2>

<p>I followed the README, right? But&#8230;</p>


<div class="wp_syntax"><div class="code"><pre class="bash">$ .<span class="sy0">/</span>symfony propel:build <span class="re5">--forms</span>
<span class="sy0">&gt;&gt;</span> schema    converting <span class="st0">&quot;C:/web/myproject/config/schema.yml&quot;</span> to XML
<span class="sy0">&gt;&gt;</span> schema    putting C:<span class="sy0">/</span>web<span class="sy0">/</span>myproject<span class="sy0">/</span>config<span class="sy0">/</span>generated-schema.xml
<span class="sy0">&gt;&gt;</span> propel    Running <span class="st0">&quot;om&quot;</span> phing task
<span class="sy0">&gt;&gt;</span> file-     C:<span class="sy0">/</span>web<span class="sy0">/</span>myproject<span class="sy0">/</span>config<span class="sy0">/</span>generated-schema.xml
<span class="sy0">&gt;&gt;</span> autoload  Resetting application autoloaders
<span class="sy0">&gt;&gt;</span> autoload  Resetting CLI autoloader
<span class="sy0">&gt;&gt;</span> propel    generating form classes
PHP Warning:  call_user_func<span class="br0">&#40;</span><span class="br0">&#41;</span> expects parameter <span class="nu0">1</span> to be a valid callback, class <span class="st_h">'FooPeer'</span> does not have a method <span class="st_h">'getUniqueColumnNames'</span> <span class="kw1">in</span> C:\web\myproject\plugins\sfPropel15Plugin\lib\generator\sfPropelFormGenerator.class.php on line <span class="nu0">485</span>
PHP Stack trace:
PHP   <span class="nu0">1</span>. <span class="br0">&#123;</span>main<span class="br0">&#125;</span><span class="br0">&#40;</span><span class="br0">&#41;</span> C:\web\myproject\symfony:<span class="nu0">0</span>
PHP   <span class="nu0">2</span>. include<span class="br0">&#40;</span><span class="br0">&#41;</span> C:\web\myproject\symfony:<span class="nu0">14</span>
PHP   <span class="nu0">3</span>. sfSymfonyCommandApplication-<span class="sy0">&gt;</span>run<span class="br0">&#40;</span><span class="br0">&#41;</span> C:\symfony1.3\lib\<span class="kw3">command</span>\cli.php:<span class="nu0">20</span>
PHP   <span class="nu0">4</span>. sfTask-<span class="sy0">&gt;</span>runFromCLI<span class="br0">&#40;</span><span class="br0">&#41;</span> C:\symfony1.3\lib\<span class="kw3">command</span>\sfSymfonyCommandApplication.class.php:<span class="nu0">76</span>
PHP   <span class="nu0">5</span>. sfBaseTask-<span class="sy0">&gt;</span>doRun<span class="br0">&#40;</span><span class="br0">&#41;</span> C:\symfony1.3\lib\task\sfTask.class.php:<span class="nu0">97</span>
PHP   <span class="nu0">6</span>. sfPropelBuildTask-<span class="sy0">&gt;</span>execute<span class="br0">&#40;</span><span class="br0">&#41;</span> C:\symfony1.3\lib\task\sfBaseTask.class.php:<span class="nu0">68</span>
PHP   <span class="nu0">7</span>. sfTask-<span class="sy0">&gt;</span>run<span class="br0">&#40;</span><span class="br0">&#41;</span> C:\web\myproject\plugins\sfPropel15Plugin\lib\task\sfPropelBuildTask.class.php:<span class="nu0">135</span>
PHP   <span class="nu0">8</span>. sfBaseTask-<span class="sy0">&gt;</span>doRun<span class="br0">&#40;</span><span class="br0">&#41;</span> C:\symfony1.3\lib\task\sfTask.class.php:<span class="nu0">173</span>
PHP   <span class="nu0">9</span>. sfPropelBuildFormsTask-<span class="sy0">&gt;</span>execute<span class="br0">&#40;</span><span class="br0">&#41;</span> C:\symfony1.3\lib\task\sfBaseTask.class.php:<span class="nu0">68</span>
PHP  <span class="nu0">10</span>. sfGeneratorManager-<span class="sy0">&gt;</span>generate<span class="br0">&#40;</span><span class="br0">&#41;</span> C:\web\myproject\plugins\sfPropel15Plugin\lib\task\sfPropelBuildFormsTask.class.php:<span class="nu0">72</span>
PHP  <span class="nu0">11</span>. sfPropelFormGenerator-<span class="sy0">&gt;</span>generate<span class="br0">&#40;</span><span class="br0">&#41;</span> C:\symfony1.3\lib\generator\sfGeneratorManager.class.php:<span class="nu0">126</span>
PHP  <span class="nu0">12</span>. sfGenerator-<span class="sy0">&gt;</span>evalTemplate<span class="br0">&#40;</span><span class="br0">&#41;</span> C:\web\myproject\plugins\sfPropel15Plugin\lib\generator\sfPropelFormGenerator.class.php:<span class="nu0">106</span>
PHP  <span class="nu0">13</span>. require<span class="br0">&#40;</span><span class="br0">&#41;</span> C:\symfony1.3\lib\generator\sfGenerator.class.php:<span class="nu0">84</span>
PHP  <span class="nu0">14</span>. sfPropelFormGenerator-<span class="sy0">&gt;</span>getUniqueColumnNames<span class="br0">&#40;</span><span class="br0">&#41;</span> C:\web\myproject\plugins\sfPropel15Plugin\data\generator\sfPropelForm\default\template\sfPropelFormGeneratedTemplate.php:<span class="nu0">34</span>
PHP  <span class="nu0">15</span>. call_user_func<span class="br0">&#40;</span><span class="br0">&#41;</span> C:\web\myproject\plugins\sfPropel15Plugin\lib\generator\sfPropelFormGenerator.class.php:<span class="nu0">485</span>
... etc ...</pre></div></div>


<h2>Solution</h2>

<p>I <a href="http://www.mail-archive.com/symfony-users@googlegroups.com/msg32719.html">removed too much of the default config in propel.ini.</a>.</p>

<p>Make sure this original line is still intact in <var>propel.ini</var>, even though you&#8217;ve removed/commented out all the <code>propel.behavior</code> lines that point to the old <var>sfPropelPlugin</var>:</p>


<div class="wp_syntax"><div class="code"><pre class="ini">propel.behavior.default                        <span class="sy0">=</span><span class="re2"> symfony,symfony_i18n</span></pre></div></div>

]]></content:encoded>
			<wfw:commentRss>http://n8v.enteuxis.org/2011/05/sfpropel15plugin-error-class-foopeer-does-not-have-a-method-getuniquecolumnnames/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Adding WordPress-like tags to a Symfony 1.4 admin generator form</title>
		<link>http://n8v.enteuxis.org/2010/05/adding-wordpress-like-tags-to-a-symfony-1-4-admin-generator-form/</link>
		<comments>http://n8v.enteuxis.org/2010/05/adding-wordpress-like-tags-to-a-symfony-1-4-admin-generator-form/#comments</comments>
		<pubDate>Sat, 15 May 2010 01:15:46 +0000</pubDate>
		<dc:creator>nathan</dc:creator>
				<category><![CDATA[Figuring IT Out]]></category>
		<category><![CDATA[doctrine]]></category>
		<category><![CDATA[jQuery]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[plugins]]></category>
		<category><![CDATA[symfony]]></category>

		<guid isPermaLink="false">http://n8v.enteuxis.org/?p=325</guid>
		<description><![CDATA[Update 2010-08-06: It looks like the sfDoctrineActAsTaggablePlugin team has incorporated much of this into the version they released today. I&#8217;ll post more when I get a chance to try it out. This week I used the sfDoctrineActAsTaggablePlugin to add tag behavior to a Symfony project. My goal was to have a user interface similar to [...]]]></description>
			<content:encoded><![CDATA[<p><ins datetime="2010-08-06T23:57:46+00:00">Update 2010-08-06:  It looks like the sfDoctrineActAsTaggablePlugin team has incorporated much of this into the version they released today.  I&#8217;ll post more when I get a chance to try it out.</ins></p>

<p>This week I used the <a href="http://www.symfony-project.org/plugins/sfDoctrineActAsTaggablePlugin">sfDoctrineActAsTaggablePlugin</a> to add tag behavior to a Symfony project.  My goal was to have a user interface similar to WordPress&#8217;s, which I like a lot:</p>

<p><img src="http://n8v.enteuxis.org/wp-content/uploads/2010/05/wordpress_tag_ui.png" alt="Tag user interface in WordPress" title="wordpress_tag_ui" width="290" height="174" class="aligncenter size-full wp-image-330" /></p>

<p>Another goal was to use <a href="http://jqueryui.com/">JQuery UI</a> with the nice visual theme I built with their <a href="http://jqueryui.com/themeroller/">Theme Roller tool</a> (which I had already installed and added to my symfony project).</p>

<p>The documentation was a little bit sparse and I&#8217;ll probably end up doing this again, so here are relevant instructions so Future Me and others may also benefit:
<span id="more-325"></span></p>

<h2>Set up</h2>

<ol>
    <li>get/install/checkout sfDoctrineActAsTaggablePlugin.  I use this line in the `svn:externals` property on <var>plugins/</var>:
  
<pre>sfDoctrineActAsTaggablePlugin/ http://svn.symfony-project.com/plugins/sfDoctrineActAsTaggablePlugin/tags/RELEASE_1_0_0</pre>
</li>

    <li>enable the plugin in <var>config/ProjectConfiguration.class.php</var>.


<div class="wp_syntax"><div class="code"><pre class="php">    <span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">enablePlugins</span><span class="br0">&#40;</span>
                         <span class="st_h">'sfDoctrinePlugin'</span><span class="sy0">,</span> 
                         <span class="sy0">...</span>
			 <span class="st_h">'sfDoctrineActAsTaggablePlugin'</span>
			 <span class="br0">&#41;</span><span class="sy0">;</span></pre></div></div>



</li>

    <li>Add the Taggable behavior to your model(s) in <var>config/doctrine/schema.yml</var>


<div class="wp_syntax"><div class="code"><pre class="yaml">    actAs:            { Timestampable: ~ , Taggable: ~ }</pre></div></div>




         (<strong>not</strong> <code>templates: [Taggable]</code> as the README says)

</li>
    <li>Rebuild your model and forms and everything.  My favorite way is to dump the old data to fixtures, then rebuild everything.


<div class="wp_syntax"><div class="code"><pre class="bash">.<span class="sy0">/</span>symfony <span class="re5">--color</span> doctrine:data-dump
.<span class="sy0">/</span>symfony <span class="re5">--color</span> doctrine:build <span class="re5">--all</span> <span class="re5">--and-load</span> <span class="re5">--no-confirmation</span></pre></div></div>




</li>

</ol>

<p>At this point you should have new database tables <code>tag</code> and <code>tagging</code>, and the proper model relations and everything.</p>

<h2>Form fields for tags</h2>

<div id="attachment_352" class="wp-caption aligncenter" style="width: 490px"><img src="http://n8v.enteuxis.org/wp-content/uploads/2010/05/my_tags_ui.png" alt="" title="my_tags_ui" width="480" height="150" class="size-full wp-image-352" /><p class="wp-caption-text">UI for entering tags, displaying existing tags (with remove buttons)</p></div>

<p>I&#8217;m going to have</p>

<ol>
<li>a single text input field for adding tags, </li>
<li>a link to display a tag cloud (see below) if the user wants to pick tags by clicking instead of typing</li>
<li>a styled list of existing tags with delete icons</li>
<li>a hidden field for storing tags to be removed after clicking on the delete icons.</li>
</ol>

<p>The delete icons will trigger a Javascript function which populates the hidden field and hides the tag.  Changes won&#8217;t be saved until the user submits the form, so we&#8217;ll also provide a little reminder.</p>

<img src="http://n8v.enteuxis.org/wp-content/uploads/2010/05/my_tags_removed.png" alt="" title="my_tags_removed" width="461" height="86" class="size-full wp-image-354" />

<ol>
    <li>add the tag field(s) to the <code>configure()</code> method of your form, in <var>lib/form/doctrine/<samp>Model</samp>Form.class.php</var>.


<div class="wp_syntax"><div class="code"><pre class="php">    <span class="co1">// this text appears in gray until the user focuses on the field</span>
    <span class="re0">$default</span> <span class="sy0">=</span> <span class="st_h">'Add tags with commas'</span><span class="sy0">;</span>
    <span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">widgetSchema</span><span class="br0">&#91;</span><span class="st_h">'new_tags'</span><span class="br0">&#93;</span> <span class="sy0">=</span> <span class="kw2">new</span> sfWidgetFormInput
      <span class="br0">&#40;</span>
       <a href="http://www.php.net/array"><span class="kw3">array</span></a><span class="br0">&#40;</span><span class="st_h">'label'</span>   <span class="sy0">=&gt;</span>  <span class="st_h">'Add Tags'</span><span class="sy0">,</span> <span class="st_h">'default'</span>   <span class="sy0">=&gt;</span>  <span class="re0">$default</span><span class="br0">&#41;</span><span class="sy0">,</span>
       <a href="http://www.php.net/array"><span class="kw3">array</span></a><span class="br0">&#40;</span>
             <span class="st_h">'onclick'</span> <span class="sy0">=&gt;</span> <span class="st0">&quot;if (this.value=='<span class="es4">$default</span>') { 
                                this.value = ''; this.style.color='black'; }&quot;</span><span class="sy0">,</span> 
             <span class="st_h">'size'</span>   <span class="sy0">=&gt;</span>  <span class="st_h">'32'</span><span class="sy0">,</span>
	     <span class="st_h">'id'</span> <span class="sy0">=&gt;</span> <span class="st_h">'new_tags'</span><span class="sy0">,</span> 
             <span class="co1">// don't let the browser autocomplete.  We'll add typeahead, below</span>
	     <span class="st_h">'autocomplete'</span> <span class="sy0">=&gt;</span> <span class="st0">&quot;off&quot;</span><span class="sy0">,</span>      
	     <span class="st_h">'style'</span> <span class="sy0">=&gt;</span> <span class="st_h">'color:#aaa'</span>
	     <span class="br0">&#41;</span>
       <span class="br0">&#41;</span><span class="sy0">;</span>
    <span class="co1">// allow the field to remain blank</span>
    <span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">setValidator</span><span class="br0">&#40;</span><span class="st_h">'new_tags'</span><span class="sy0">,</span> <span class="kw2">new</span> sfValidatorString<span class="br0">&#40;</span><a href="http://www.php.net/array"><span class="kw3">array</span></a><span class="br0">&#40;</span><span class="st_h">'required'</span> <span class="sy0">=&gt;</span> <span class="kw4">false</span><span class="br0">&#41;</span><span class="br0">&#41;</span><span class="br0">&#41;</span><span class="sy0">;</span>
&nbsp;
    <span class="co1">// this hidden field will be populated with JavaScript.</span>
    <span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">widgetSchema</span><span class="br0">&#91;</span><span class="st_h">'remove_tags'</span><span class="br0">&#93;</span> <span class="sy0">=</span> <span class="kw2">new</span> sfWidgetFormInputHidden<span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">;</span>
    <span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">setValidator</span><span class="br0">&#40;</span><span class="st_h">'remove_tags'</span><span class="sy0">,</span> <span class="kw2">new</span> sfValidatorString<span class="br0">&#40;</span><a href="http://www.php.net/array"><span class="kw3">array</span></a><span class="br0">&#40;</span><span class="st_h">'required'</span> <span class="sy0">=&gt;</span> <span class="kw4">false</span><span class="br0">&#41;</span><span class="br0">&#41;</span><span class="br0">&#41;</span><span class="sy0">;</span></pre></div></div>



</li>
    <li>Add a partial with the admin generator.  In <var>apps/frontend/modules/<samp>mymodule</samp>/config/generator.yml</var>:


<div class="wp_syntax"><div class="code"><pre class="yaml">      form:
        display:
          # add another section after the other fields...
          Tags: [_tags]</pre></div></div>




</li>
    <li>edit the partial, <var>apps/frontend/modules/<samp>mymodule</samp>/templates/_tags.php</var>


<div class="wp_syntax"><div class="code"><pre class="php"><span class="kw2">&lt;?php</span> use_helper<span class="br0">&#40;</span><span class="st_h">'JavascriptBase'</span><span class="sy0">,</span> <span class="st_h">'Tags'</span><span class="br0">&#41;</span>  <span class="sy1">?&gt;</span>
<span class="kw2">&lt;?php</span>  
&nbsp;
<span class="co1">// much of this I copied and adapted from a cached admin generator template.</span>
<span class="re0">$name</span> <span class="sy0">=</span> <span class="st_h">'new_tags'</span><span class="sy0">;</span> <span class="re0">$label</span> <span class="sy0">=</span> <span class="st_h">''</span><span class="sy0">;</span> <span class="re0">$help</span> <span class="sy0">=</span> <span class="st_h">''</span><span class="sy0">;</span> 
<span class="re0">$class</span> <span class="sy0">=</span> <span class="st_h">'sf_admin_form_row sf_admin_text sf_admin_form_field_tags'</span><span class="sy0">;</span>
&nbsp;
 <span class="sy1">?&gt;</span>
&nbsp;
  &lt;div class=&quot;<span class="kw2">&lt;?php</span> <span class="kw1">echo</span> <span class="re0">$class</span> <span class="sy1">?&gt;</span><span class="kw2">&lt;?php</span> <span class="re0">$form</span><span class="br0">&#91;</span><span class="re0">$name</span><span class="br0">&#93;</span><span class="sy0">-&gt;</span><span class="me1">hasError</span><span class="br0">&#40;</span><span class="br0">&#41;</span> and <span class="kw1">print</span> <span class="st_h">' errors'</span> <span class="sy1">?&gt;</span>&quot;&gt;
    <span class="kw2">&lt;?php</span> <span class="kw1">echo</span> <span class="re0">$form</span><span class="br0">&#91;</span><span class="re0">$name</span><span class="br0">&#93;</span><span class="sy0">-&gt;</span><span class="me1">renderError</span><span class="br0">&#40;</span><span class="br0">&#41;</span> <span class="sy1">?&gt;</span>
    &lt;div&gt;
      <span class="kw2">&lt;?php</span> <span class="kw1">echo</span> <span class="re0">$form</span><span class="br0">&#91;</span><span class="re0">$name</span><span class="br0">&#93;</span><span class="sy0">-&gt;</span><span class="me1">renderLabel</span><span class="br0">&#40;</span><span class="re0">$label</span><span class="br0">&#41;</span> <span class="sy1">?&gt;</span>
&nbsp;
      &lt;div class=&quot;content&quot;&gt;<span class="kw2">&lt;?php</span> <span class="kw1">echo</span> <span class="re0">$form</span><span class="br0">&#91;</span><span class="re0">$name</span><span class="br0">&#93;</span><span class="sy0">-&gt;</span><span class="me1">render</span><span class="br0">&#40;</span><span class="re0">$attributes</span> instanceof sfOutputEscaper ? <span class="re0">$attributes</span><span class="sy0">-&gt;</span><span class="me1">getRawValue</span><span class="br0">&#40;</span><span class="br0">&#41;</span> <span class="sy0">:</span> <span class="re0">$attributes</span><span class="br0">&#41;</span> <span class="sy1">?&gt;</span>
&nbsp;
&nbsp;
<span class="kw2">&lt;?php</span> <span class="co1">// tag cloud will go here, see below ?&gt;</span>
&nbsp;
&nbsp;
<span class="sy0">&lt;/</span>div<span class="sy0">&gt;</span>
&nbsp;
      <span class="kw2">&lt;?php</span> <span class="kw1">if</span> <span class="br0">&#40;</span><span class="re0">$help</span><span class="br0">&#41;</span><span class="sy0">:</span> <span class="sy1">?&gt;</span>
        &lt;div class=&quot;help&quot;&gt;<span class="kw2">&lt;?php</span> <span class="kw1">echo</span> __<span class="br0">&#40;</span><span class="re0">$help</span><span class="sy0">,</span> <a href="http://www.php.net/array"><span class="kw3">array</span></a><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">,</span> <span class="st_h">'messages'</span><span class="br0">&#41;</span> <span class="sy1">?&gt;</span>&lt;/div&gt;
      <span class="kw2">&lt;?php</span> <span class="kw1">elseif</span> <span class="br0">&#40;</span><span class="re0">$help</span> <span class="sy0">=</span> <span class="re0">$form</span><span class="br0">&#91;</span><span class="re0">$name</span><span class="br0">&#93;</span><span class="sy0">-&gt;</span><span class="me1">renderHelp</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="br0">&#41;</span><span class="sy0">:</span> <span class="sy1">?&gt;</span>
        &lt;div class=&quot;help&quot;&gt;<span class="kw2">&lt;?php</span> <span class="kw1">echo</span> <span class="re0">$help</span> <span class="sy1">?&gt;</span>&lt;/div&gt;
      <span class="kw2">&lt;?php</span> <span class="kw1">endif</span><span class="sy0">;</span> <span class="sy1">?&gt;</span>
    &lt;/div&gt;
  &lt;/div&gt;
&nbsp;
<span class="kw2">&lt;?php</span> <span class="co1">// list of current tags, with remove buttons ?&gt;</span>
<span class="sy0">&lt;</span>div <span class="kw2">class</span><span class="sy0">=</span><span class="st0">&quot;sf_admin_form_row sf_admin_text sf_admin_form_field_tags&quot;</span><span class="sy0">&gt;</span>
<span class="sy0">&lt;</span>div<span class="sy0">&gt;</span>
<span class="sy0">&lt;</span>label<span class="sy0">&gt;</span>Current tags<span class="sy0">&lt;/</span>label<span class="sy0">&gt;</span>
<span class="sy0">&lt;</span>div <span class="kw2">class</span><span class="sy0">=</span><span class="st0">&quot;content&quot;</span><span class="sy0">&gt;</span>
<span class="sy0">&lt;</span>div <span class="kw2">class</span><span class="sy0">=</span><span class="st0">&quot;taglist&quot;</span><span class="sy0">&gt;</span>   
   <span class="kw2">&lt;?php</span> <span class="kw1">foreach</span> <span class="br0">&#40;</span> <span class="re0">$form</span><span class="sy0">-&gt;</span><span class="me1">getObject</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span><span class="me1">getTags</span><span class="br0">&#40;</span><span class="br0">&#41;</span> <span class="kw1">as</span> <span class="re0">$t</span><span class="br0">&#41;</span><span class="sy0">:</span>   <span class="sy1">?&gt;</span>
    &lt;span&gt;&lt;nobr&gt;<span class="kw2">&lt;?php</span> 
&nbsp;
<span class="kw1">echo</span> link_to_function<span class="br0">&#40;</span><span class="st0">&quot;Remove '<span class="es4">$t</span>'&quot;</span><span class="sy0">,</span> 
           <span class="st0">&quot;remove_tag(&quot;</span><span class="sy0">.</span><a href="http://www.php.net/json_encode"><span class="kw3">json_encode</span></a><span class="br0">&#40;</span><span class="re0">$t</span><span class="br0">&#41;</span><span class="sy0">.</span><span class="st0">&quot;, this.parentElement)&quot;</span><span class="sy0">,</span> 
           <span class="st0">&quot;class=removetag&quot;</span><span class="br0">&#41;</span>
&nbsp;
  <span class="sy1">?&gt;</span>&amp;nbsp;<span class="kw2">&lt;?php</span> <span class="kw1">echo</span> <span class="re0">$t</span>  <span class="sy1">?&gt;</span>&lt;/nobr&gt;&lt;/span&gt;
    <span class="kw2">&lt;?php</span> <span class="kw1">endforeach</span><span class="sy0">;</span>  <span class="sy1">?&gt;</span>
&lt;/div&gt;
&lt;span id=&quot;remove_tag_help&quot; style=&quot;display:none;&quot;&gt;Tag(s) removed. Remember to save the complaint.&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&nbsp;
&lt;/div&gt;</pre></div></div>



</li>

    <li>Add a Javascript function to support the &#8220;remove tag&#8221; buttons.


<div class="wp_syntax"><div class="code"><pre class="javascript">$<span class="br0">&#40;</span><span class="kw2">function</span><span class="br0">&#40;</span><span class="br0">&#41;</span> <span class="br0">&#123;</span>
            <span class="co1">// add fancy jQuery UI button styles.  See additional in &quot;CSS&quot; below</span>
	    $<span class="br0">&#40;</span><span class="st0">&quot;.taglist a&quot;</span><span class="br0">&#41;</span>.<span class="me1">button</span><span class="br0">&#40;</span><span class="br0">&#123;</span>icons<span class="sy0">:</span><span class="br0">&#123;</span>primary<span class="sy0">:</span><span class="st0">'ui-icon-trash'</span><span class="br0">&#125;</span><span class="sy0">,</span> text<span class="sy0">:</span> <span class="kw2">false</span><span class="br0">&#125;</span><span class="br0">&#41;</span><span class="sy0">;</span>
<span class="br0">&#125;</span><span class="br0">&#41;</span><span class="sy0">;</span>
&nbsp;
<span class="kw2">function</span> remove_tag <span class="br0">&#40;</span>tag<span class="sy0">,</span> element<span class="br0">&#41;</span> <span class="br0">&#123;</span>
  remove_field <span class="sy0">=</span> $<span class="br0">&#40;</span><span class="st0">&quot;#complaint_remove_tags&quot;</span><span class="br0">&#41;</span><span class="sy0">;</span>
  <span class="kw1">if</span> <span class="br0">&#40;</span> remove_field.<span class="me1">val</span><span class="br0">&#40;</span><span class="br0">&#41;</span>.<span class="me1">length</span> <span class="br0">&#41;</span> <span class="br0">&#123;</span>
    remove_field.<span class="me1">val</span><span class="br0">&#40;</span> remove_field.<span class="me1">val</span><span class="br0">&#40;</span><span class="br0">&#41;</span>  <span class="sy0">+</span> <span class="st0">&quot;,&quot;</span> <span class="sy0">+</span> tag <span class="br0">&#41;</span><span class="sy0">;</span>
  <span class="br0">&#125;</span>
  <span class="kw1">else</span> <span class="br0">&#123;</span>
    remove_field.<span class="me1">val</span><span class="br0">&#40;</span> tag <span class="br0">&#41;</span><span class="sy0">;</span>
  <span class="br0">&#125;</span>
  $<span class="br0">&#40;</span>element<span class="br0">&#41;</span>.<span class="me1">hide</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">;</span>
  $<span class="br0">&#40;</span><span class="st0">&quot;#remove_tag_help&quot;</span><span class="br0">&#41;</span>.<span class="me1">show</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">;</span>
<span class="br0">&#125;</span></pre></div></div>




</li>
    <li>edit the `processForm` action to process the `new_tags` and `remove_tags` fields.    I started by copying the `processForm` method from <var>cache/frontend/dev/modules/auto<samp>Mymodel</samp>/actions/actions.class.php</var> into <var>apps/frontend/modules/<samp>mymodule</samp>/actions/actions.class.php</var>, then added code after validation:


<div class="wp_syntax"><div class="code"><pre class="php">  <span class="kw2">protected</span> <span class="kw2">function</span> processForm<span class="br0">&#40;</span>sfWebRequest <span class="re0">$request</span><span class="sy0">,</span> sfForm <span class="re0">$form</span><span class="br0">&#41;</span>
  <span class="br0">&#123;</span>
    <span class="re0">$form</span><span class="sy0">-&gt;</span><span class="me1">bind</span><span class="br0">&#40;</span><span class="re0">$request</span><span class="sy0">-&gt;</span><span class="me1">getParameter</span><span class="br0">&#40;</span><span class="re0">$form</span><span class="sy0">-&gt;</span><span class="me1">getName</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="br0">&#41;</span><span class="sy0">,</span> <span class="re0">$request</span><span class="sy0">-&gt;</span><span class="me1">getFiles</span><span class="br0">&#40;</span><span class="re0">$form</span><span class="sy0">-&gt;</span><span class="me1">getName</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="br0">&#41;</span><span class="br0">&#41;</span><span class="sy0">;</span>
&nbsp;
    <span class="kw1">if</span> <span class="br0">&#40;</span><span class="re0">$form</span><span class="sy0">-&gt;</span><span class="me1">isValid</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="br0">&#41;</span>
    <span class="br0">&#123;</span>
      <span class="re0">$notice</span> <span class="sy0">=</span> <span class="re0">$form</span><span class="sy0">-&gt;</span><span class="me1">getObject</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span><span class="me1">isNew</span><span class="br0">&#40;</span><span class="br0">&#41;</span> ? <span class="st_h">'The item was created successfully.'</span> <span class="sy0">:</span> <span class="st_h">'The item was updated successfully.'</span><span class="sy0">;</span>
&nbsp;
      <span class="co1">// NEW: deal with tags</span>
      <span class="kw1">if</span> <span class="br0">&#40;</span><span class="re0">$form</span><span class="sy0">-&gt;</span><span class="me1">getValue</span><span class="br0">&#40;</span><span class="st_h">'remove_tags'</span><span class="br0">&#41;</span><span class="br0">&#41;</span> <span class="br0">&#123;</span>
	<span class="kw1">foreach</span> <span class="br0">&#40;</span><a href="http://www.php.net/preg_split"><span class="kw3">preg_split</span></a><span class="br0">&#40;</span><span class="st_h">'/\s*,\s*/'</span><span class="sy0">,</span> <span class="re0">$form</span><span class="sy0">-&gt;</span><span class="me1">getValue</span><span class="br0">&#40;</span><span class="st_h">'remove_tags'</span><span class="br0">&#41;</span><span class="br0">&#41;</span> <span class="kw1">as</span> <span class="re0">$tag</span><span class="br0">&#41;</span> <span class="br0">&#123;</span>
	  <span class="re0">$form</span><span class="sy0">-&gt;</span><span class="me1">getObject</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span><span class="me1">removeTag</span><span class="br0">&#40;</span><span class="re0">$tag</span><span class="br0">&#41;</span><span class="sy0">;</span>
	<span class="br0">&#125;</span>
      <span class="br0">&#125;</span>
      <span class="kw1">if</span> <span class="br0">&#40;</span><span class="re0">$form</span><span class="sy0">-&gt;</span><span class="me1">getValue</span><span class="br0">&#40;</span><span class="st_h">'new_tags'</span><span class="br0">&#41;</span><span class="br0">&#41;</span> <span class="br0">&#123;</span>
	<span class="kw1">foreach</span> <span class="br0">&#40;</span><a href="http://www.php.net/preg_split"><span class="kw3">preg_split</span></a><span class="br0">&#40;</span><span class="st_h">'/\s*,\s*/'</span><span class="sy0">,</span> <span class="re0">$form</span><span class="sy0">-&gt;</span><span class="me1">getValue</span><span class="br0">&#40;</span><span class="st_h">'new_tags'</span><span class="br0">&#41;</span><span class="br0">&#41;</span> <span class="kw1">as</span> <span class="re0">$tag</span><span class="br0">&#41;</span> <span class="br0">&#123;</span>
          <span class="co1">// sorry, it would be better to not hard-code this string</span>
	  <span class="kw1">if</span> <span class="br0">&#40;</span><span class="re0">$tag</span> <span class="sy0">==</span> <span class="st_h">'Add tags with commas'</span><span class="br0">&#41;</span> <span class="kw1">continue</span><span class="sy0">;</span>
	  <span class="re0">$form</span><span class="sy0">-&gt;</span><span class="me1">getObject</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span><span class="me1">addTag</span><span class="br0">&#40;</span><span class="re0">$tag</span><span class="br0">&#41;</span><span class="sy0">;</span>
	<span class="br0">&#125;</span>
      <span class="br0">&#125;</span>
&nbsp;
      try <span class="br0">&#123;</span>
        <span class="re0">$complaint</span> <span class="sy0">=</span> <span class="re0">$form</span><span class="sy0">-&gt;</span><span class="me1">save</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">;</span>
        <span class="co1">// and the remainder is just pasted from the generated actions file</span></pre></div></div>




</li>
</ol>

<p>Yay!  Now I can add, display, and remove tags.   Now for the fancy parts.</p>

<h2>Typeahead tag autocompletion</h2>

<p><img src="http://n8v.enteuxis.org/wp-content/uploads/2010/05/my_tags_typeahead.png" alt="" title="my_tags_typeahead" width="480" height="150" class="aligncenter size-full wp-image-360" /></p>

<p>As detailed in the README, sfDoctrineActAsTaggablePlugin has typeahead support. I tried it, but I didn&#8217;t feel like the user interaction was quite smooth enough, and I wanted to use the excellent, beautiful, and user-expectation-meeting <a href="http://jqueryui.com/demos/autocomplete/">jQuery UI Autocomplete widget</a>.</p>

<p>I ended up using the action provided by sfDoctrineActAsTaggablePlugin, but modified the view to return JSON instead of an HTML &lt;ul&gt;, and I used the <a href="http://jqueryui.com/demos/autocomplete/multiple-remote.html">multiple, remote demo</a> code from <a href="http://jqueryui.com/demos/autocomplete/#multiple-remote">the jQuery UI Autocomplete documentation</a>.</p>

<ol>
    <li>override the <code>completeSuccess</code> view for the <code>taggableComplete/complete</code> action


<div class="wp_syntax"><div class="code"><pre class="bash"><span class="kw2">mkdir</span> <span class="re5">-p</span> apps<span class="sy0">/</span>frontend<span class="sy0">/</span>modules<span class="sy0">/</span>taggableComplete<span class="sy0">/</span>templates
<span class="kw2">touch</span> apps<span class="sy0">/</span>frontend<span class="sy0">/</span>modules<span class="sy0">/</span>taggableComplete<span class="sy0">/</span>templates<span class="sy0">/</span>completeSuccess.php</pre></div></div>




And here is my new template:


<div class="wp_syntax"><div class="code"><pre class="php"><span class="kw2">&lt;?php</span>
&nbsp;
<span class="co1">// we're rewriting the view for the taggable plugin to output JSON instead of HTML</span>
<span class="co1">// see http://wiki.jqueryui.com/Autocomplete</span>
&nbsp;
<span class="re0">$tags_simple</span> <span class="sy0">=</span> <a href="http://www.php.net/array"><span class="kw3">array</span></a><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">;</span>
<span class="kw1">foreach</span> <span class="br0">&#40;</span> <span class="re0">$tagSuggestions</span> <span class="kw1">as</span> <span class="re0">$suggestion</span> <span class="br0">&#41;</span> <span class="br0">&#123;</span>
  <span class="re0">$tags_simple</span><span class="br0">&#91;</span><span class="br0">&#93;</span> <span class="sy0">=</span>  <span class="re0">$suggestion</span><span class="br0">&#91;</span><span class="st_h">'suggested'</span><span class="br0">&#93;</span><span class="sy0">;</span>
<span class="br0">&#125;</span>
&nbsp;
<span class="kw1">echo</span> <a href="http://www.php.net/json_encode"><span class="kw3">json_encode</span></a><span class="br0">&#40;</span><span class="re0">$tags_simple</span><span class="br0">&#41;</span><span class="sy0">;</span></pre></div></div>




You can test your autocomplete action with a URL like <code>http://myserver/myproject/taggableComplete/complete/current/<samp>p</samp></code>.

In my case (because I have some data in my <code>tag</code> table) it returns this JSON-formatted array:


<div class="wp_syntax"><div class="code"><pre class="javascript"><span class="br0">&#91;</span><span class="st0">&quot;PHP&quot;</span><span class="sy0">,</span><span class="st0">&quot;plugins&quot;</span><span class="br0">&#93;</span></pre></div></div>



</li>

    <li>Add jQuery UI javascript.  I used a <code>&lt;script&gt;</code> section in my <var>_tags.php</var> partial, but you could also use an external javascript file (but mind the line of PHP code using the <code>url_for()</code> helper).


<div class="wp_syntax"><div class="code"><pre class="javascript">	$<span class="br0">&#40;</span><span class="kw2">function</span><span class="br0">&#40;</span><span class="br0">&#41;</span> <span class="br0">&#123;</span>
&nbsp;
<span class="co1">// for debug info, uncomment these lines and add a &lt;div id=&quot;autocomplete_log&quot;&gt;</span>
<span class="co1">// 		function log(message) {</span>
<span class="co1">// 			$(&quot;&lt;div/&gt;&quot;).text(message).prependTo(&quot;#autocomplete_log&quot;);</span>
<span class="co1">// 			$(&quot;#autocomplete_log&quot;).attr(&quot;scrollTop&quot;, 0);</span>
<span class="co1">// 		}</span>
		<span class="kw2">function</span> split<span class="br0">&#40;</span>val<span class="br0">&#41;</span> <span class="br0">&#123;</span>
			<span class="kw1">return</span> val.<span class="me1">split</span><span class="br0">&#40;</span><span class="co2">/\s*,\s*/</span><span class="br0">&#41;</span><span class="sy0">;</span>
		<span class="br0">&#125;</span>
		<span class="kw2">function</span> extractLast<span class="br0">&#40;</span>term<span class="br0">&#41;</span> <span class="br0">&#123;</span>
		        last <span class="sy0">=</span> split<span class="br0">&#40;</span>term<span class="br0">&#41;</span>.<span class="me1">pop</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">;</span>
<span class="co1">// 			log ( &quot;extracted last = &quot;+last );</span>
			<span class="kw1">return</span> last<span class="sy0">;</span>
		<span class="br0">&#125;</span>
&nbsp;
&nbsp;
		$<span class="br0">&#40;</span><span class="st0">&quot;#new_tags&quot;</span><span class="br0">&#41;</span>.<span class="me1">autocomplete</span><span class="br0">&#40;</span><span class="br0">&#123;</span>
&nbsp;
			source<span class="sy0">:</span> <span class="kw2">function</span><span class="br0">&#40;</span>request<span class="sy0">,</span> response<span class="br0">&#41;</span> <span class="br0">&#123;</span>
		      $.<span class="me1">getJSON</span><span class="br0">&#40;</span><span class="sy0">&lt;?</span>php echo json_encode<span class="br0">&#40;</span>url_for<span class="br0">&#40;</span><span class="st0">&quot;taggableComplete/complete&quot;</span><span class="br0">&#41;</span><span class="br0">&#41;</span> <span class="sy0">?&gt;,</span> <span class="br0">&#123;</span>
					current<span class="sy0">:</span> extractLast<span class="br0">&#40;</span>request.<span class="me1">term</span><span class="br0">&#41;</span>
				<span class="br0">&#125;</span><span class="sy0">,</span> response<span class="br0">&#41;</span><span class="sy0">;</span>
			<span class="br0">&#125;</span><span class="sy0">,</span>
			search<span class="sy0">:</span> <span class="kw2">function</span><span class="br0">&#40;</span><span class="br0">&#41;</span> <span class="br0">&#123;</span>
				<span class="co1">// custom minLength</span>
				<span class="kw2">var</span> term <span class="sy0">=</span> extractLast<span class="br0">&#40;</span><span class="kw1">this</span>.<span class="me1">value</span><span class="br0">&#41;</span><span class="sy0">;</span>
				<span class="kw1">if</span> <span class="br0">&#40;</span>term.<span class="me1">length</span> <span class="sy0">&lt;</span> <span class="nu0">1</span><span class="br0">&#41;</span> <span class="br0">&#123;</span>
					<span class="kw1">return</span> <span class="kw2">false</span><span class="sy0">;</span>
				<span class="br0">&#125;</span>
			<span class="br0">&#125;</span><span class="sy0">,</span>
		        <span class="kw3">focus</span><span class="sy0">:</span> <span class="kw2">function</span><span class="br0">&#40;</span>event<span class="sy0">,</span> ui<span class="br0">&#41;</span> <span class="br0">&#123;</span>
				<span class="co1">// prevent value inserted on focus</span>
				<span class="kw1">return</span> <span class="kw2">false</span><span class="sy0">;</span>
			<span class="br0">&#125;</span><span class="sy0">,</span>
			select<span class="sy0">:</span> <span class="kw2">function</span><span class="br0">&#40;</span>event<span class="sy0">,</span> ui<span class="br0">&#41;</span> <span class="br0">&#123;</span>
				<span class="kw2">var</span> terms <span class="sy0">=</span> split<span class="br0">&#40;</span> <span class="kw1">this</span>.<span class="me1">value</span> <span class="br0">&#41;</span><span class="sy0">;</span>
				<span class="co1">// remove the current input</span>
				terms.<span class="me1">pop</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">;</span>
				<span class="co1">// add the selected item</span>
				terms.<span class="me1">push</span><span class="br0">&#40;</span> ui.<span class="kw1">item</span>.<span class="me1">value</span> <span class="br0">&#41;</span><span class="sy0">;</span>
				<span class="co1">// add placeholder to get the comma-and-space at the end</span>
				terms.<span class="me1">push</span><span class="br0">&#40;</span><span class="st0">&quot;&quot;</span><span class="br0">&#41;</span><span class="sy0">;</span>
				<span class="kw1">this</span>.<span class="me1">value</span> <span class="sy0">=</span> terms.<span class="me1">join</span><span class="br0">&#40;</span><span class="st0">&quot;, &quot;</span><span class="br0">&#41;</span><span class="sy0">;</span>
				<span class="kw1">return</span> <span class="kw2">false</span><span class="sy0">;</span>
			<span class="br0">&#125;</span>
&nbsp;
		<span class="br0">&#125;</span><span class="br0">&#41;</span><span class="sy0">;</span>
	<span class="br0">&#125;</span><span class="br0">&#41;</span><span class="sy0">;</span></pre></div></div>



</li>
</ol>

<h2>tag cloud</h2>

<img src="http://n8v.enteuxis.org/wp-content/uploads/2010/05/my_tags_pick_from_cloud.png" alt="" title="my_tags_pick_from_cloud" width="468" height="76" class="size-full wp-image-362" />

<p>I like how WordPress has an option for picking tags from a tag cloud too.  The sfDoctrineActAsTaggablePlugin made this nice and easy.</p>

<ol>
    <li>Here&#8217;s the code for the view (in the <var>_tags.php</var> partial)


<div class="wp_syntax"><div class="code"><pre class="php"><span class="kw2">&lt;?php</span> <span class="kw1">echo</span> link_to_function<span class="br0">&#40;</span><span class="st0">&quot;Choose from the most used tags &amp;gt;&amp;gt;&quot;</span><span class="sy0">,</span> <span class="st_h">'$(&quot;#add_tag_from_cloud&quot;).show(); $(this).hide()'</span> <span class="br0">&#41;</span> <span class="sy1">?&gt;</span>
&nbsp;
&lt;div id=&quot;add_tag_from_cloud&quot; class=&quot;tag_cloud popular&quot; style=&quot;display:none&quot;&gt;
&lt;h3&gt;Popular tags&lt;/h3&gt;
<span class="kw2">&lt;?php</span> 
<span class="co1">// gets the popular tags</span>
<span class="re0">$tags</span> <span class="sy0">=</span> PluginTagTable<span class="sy0">::</span><span class="me2">getPopulars</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">;</span>
&nbsp;
<span class="co1">// Display the tags cloud, using link_to_function() instead of link_to()</span>
<span class="co1">// The %s in the second arg will be substituted with the tag text.</span>
<span class="kw1">echo</span> tag_cloud<span class="br0">&#40;</span><span class="re0">$tags</span><span class="sy0">,</span> <span class="st_h">'add_tag(&quot;%s&quot;)'</span><span class="sy0">,</span> <a href="http://www.php.net/array"><span class="kw3">array</span></a><span class="br0">&#40;</span>
				<span class="st_h">'link_function'</span>   <span class="sy0">=&gt;</span>  <span class="st_h">'link_to_function'</span><span class="sy0">,</span>  
				<span class="st_h">'link_options'</span>   <span class="sy0">=&gt;</span>  <a href="http://www.php.net/array"><span class="kw3">array</span></a><span class="br0">&#40;</span><span class="st_h">'class=addtag'</span><span class="br0">&#41;</span>
					     <span class="br0">&#41;</span>
	       <span class="br0">&#41;</span><span class="sy0">;</span>
&nbsp;
  <span class="sy1">?&gt;</span>
&lt;/div&gt;</pre></div></div>



</li>
    <li>And here&#8217;s the javascript to handle those links.


<div class="wp_syntax"><div class="code"><pre class="javascript"><span class="kw2">function</span> add_tag <span class="br0">&#40;</span>tag<span class="br0">&#41;</span> <span class="br0">&#123;</span>
  add_field <span class="sy0">=</span> $<span class="br0">&#40;</span><span class="st0">&quot;#new_tags&quot;</span><span class="br0">&#41;</span><span class="sy0">;</span>
&nbsp;
  <span class="kw1">if</span> <span class="br0">&#40;</span> add_field.<span class="me1">val</span><span class="br0">&#40;</span><span class="br0">&#41;</span> <span class="sy0">==</span> <span class="st0">&quot;Add tags with commas&quot;</span> <span class="br0">&#41;</span> <span class="br0">&#123;</span>
    add_field.<span class="me1">val</span><span class="br0">&#40;</span> tag <span class="br0">&#41;</span><span class="sy0">;</span>
    add_field.<span class="me1">css</span><span class="br0">&#40;</span><span class="st0">&quot;color&quot;</span><span class="sy0">,</span> <span class="st0">&quot;black&quot;</span><span class="br0">&#41;</span><span class="sy0">;</span>
  <span class="br0">&#125;</span>
  <span class="kw1">else</span> <span class="kw1">if</span> <span class="br0">&#40;</span> add_field.<span class="me1">val</span><span class="br0">&#40;</span><span class="br0">&#41;</span>.<span class="me1">length</span> <span class="br0">&#41;</span> <span class="br0">&#123;</span>
    add_field.<span class="me1">val</span><span class="br0">&#40;</span> add_field.<span class="me1">val</span><span class="br0">&#40;</span><span class="br0">&#41;</span>  <span class="sy0">+</span> <span class="st0">&quot;, &quot;</span> <span class="sy0">+</span> tag <span class="br0">&#41;</span><span class="sy0">;</span>
  <span class="br0">&#125;</span>
  <span class="kw1">else</span> <span class="br0">&#123;</span>
    add_field.<span class="me1">val</span><span class="br0">&#40;</span> tag <span class="br0">&#41;</span><span class="sy0">;</span>
  <span class="br0">&#125;</span>
  $<span class="br0">&#40;</span>element<span class="br0">&#41;</span>.<span class="me1">hide</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">;</span>
<span class="br0">&#125;</span></pre></div></div>



</li>
</ol>

<h2>CSS</h2>

<p>I made lots of incremental changes to my CSS but here are all of the sections relevant to the tag stuff I&#8217;ve shown here, I think.</p>


<div class="wp_syntax"><div class="code"><pre class="css">&nbsp;
<span class="re1">.taglist</span> <span class="re1">.removetag</span> <span class="br0">&#123;</span>
   <span class="kw1">vertical-align</span><span class="sy0">:</span> <span class="kw2">middle</span><span class="sy0">;</span>
   <span class="kw1">width</span><span class="sy0">:</span> <span class="re3">18px</span><span class="sy0">;</span>
   <span class="kw1">height</span><span class="sy0">:</span> <span class="re3">18px</span><span class="sy0">;</span>
<span class="br0">&#125;</span>
&nbsp;
<span class="re1">.taglist</span> span <span class="br0">&#123;</span>
   <span class="kw1">margin-right</span><span class="sy0">:</span> <span class="re3">1.4em</span><span class="sy0">;</span>
<span class="br0">&#125;</span>
&nbsp;
<span class="re1">.tag_cloud</span> li <span class="br0">&#123;</span>
   <span class="kw1">display</span><span class="sy0">:</span> <span class="kw2">inline</span><span class="sy0">;</span>
   <span class="kw1">list-style</span><span class="sy0">:</span> <span class="kw2">none</span><span class="sy0">;</span>
<span class="br0">&#125;</span>
&nbsp;
<span class="re0">#sf_admin_container</span> <span class="re1">.tag_cloud</span> li a <span class="br0">&#123;</span>
   <span class="kw1">background</span><span class="sy0">:</span> <span class="kw2">transparent</span><span class="sy0">;</span>
   <span class="kw1">padding-left</span><span class="sy0">:</span> <span class="re3">0px</span><span class="sy0">;</span>
<span class="br0">&#125;</span></pre></div></div>


<p>And I had this in my <var>apps/frontend/templates/layout.php</var> to apply JQuery UI button styling to all my form buttons and hyperlinks with the &#8220;button&#8221; CSS class:</p>


<div class="wp_syntax"><div class="code"><pre class="javascript"><span class="sy0">&lt;</span>script type<span class="sy0">=</span><span class="st0">&quot;text/javascript&quot;</span><span class="sy0">&gt;</span>
<span class="co1">// use JQueryUI buttons</span>
      $<span class="br0">&#40;</span><span class="kw2">function</span><span class="br0">&#40;</span><span class="br0">&#41;</span> <span class="br0">&#123;</span>
       $<span class="br0">&#40;</span><span class="st0">&quot;input:submit,  button, a.button&quot;</span><span class="br0">&#41;</span>.<span class="me1">button</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">;</span>
      <span class="br0">&#125;</span><span class="br0">&#41;</span><span class="sy0">;</span>
<span class="sy0">&lt;/</span>script<span class="sy0">&gt;</span></pre></div></div>


<p>Yay! I&#8217;m pretty happy with it so far.  I hacked at it for a couple days though and have been cloudy with fighting a cold, so if I&#8217;ve missed anything let me know.  I think this is enough to get me there more quickly the next time.</p>
]]></content:encoded>
			<wfw:commentRss>http://n8v.enteuxis.org/2010/05/adding-wordpress-like-tags-to-a-symfony-1-4-admin-generator-form/feed/</wfw:commentRss>
		<slash:comments>10</slash:comments>
		</item>
		<item>
		<title>Symfony log management with `logrotate` on Linux</title>
		<link>http://n8v.enteuxis.org/2009/10/symfony-log-management-with-logrotate-on-linux/</link>
		<comments>http://n8v.enteuxis.org/2009/10/symfony-log-management-with-logrotate-on-linux/#comments</comments>
		<pubDate>Fri, 16 Oct 2009 20:44:34 +0000</pubDate>
		<dc:creator>nathan</dc:creator>
				<category><![CDATA[Figuring IT Out]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[logs]]></category>
		<category><![CDATA[symfony]]></category>

		<guid isPermaLink="false">http://n8v.enteuxis.org/?p=282</guid>
		<description><![CDATA[It sucks when server disks fill up. I have Nagios set up to notify me well before it&#8217;s a problem, but it happens annoyingly frequently, and it&#8217;s always the same cause: log files. Symfony&#8217;s production environments don&#8217;t create logs, but the development environments do, and for one project I had several batch jobs logging to [...]]]></description>
			<content:encoded><![CDATA[<p>It sucks when server disks fill up.  I have <a href="http://www.nagios.org/">Nagios</a> set up to notify me well before it&#8217;s a problem, but it happens annoyingly frequently, and it&#8217;s always the same cause:  log files.</p>

<p>Symfony&#8217;s production environments don&#8217;t create logs, but the development environments do, and for one project I had several batch jobs logging to the <var>myproject/log</var> directory.  (Troubleshooting batch jobs without logs is just crazy).</p>

<p>I was going to use <a href="http://www.symfony-project.org/book/1_2/16-Application-Management-Tools#chapter_16_sub_symfony_logs"><code>symfony log:rotate</code></a>, but</p>

<ul>
<li>I would need one cron job for each log</li>
<li>I had permission issues since some of the logs belong to <var>apache</var> and others to <var>root</var></li>
<li>Everything else on the machine, pretty much, is managed with the standard Linux utility <a href="http://linux.die.net/man/8/logrotate"><var>logrotate</var></a>.  Logrotate has been around for about 174 Linux Years, and is common to most distributions.</li>
</ul>

<p>So here&#8217;s how I did it using with <var>logrotate</var>.</p>

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

<ol>

    <li><strong>WARNING</strong>:  You might want to make a convenient copy of your log directory before you do this.  I accidently wiped all the logs out while messing with it.
</li>

    <li>Create a file <var>config/myproject.logrotate</var>.  Very simple, I just want it to use the same rotation schedule specified for the whole machine in <var>/etc/logrotate.conf</var>


<div class="wp_syntax"><div class="code"><pre class="bash"><span class="sy0">/</span>path<span class="sy0">/</span>to<span class="sy0">/</span>myproject<span class="sy0">/</span>log<span class="sy0">/*</span>.log <span class="br0">&#123;</span>
        weekly
        rotate <span class="nu0">4</span>
<span class="br0">&#125;</span></pre></div></div>



</li>

    <li>Check that it will do stuff by running it with the debug flag


<div class="wp_syntax"><div class="code"><pre class="bash">logrotate <span class="re5">-df</span> config<span class="sy0">/</span>badge.logrotate</pre></div></div>


</li>


    <li>Make a symlink to it in <var>/etc/logrotate.d</var>


<div class="wp_syntax"><div class="code"><pre class="bash"><span class="kw2">ln</span> <span class="re5">-s</span> <span class="sy0">/</span>path<span class="sy0">/</span>to<span class="sy0">/</span>myproject<span class="sy0">/</span>config<span class="sy0">/</span>myproject.logrotate  <span class="sy0">/</span>etc<span class="sy0">/</span>logrotate.d<span class="sy0">/</span>myproject</pre></div></div>



</li>

    <li>Run it the first time manually to rotate your logs and make sure it does the right thing!


<div class="wp_syntax"><div class="code"><pre class="bash"><span class="kw2">sudo</span> logrotate <span class="re5">-fv</span> <span class="sy0">/</span>etc<span class="sy0">/</span>logrotate.d<span class="sy0">/</span>myproject</pre></div></div>



</li>
</ol>
]]></content:encoded>
			<wfw:commentRss>http://n8v.enteuxis.org/2009/10/symfony-log-management-with-logrotate-on-linux/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Making a symfony plug-in for visual theme and custom errors</title>
		<link>http://n8v.enteuxis.org/2009/06/making-a-symfony-plug-in-for-visual-theme-and-custom-errors/</link>
		<comments>http://n8v.enteuxis.org/2009/06/making-a-symfony-plug-in-for-visual-theme-and-custom-errors/#comments</comments>
		<pubDate>Fri, 26 Jun 2009 00:13:38 +0000</pubDate>
		<dc:creator>nathan</dc:creator>
				<category><![CDATA[Figuring IT Out]]></category>
		<category><![CDATA[design]]></category>
		<category><![CDATA[plugin]]></category>
		<category><![CDATA[style]]></category>
		<category><![CDATA[symfony]]></category>
		<category><![CDATA[theme]]></category>

		<guid isPermaLink="false">http://n8v.enteuxis.org/?p=255</guid>
		<description><![CDATA[I manage a growing number of symfony-based Intranet apps that share a common look-and-feel. I use the same stylesheet and images on them all, and it I am nervous every time I make Yet Another Copy of the same files, knowing that I&#8217;m duplicating them and causing trouble for myself. Also, as I wrote in [...]]]></description>
			<content:encoded><![CDATA[<p>I manage a growing number of symfony-based Intranet apps that share a common look-and-feel.  I use the same stylesheet and images on them all, and it I am nervous every time I make Yet Another Copy of the same files, knowing that I&#8217;m duplicating them and causing trouble for myself.</p>

<p>Also, as I wrote in <a href="/2009/03/completely-custom-symfony-error-pages/">my post about custom error pages</a>, I have error pages set up for each, and I hate copying them.  So I have invested some time into building a plugin containing the visual assets and the customized error pages, so that it&#8217;s easier to keep them uniform across all the apps I work on, and so that it will be that much quicker to build new projects.</p>

<p>I don&#8217;t think anyone outside my company will want my plug-in, but these instructions will hopefully be useful for anyone who wants to do the same thing.</p>

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

<p>These instructions are for symfony 1.1 but they should apply equally to 1.0 and 1.2.</p>

<h2>Plugin organization</h2>

<p>Here are the files and directories in my plugin:</p>

<p><pre>plugins/myThemePlugin/
  config/
    view.yml
  modules/
    default/
      config/
        view.yml
      templates/
        disabledSuccess.php
        error404Success.php
  README
  web/
    css/
      mystyles.css
      myerrors.css
      mymain.css
    errors/
      error500.php
      unavailable.php
    images/
      my_swoop.jpg
      bg_sfTAlert.jpg
      bg_sfTLock.jpg
      bg_sfTMessage.jpg
      indicator.gif
      tall_gradient.jpg
</pre></p>

<h2>Using <code>svn:externals</code> to install the plugin in each symfony project</h2>

<p>I only want one copy of this thing, with version control, so I use svn:externals (for my other plugins too ).</p>

<p><pre>$ svn propget svn:externals plugins
myThemePlugin/        https://mysvnserver.com/svn/web/myThemePlugin
sfGuardPlugin/        http://svn.symfony-project.com/plugins/sfGuardPlugin/branches/1.1/
$ svn up
</pre></p>

<h2>Publishing the assets</h2>

<p>The <a href="http://www.symfony-project.org/book/1_2/17-Extending-Symfony#chapter_17_sub_anatomy_of_a_plug_in">chapter on plugins</a> in the <cite>The Definitive Guide to symfony</cite> explains:</p>

<blockquote>
  <p>Web assets (images, scripts, style sheets, etc.) are made available to the server. When you install a plug-in via the command line, symfony creates a symlink to the project <code>web/</code> directory if the system allows it, or copies the content of the module <code>web/</code> directory into the project one. If the plug-in is installed from an archive or a version control repository, you have to copy the plug-in <code>web/</code> directory by hand (as the <code>README</code> bundled with the plug-in should mention).</p>
</blockquote>

<p>As I showed above, I prefer to use <code>svn:externals</code> for my plugins, so this is a bit of a problem for me.  Also, I develop on Windows (with XAMPP and Cygwin), so the more elegant symlink solution is not an option.   Symfony 1.2 has a task called <code>plugin:publish_assets</code> that makes the copies, but that messes up my version control strategy. SO:  I am going to use <code>svn:externals</code> to get the web assets from my plugin too.</p>


<div class="wp_syntax"><div class="code"><pre class="bash">$ <span class="kw2">svn</span> propset <span class="kw2">svn</span>:externals <span class="st0">&quot;myThemePlugin  https://my_svn_server/svn/web/myThemePlugin/web/&quot;</span> web
$ <span class="kw2">svn</span> propget <span class="kw2">svn</span>:externals web
    myThemePlugin  https:<span class="sy0">//</span>my_svn_server<span class="sy0">/</span>svn<span class="sy0">/</span>web<span class="sy0">/</span>myThemePlugin<span class="sy0">/</span>web<span class="sy0">/</span>
$ <span class="kw2">svn</span> up</pre></div></div>


<p>This lets me keep the plugin in its own repository.  You could get a similar effect by just checking out the files, but it would clutter up the SVN for your main project.</p>

<p>The end result:  I have one copy of the web assets in the SVN repository, but they&#8217;re checked out twice into each project (once under <var>plugins/myThemePlugin/web</var> and once under <var>web/myThemePlugin</var>).</p>

<h2><var>view.yml</var> files</h2>

<p>Now the overall <var>view.yml</var> file in <var>plugins/myThemePlugin/config/</var> sets stylesheets for my whole project:</p>


<div class="wp_syntax"><div class="code"><pre class="yaml">default:
  stylesheets:    
    - /myThemePlugin/css/main: { position: last }
    - /myThemePlugin/css/bannerhealth: {position: last}
    - %SF_ADMIN_WEB_DIR%/css/main
    - %SF_CALENDAR_WEB_DIR%/skins/aqua/theme</pre></div></div>


<p>And I have another one to apply some additional stylesheets to my error pages:</p>


<div class="wp_syntax"><div class="code"><pre class="yaml">error404Success:
  metas:
    title:        404 Not Found
  stylesheets: 
    - /sf/sf_default/css/screen.css
    - /sf/sf_default/css/ie.css
    - /myThemePlugin/css/errors: {position: last}
&nbsp;
disabledSuccess:
  metas:
    title:        Temporarily Unavailable
  stylesheets: 
    - /sf/sf_default/css/screen.css
    - /sf/sf_default/css/ie.css
    - /myThemePlugin/css/errors: {position: last}</pre></div></div>


<h3>relative image paths in stylesheets and error pages</h3>

<p>I use relative paths in my stylesheets (served out of <var>myprojecturl/myThemePlugin/css/</var>) so they can find the images (<var>myprojecturl/myThemePlugin/images/</var>):</p>


<div class="wp_syntax"><div class="code"><pre class="css"><span class="kw1">background-image</span><span class="sy0">:</span> <span class="kw2">url</span><span class="br0">&#40;</span><span class="co2">../images/tall_gradient.jpg</span><span class="br0">&#41;</span><span class="sy0">;</span></pre></div></div>


<p>I&#8217;d do the same if my error templates (like <var>plugins/myThemePlugin/modules/default/templates/error404Success.php</var>) used images, but they don&#8217;t.</p>

<p>The pages in <var>plugins/myThemePlugin/web/errors</var> (which are installed via <code>svn:externals</code> now in <var>web/myThemePlugin/errors</var>) are used when things are so broken symfony can&#8217;t build template pages, so they have full HTML structure, including full stylesheet tags:</p>


<div class="wp_syntax"><div class="code"><pre class="php"><span class="kw2">&lt;?php</span> <span class="re0">$path</span> <span class="sy0">=</span> <span class="st_h">'..'</span><span class="sy0">;</span> <span class="sy1">?&gt;</span>
&nbsp;
&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; media=&quot;screen&quot; href=&quot;<span class="kw2">&lt;?php</span> <span class="kw1">echo</span> <span class="re0">$path</span> <span class="sy1">?&gt;</span>/../sf/sf_default/css/screen.css&quot; /&gt;
&lt;!--[if lt IE 7.]&gt;
&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; media=&quot;screen&quot; href=&quot;<span class="kw2">&lt;?php</span> <span class="kw1">echo</span> <span class="re0">$path</span> <span class="sy1">?&gt;</span>/../sf/sf_default/css/ie.css&quot; /&gt;
&lt;![endif]--&gt;
&nbsp;
&nbsp;
&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; media=&quot;screen&quot; href=&quot;<span class="kw2">&lt;?php</span> <span class="kw1">echo</span> <span class="re0">$path</span> <span class="sy1">?&gt;</span>/css/main.css&quot; /&gt;
&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; media=&quot;screen&quot; href=&quot;<span class="kw2">&lt;?php</span> <span class="kw1">echo</span> <span class="re0">$path</span> <span class="sy1">?&gt;</span>/css/bannerhealth.css&quot; /&gt;
&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; media=&quot;screen&quot; href=&quot;<span class="kw2">&lt;?php</span> <span class="kw1">echo</span> <span class="re0">$path</span> <span class="sy1">?&gt;</span>/css/errors.css&quot; /&gt;</pre></div></div>


<h2>Apache error page configuration</h2>

<p>Finally, I tell Apache to use my customized error pages for errors in this project.</p>


<div class="wp_syntax"><div class="code"><pre class="apache">  &lt;<span class="kw3">Directory</span> <span class="st0">&quot;/path/to/myproject/web&quot;</span>&gt;
        <span class="kw1">ErrorDocument</span> <span class="nu0">404</span> /myproject/default/error404
	<span class="kw1">ErrorDocument</span> <span class="nu0">500</span> /myproject/bhThemePlugin/errors/error500.php
  &lt;/<span class="kw3">Directory</span>&gt;</pre></div></div>


<p>There we go!   Now I have a lot less to do each time I make a new app; all the pretty stuff is in my plugin, and there aren&#8217;t any surprisingly brown symfony pages to throw off any users.</p>

<p>What do you think?  Any errors or ommissions or problems?</p>
]]></content:encoded>
			<wfw:commentRss>http://n8v.enteuxis.org/2009/06/making-a-symfony-plug-in-for-visual-theme-and-custom-errors/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>WordPress plugin to make code samples prettier</title>
		<link>http://n8v.enteuxis.org/2009/03/wordpress-plugin-to-make-code-samples-prettier/</link>
		<comments>http://n8v.enteuxis.org/2009/03/wordpress-plugin-to-make-code-samples-prettier/#comments</comments>
		<pubDate>Thu, 26 Mar 2009 00:24:52 +0000</pubDate>
		<dc:creator>nathan</dc:creator>
				<category><![CDATA[Giving back]]></category>
		<category><![CDATA[blogging]]></category>
		<category><![CDATA[symfony]]></category>
		<category><![CDATA[syntax]]></category>
		<category><![CDATA[wordpress]]></category>
		<category><![CDATA[wp-syntax]]></category>

		<guid isPermaLink="false">http://n8v.enteuxis.org/?p=215</guid>
		<description><![CDATA[I&#8217;ve always had a weakness for &#8220;preferences&#8221;. I spent hours going through all the available preference panes on the first Mac I had access to (an SE II I think, ca. 1989), tweaking this and that to get it to look as &#8220;customized&#8221; as possible. Hours setting up font and styles on school assignments, days [...]]]></description>
			<content:encoded><![CDATA[<p>I&#8217;ve always had a weakness for &#8220;preferences&#8221;.  I spent hours going through all the available preference panes on the first Mac I had access to (an <var>SE II</var> I think, ca. 1989), tweaking this and that to get it to look as &#8220;customized&#8221; as possible.  Hours setting up font and styles on school assignments, days banging on my Emacs config to be more like I want.</p>

<p>So it&#8217;s been secretly irking me for a while that although I installed the very nice <a href="http://wordpress.org/extend/plugins/wp-syntax/">WP-Syntax code highlighting plugin </a>for this WordPress blog, it was hard to get it to display things in reverse-video like I prefer for all my terminal and text editor windows, and like I love in <a href="http://www.symfony-project.org/book/">the online Symfony documentation</a>.  It&#8217;s just nicer on the eyes, you know?  Digging in, I found the <a href="http://qbnz.com/highlighter/">GeSHI syntax highlighter</a> to be impressive in scope but scary in its inline-style-generating detail.</p>

<p>So today, to solve the problem for more than just me, I published <a href="http://wordpress.org/extend/plugins/wp-syntax-hacktify/">my very first WordPress plugin, wp-syntax-hacktify</a>.  Very educational!  I like the plugin system at wordpress.org.</p>

<p>The plugin tells GeSHI to use stylesheets instead and provides a <a href="http://svn.wp-plugins.org/wp-syntax-hacktify/trunk/hacktified.css">slightly more commented/documented stylesheet</a> as an example for if you&#8217;d like to override it with your own color scheme.</p>

<p>So now my code samples can be all nicer and stuff, like this:</p>


<div class="wp_syntax"><div class="code"><pre class="php">  <span class="co4">/**
   * Print a string to the log using 'debug' level.  For printf-style
   * debugging.
   *
   * @param      string $m      The string to log
   * @return     nothing
   */</span>
  <span class="kw2">public</span> static <span class="kw2">function</span> debug <span class="br0">&#40;</span><span class="re0">$m</span><span class="br0">&#41;</span> <span class="br0">&#123;</span>
    <span class="kw1">if</span> <span class="br0">&#40;</span>sfConfig<span class="sy0">::</span><span class="me2">has</span><span class="br0">&#40;</span><span class="st_h">'sf_logging_enabled'</span><span class="br0">&#41;</span> <span class="sy0">&amp;&amp;</span> sfConfig<span class="sy0">::</span><span class="me2">get</span><span class="br0">&#40;</span><span class="st_h">'sf_logging_enabled'</span><span class="br0">&#41;</span><span class="br0">&#41;</span>
      <span class="br0">&#123;</span>
        <span class="kw1">if</span> <span class="br0">&#40;</span><span class="re0">$logger</span> <span class="sy0">=</span> sfContext<span class="sy0">::</span><span class="me2">getInstance</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span><span class="me1">getLogger</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="br0">&#41;</span> <span class="br0">&#123;</span>
          <span class="re0">$logger</span><span class="sy0">-&gt;</span><span class="me1">debug</span><span class="br0">&#40;</span><span class="re0">$m</span><span class="br0">&#41;</span><span class="sy0">;</span>
        <span class="br0">&#125;</span>
      <span class="br0">&#125;</span>
    <span class="kw1">elseif</span> <span class="br0">&#40;</span>sfConfig<span class="sy0">::</span><span class="me2">has</span><span class="br0">&#40;</span><span class="st_h">'bhLDAP_echo_debugging'</span><span class="br0">&#41;</span> <span class="sy0">&amp;&amp;</span> sfConfig<span class="sy0">::</span><span class="me2">get</span><span class="br0">&#40;</span><span class="st_h">'bhLDAP_echo_debugging'</span><span class="br0">&#41;</span><span class="br0">&#41;</span>
      <span class="br0">&#123;</span>
        <span class="kw1">echo</span> <span class="st0">&quot;# <span class="es4">$m</span><span class="es1">\n</span>&quot;</span><span class="sy0">;</span>
      <span class="br0">&#125;</span>
    <span class="kw1">else</span>
      <span class="br0">&#123;</span>
<span class="co1">//      echo $m;</span>
      <span class="br0">&#125;</span>
  <span class="br0">&#125;</span>
&nbsp;
  <span class="co4">/**
   * Dump a data structure to the log at the 'debug' level.  Uses
   * print_r() formatting.
   *
   * @param      mixed $v         The variable/data structure to dump
   * @param      string $label    An optional label to print in front of the dump
   * @return     nothing
   */</span>
  <span class="kw2">public</span> static <span class="kw2">function</span> debugDump <span class="br0">&#40;</span><span class="re0">$v</span><span class="sy0">,</span> <span class="re0">$label</span> <span class="sy0">=</span> <span class="st0">&quot;var dump&quot;</span><span class="br0">&#41;</span> <span class="br0">&#123;</span>
    <span class="kw2">self</span><span class="sy0">::</span><span class="me2">debug</span><span class="br0">&#40;</span><span class="st0">&quot;<span class="es4">$label</span>:  &quot;</span> <span class="sy0">.</span> <a href="http://www.php.net/print_r"><span class="kw3">print_r</span></a><span class="br0">&#40;</span><span class="re0">$v</span><span class="sy0">,</span> <span class="kw4">true</span><span class="br0">&#41;</span><span class="br0">&#41;</span><span class="sy0">;</span>
  <span class="br0">&#125;</span></pre></div></div>


<p>Now the code on my blog can look nice even if it&#8217;s terrifically hacky!</p>
]]></content:encoded>
			<wfw:commentRss>http://n8v.enteuxis.org/2009/03/wordpress-plugin-to-make-code-samples-prettier/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>Completely custom Symfony error pages</title>
		<link>http://n8v.enteuxis.org/2009/03/completely-custom-symfony-error-pages/</link>
		<comments>http://n8v.enteuxis.org/2009/03/completely-custom-symfony-error-pages/#comments</comments>
		<pubDate>Tue, 17 Mar 2009 22:39:58 +0000</pubDate>
		<dc:creator>nathan</dc:creator>
				<category><![CDATA[Figuring IT Out]]></category>
		<category><![CDATA[apache]]></category>
		<category><![CDATA[error]]></category>
		<category><![CDATA[symfony]]></category>

		<guid isPermaLink="false">http://n8v.enteuxis.org/?p=177</guid>
		<description><![CDATA[For every Symfony project I work on, I eventually want to get rid of the tasteful brown error pages with the Symfony logo on them and install my own, so the users don&#8217;t get weirded out and somehow start thinking &#8220;Symfony = error&#8221;. Also, I&#8217;m probably overly optimistic, but it would be nice if there [...]]]></description>
			<content:encoded><![CDATA[<p>For every Symfony project I work on, I eventually want to get rid of the tasteful brown error pages with the Symfony logo on them and install my own, so the users don&#8217;t get weirded out and somehow start thinking &#8220;Symfony = error&#8221;.  Also, I&#8217;m probably overly optimistic, but it would be nice if there was a valid email address on the error page so I could hear users complain.</p>

<p><a href="http://www.symfony-project.org/book/1_1/19-Mastering-Symfony-s-Configuration-Files#chapter_19_sub_default_modules_and_actions">The documentation</a> is good, but I want a simpler recipe that also accounts for all the forseeable errors, even those that don&#8217;t get to Symfony.  Some details vary by version, but in short, for symfony 1.1:
<span id="more-177"></span></p>

<h3>Test URLs</h3>

<p>We want to ultimately make sure that these URLs behave the right way:</p>

<h4>Normal errors (must have)&#8211; 404 Not Found and 500 Server Error</h4>

<table cellpadding="3">
<tr style="background: rgb(218, 231, 180);"><td>
<var>http://<samp>localhost/myapp</samp>/default/error404</var>
</td><td>
The &#8220;real&#8221; 404 page&#8230; it&#8217;s actually strangely &#8220;found&#8221; in this case.
</td></tr>

<tr><td>

http://<samp>localhost/myapp/mymodule/view</samp>/id/22222

</td><td>
A <code>forward404Unless</code> style forwarding should get the custom 404 page.  This is really the most likely case&#8211; if a user follows a link to an object another user has deleted, I want them to see the nice page.

</td></tr>



<tr style="background: rgb(218, 231, 180);"><td>
http://<samp>localhost/myapp/</samp>junk.php , or 

http://<samp>localhost/myapp/</samp>images/junk.gif

</td><td>
A request that Apache handles should display the custom 404 page too.
</td></tr>

<tr><td>

http://<samp>localhost/myapp</samp>/errors/error500.php

</td><td>
The &#8220;real&#8221; URL of the 500 error page should work.
</td></tr>

<tr style="background: rgb(218, 231, 180);"><td>

http://<samp>localhost/myapp</samp>/awfijwef/awfijawfo

</td><td>
Another real 404 error&#8211; Symfony forwards nonsensical module/action stuff to the custom 404 page.
</td></tr>


</table>

<p>I have seen error 500 pages countless times during app development, but I just could <strong>NOT</strong> reproduce one to test the 500 error page.  Any quick recipes for that, anyone?</p>

<h4>More esoteric error conditions&#8211; app unavailable/locked, module disabled</h4>

<table cellpadding="3">
<tr><td>
<var>http://localhost/myapp/errors/unavailable.php</var>
</td><td>
The &#8220;unavailable&#8221; page for when a whole symfony app is locked with <kbd>symfony project:disable <samp>app env</samp></kbd>*, or while the cache is being cleared.
</td></tr>


<tr style="background: rgb(218, 231, 180);"><td>
any application URL after I run <kbd>symfony project:disable</kbd> and I have the <code>check_lock</code> setting on*
</td><td>
should go to the &#8216;unavailable&#8217; page (unavailable.php)
</td></tr>

<tr><td>
<var>http://localhost/myapp/default/disabled</var>
</td><td>
The real &#8220;I&#8217;m disabled&#8221; page for when a module is locked with <kbd></kbd> in the <a href="http://www.symfony-project.org/book/1_1/06-Inside-the-Controller-Layer#chapter_06_module_configuration">module&#8217;s config file</a>, e.g. <var>apps/frontend/modules/<samp>mymodule</samp>/config/module.yml</var>
</td></tr>

<tr style="background: rgb(218, 231, 180);"><td>
http://<samp>localhost/myapp/</samp>mymodule/myaction , when I&#8217;ve explicitly disabled the module by putting this in <var>apps/frontend/mymodule/config/module.yml</var>:
<pre>
all:
  enabled:     false
</pre>
</td><td>
The disabled page (above)
</td></tr>


</table>

<h3>Steps</h3>

<ol>

    <li>manually create a few files for the <var>default</var> module.  <strong>DO NOT</strong> use <code>symfony generate:module</code> because you only want to override a small bit of the built-in <var>default</var> module (found in <var>/path/to/symfony/lib/controller/default/templates/</var>* incidentally, if you ever want to see what you&#8217;re overriding).


<div class="wp_syntax"><div class="code"><pre class="bash">$ <span class="kw2">mkdir</span> apps<span class="sy0">/</span>frontend<span class="sy0">/</span>modules<span class="sy0">/</span>default
$ <span class="kw2">mkdir</span> apps<span class="sy0">/</span>frontend<span class="sy0">/</span>modules<span class="sy0">/</span>default<span class="sy0">/</span>config
$ <span class="kw2">mkdir</span> apps<span class="sy0">/</span>frontend<span class="sy0">/</span>modules<span class="sy0">/</span>default<span class="sy0">/</span>templates
$ <span class="kw2">touch</span> apps<span class="sy0">/</span>frontend<span class="sy0">/</span>modules<span class="sy0">/</span>default<span class="sy0">/</span>config<span class="sy0">/</span>view.yml
$ <span class="kw2">touch</span> apps<span class="sy0">/</span>frontend<span class="sy0">/</span>modules<span class="sy0">/</span>default<span class="sy0">/</span>templates<span class="sy0">/</span>error404Success.php
$ <span class="kw2">touch</span> apps<span class="sy0">/</span>frontend<span class="sy0">/</span>modules<span class="sy0">/</span>default<span class="sy0">/</span>templates<span class="sy0">/</span>disabledSuccess.php</pre></div></div>



</li>

    <li>Edit the templates I just made&#8211; remember, the content for these goes inside your sitewide <var>layout.php</var>.

I started out by copying the default templates in <var>lib/controller/default/templates/</var> from my Symfony distribution.
</li>
    <li>create mostly static pages for <var>web/errors/error500.php</var> and <var>unavailable.php</var>***  They won&#8217;t be wrapped in layout.php so they need to have the full page structure, and basically no symfony-involving PHP code (like helpers, too bad), though I do use a bit of really basic stuff, like including the URL in my mailto form on the 500 page:


<div class="wp_syntax"><div class="code"><pre class="php">  &lt;dl class=&quot;sfTMessageInfo&quot;&gt;
    &lt;dt&gt;Something is terribly broken.&lt;/dt&gt;
    &lt;dd&gt;
	  &lt;p&gt;
Please 
&lt;a href=&quot;mailto:webmaster@mycompany.com?subject=500 error at http://
<span class="kw2">&lt;?php</span> <span class="kw1">echo</span> <span class="re0">$_SERVER</span><span class="br0">&#91;</span><span class="st_h">'SERVER_NAME'</span><span class="br0">&#93;</span> <span class="sy0">.</span> <span class="st_h">'/'</span><span class="sy0">.</span> <span class="re0">$_SERVER</span><span class="br0">&#91;</span><span class="st_h">'REQUEST_URI'</span><span class="br0">&#93;</span>  <span class="sy1">?&gt;</span>
<span class="kw2">&lt;?php</span> 
<span class="kw1">if</span> <span class="br0">&#40;</span><a href="http://www.php.net/array_key_exists"><span class="kw3">array_key_exists</span></a><span class="br0">&#40;</span><span class="st_h">'HTTP_REFERER'</span><span class="sy0">,</span> <span class="re0">$_SERVER</span><span class="br0">&#41;</span><span class="br0">&#41;</span> <span class="br0">&#123;</span>
  <span class="kw1">echo</span> <span class="st0">&quot;&amp;body=Referrer: &quot;</span><span class="sy0">.</span><span class="re0">$_SERVER</span><span class="br0">&#91;</span><span class="st_h">'HTTP_REFERER'</span><span class="br0">&#93;</span><span class="sy0">;</span>
<span class="br0">&#125;</span>
 <span class="sy1">?&gt;</span>&quot;&gt;
e-mail us at webmaster@mycompany.com&lt;/a&gt; and let us know what you were doing when this error occurred. We will fix it as soon as possible.
&lt;/p&gt;
	  &lt;p&gt;
   If you have an urgent problem, please call the Support Desk at XXXX.
&lt;/p&gt;
	  &lt;p&gt;
   You may also find the &lt;a href=&quot;/README.html&quot;&gt;documentation&lt;/a&gt; useful.
&lt;/p&gt;
&lt;/dd&gt;
&lt;/dl&gt;</pre></div></div>



</li>

    <li>Include my stylesheets, in <var>view.yml</var>:

<pre>
error404Success:
  metas:
    title:        ED Log 404 Not Found
  stylesheets:
    - -*
    - mystyles1: { position: last }
    - mystyles2: {position: last}
    - /sf/sf_default/css/screen.css
    - /sf/sf_default/css/ie.css
    - errors: {position: last}
</pre>
(and the same thing for <code>disabledSuccess:</code>)

My <var>web/css/errors.css</var>  overrides some of the default symfony error page styles (compare <var>web/sf/sf_default/css/screen.css</var> in your symfony distribution) to make them less brown, but I keep the general thing.
</li>

    <li>If your application has security on by default, make sure to un-secure the error pages so people can see them without logging in.  Add these above the <code>default</code> rule in <var>apps/frontend/config/security.yml</var>:

<pre>
error404:
  is_secure: off

disabled:
  is_secure: off
</pre>
</li>


    <li>Tell Apache where to find total 500 errors and 404 errors that don&#8217;t make it to symfony (like anything that doesn&#8217;t get to a front controller, like a misspelled controller or image name):


<div class="wp_syntax"><div class="code"><pre class="apache">  &lt;<span class="kw3">Directory</span> <span class="st0">&quot;/var/www/my_sf_app/web&quot;</span>&gt;
        <span class="kw1">ErrorDocument</span> <span class="nu0">404</span> /default/error404
	<span class="kw1">ErrorDocument</span> <span class="nu0">500</span> /errors/error500.php
  &lt;/<span class="kw3">Directory</span>&gt;</pre></div></div>



</li>

<li>Hrm, the table of URLs above sounds like a good test script&#8230; <strong>BUT</strong>, it resists scripting because most of the error URLs are outside symfony, so you don&#8217;t get the custom error pages when going to an actual error with sfTestBrowser, or even a regular sfBrowser, because instead you get fancy exception stuff.  

So, here&#8217;s a pretty lame test script I put in <var>test/functional/frontend/customErrors.php</var> that tests the easiest cases, but it&#8217;s not hard to manually try the others with a browser.  I welcome suggestions for improvement!


<div class="wp_syntax"><div class="code"><pre class="php">&nbsp;
<span class="kw2">&lt;?php</span>
&nbsp;
<span class="kw1">include</span><span class="br0">&#40;</span><a href="http://www.php.net/dirname"><span class="kw3">dirname</span></a><span class="br0">&#40;</span><span class="kw4">__FILE__</span><span class="br0">&#41;</span><span class="sy0">.</span><span class="st_h">'/../../bootstrap/functional.php'</span><span class="br0">&#41;</span><span class="sy0">;</span>
&nbsp;
<span class="re0">$e404_regexp</span> <span class="sy0">=</span> <span class="st0">&quot;webmaster@mycompany&quot;</span><span class="sy0">;</span>
<span class="re0">$disabled_regexp</span> <span class="sy0">=</span> <span class="st0">&quot;xXXXX&quot;</span><span class="sy0">;</span>
&nbsp;
<span class="co1">// create a new test browser</span>
<span class="re0">$browser</span> <span class="sy0">=</span> <span class="kw2">new</span> sfTestBrowser<span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">;</span>
&nbsp;
<span class="coMULTI">/* http://localhost/myapp/default/error404	 
The &quot;real&quot; 404 page */</span>
&nbsp;
<span class="re0">$browser</span><span class="sy0">-&gt;</span>
  <span class="me1">get</span><span class="br0">&#40;</span><span class="st_h">'/default/error404'</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
  <span class="me1">isStatusCode</span><span class="br0">&#40;</span><span class="nu0">404</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
  <span class="me1">isRequestParameter</span><span class="br0">&#40;</span><span class="st_h">'module'</span><span class="sy0">,</span> <span class="st_h">'default'</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
  <span class="me1">isRequestParameter</span><span class="br0">&#40;</span><span class="st_h">'action'</span><span class="sy0">,</span> <span class="st_h">'error404'</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
  <span class="me1">checkResponseElement</span><span class="br0">&#40;</span><span class="st_h">'body'</span><span class="sy0">,</span> <span class="st0">&quot;/<span class="es4">$e404_regexp</span>/&quot;</span><span class="br0">&#41;</span><span class="sy0">;</span>
&nbsp;
&nbsp;
<span class="coMULTI">/* http://localhost/myapp/default/disabled	 
The &quot;unavailable&quot; page for when symfony is locked with symfony project:disable */</span>
&nbsp;
<span class="re0">$browser</span><span class="sy0">-&gt;</span>
  <span class="me1">get</span><span class="br0">&#40;</span><span class="st_h">'/default/disabled'</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
  <span class="me1">isStatusCode</span><span class="br0">&#40;</span><span class="nu0">200</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
  <span class="me1">isRequestParameter</span><span class="br0">&#40;</span><span class="st_h">'module'</span><span class="sy0">,</span> <span class="st_h">'default'</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
  <span class="me1">isRequestParameter</span><span class="br0">&#40;</span><span class="st_h">'action'</span><span class="sy0">,</span> <span class="st_h">'disabled'</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span>
  <span class="me1">checkResponseElement</span><span class="br0">&#40;</span><span class="st_h">'body'</span><span class="sy0">,</span> <span class="st0">&quot;/<span class="es4">$disabled_regexp</span>/&quot;</span><span class="br0">&#41;</span><span class="sy0">;</span></pre></div></div>




And a few passing tests is better than none, right?

<pre>
$ <kbd>php ./symfony test:functional frontend customError</kbd>
# get /default/error404
ok 1 - status code is 404
ok 2 - request parameter module is default
ok 3 - request parameter action is error404
ok 4 - response selector body matches regex /webmaster@mycompany/
# get /default/disabled
ok 5 - status code is 200
ok 6 - request parameter module is default
ok 7 - request parameter action is disabled
ok 8 - response selector body matches regex /xXXXX/
1..8
 Looks like everything went fine.
</pre>
</li>

</ol>

<p>Sheesh, it&#8217;s still a little too involved for such a small benefit.  If anyone has any ideas on how to streamline this procedure, let me know!  Maybe it could be a plugin or an enhancement to symfony?</p>

<ul>
<li>Disabling projects only works if the <a href="http://www.symfony-project.org/book/1_1/19-Mastering-Symfony-s-Configuration-Files#chapter_19_sub_default_modules_and_actions"><code>check_lock</code>  option</a> is enabled in e.g. <var>apps/frontend/config/settings.yml</var></li>
</ul>

<p>** Symfony 1.0 default templates are found in <var>&#8230;/symfony/data/modules/default/templates/</var> and include an &#8216;unavailable&#8217; template in addition to &#8216;disabled&#8217;.  Symfony 1.2 templates are in the same place as in 1.1.</p>

<p>*** Symfony 1.2 wants its 500 error page in <var>config/error/error.html.php</var></p>
]]></content:encoded>
			<wfw:commentRss>http://n8v.enteuxis.org/2009/03/completely-custom-symfony-error-pages/feed/</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
		<item>
		<title>LDAP powered by MySQL on RHEL5</title>
		<link>http://n8v.enteuxis.org/2009/02/ldap-powered-by-mysql-on-rhel5/</link>
		<comments>http://n8v.enteuxis.org/2009/02/ldap-powered-by-mysql-on-rhel5/#comments</comments>
		<pubDate>Tue, 24 Feb 2009 02:01:12 +0000</pubDate>
		<dc:creator>nathan</dc:creator>
				<category><![CDATA[Figuring IT Out]]></category>
		<category><![CDATA[LDAP]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[MySQL]]></category>
		<category><![CDATA[Perl]]></category>
		<category><![CDATA[symfony]]></category>
		<category><![CDATA[sysadmin]]></category>

		<guid isPermaLink="false">http://n8v.enteuxis.org/?p=133</guid>
		<description><![CDATA[Background We need a quick-and-dirty LDAP server with basically a list of usernames and passwords to point Cisco ACS gear at, in order to provide people with limited-time authentication tokens to a wireless network. I need to populate that with an HL7 interface in Perl and make a web app UI frontend (preferably using symfony). [...]]]></description>
			<content:encoded><![CDATA[<h3>Background</h3>

<p>We need a quick-and-dirty <abbrev title="Lightweight Directory Access Protocol">LDAP</abbrev> server with basically a list of usernames and passwords to point Cisco ACS gear at, in order to provide people with limited-time authentication tokens to a wireless network.  I need to populate that with an <a href="http://hl7toolkit.sf.net">HL7 interface in Perl</a> and make a web app UI frontend (preferably using symfony).  The latter two interfaces are well established around here with MySQL, so I am trying to use OpenLDAP&#8217;s SQL backend to make that happen naturally.</p>

<h4>Simplifications</h4>

<p>Unlike other similar implementations out there, I have smaller goals:</p>

<ul>
    <li>The database will be updated/written by the HL7 interface and web interface.  The LDAP tools don&#8217;t need to write to it.</li>
    <li>All my user data should fit nicely into two tables (users and groups&#8230; and another table to relate many-to-many), and two Objectclasses in LDAPland.</li>
</ul>

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

<h3>Resources</h3>

<p>These were unexpectedly hard to Google up, which is partly why I&#8217;m documenting them here for Posterity.</p>

<ul>
    <li>Zytrax has an open source book about LDAP entitled <a href="http://www.zytrax.com/books/ldap/">LDAP for Rocket Scientists</a>, which has <a href="http://www.zytrax.com/books/ldap/ch2/index.html#basic">an excellent chapter about LDAP concepts</a>, which is nice since I don&#8217;t really understand LDAP like I do the other technologies involved.</li>

    <li><a href="http://www.openldap.org/doc/admin24/backends.html#SQL">Section &#8220;10.10.2. back-sql Configuration&#8221; of the OpenLDAP manual</a></li>
    <li>The <a href="http://linux.die.net/man/5/slapd-sql">slapd-sql man page</a> is indispensible.  See especially the &#8220;METAINFORMATION USED&#8221; section.</li>
    <li>The <a href="http://www.openldap.org/devel/cvsweb.cgi/~checkout~/servers/slapd/back-sql/rdbms_depend/README?rev=1.4.4.1&#038;hideattic=1&#038;sortbydate=0">README that comes with the back-sql samples</a></li>
    <li>The samples themselves (either unpack <var>servers/slapd/back-sql/rdbms_depend/mysql</var> from the <a href="http://www.openldap.org/software/download/">source tarball </a>or <a href="http://www.openldap.org/devel/cvsweb.cgi/servers/slapd/back-sql/rdbms_depend/mysql/?hideattic=1&#038;sortbydate=0">browse them</a>, but you&#8217;ll probably want them unpacked so you can feed the schema to the <var>mysql </var> client.  MOST OF WHAT YOU NEED TO LEARN is demonstrated in the samples.</li>
    <li>This <a href="http://www.flatmtn.com/article/setting-ldap-back-sql#LdapBacksql-2">4-year-old blog article at/by &#8220;Flat Mountain&#8221;</a> is sketchy but an improvement over the documents above!</li>
    <li><a href="http://www.docunext.com/blog/2006/10/openldap-mysql-documentation.html">Albert Lash at DocuNext has a 2-year-old post similar to this one </a> with another list of fine references! 
    <li>This <a href="http://www.darold.net/projects/ldap_pg/HOWTO/index.html">HOWTO for OpenLDAP and Postgresql</a> by Gilles Darold is far superior than the other articles, but needs adaptation of course for MySQL</li>
    <li>Likewise, here&#8217;s <a href="http://www.easysoft.com/applications/openldap/back-sql-odbc.html">a good article for doing the same thing with Oracle/MSSQL</a></li>
    <li>And here are some <a href="http://www.cmu.edu/acs/ldap/sql_notes">extended notes from someone who did it with Oracle</a>, including another explanation of the data mapping part.</li>

</ul>

<h3>I. Install Stuff</h3>

<p>I&#8217;m using <abbr title="Red Hat Enterprise Linux 5.0">RHEL5</abbr> on a virtual machine.  Your package system may vary.  You may in fact have to compile from source to get SQL support in LDAP.</p>


<div class="wp_syntax"><div class="code"><pre class="bash">$ <span class="kw2">sudo</span> yum <span class="kw2">install</span> openldap openldap-clients openldap-servers openldap-devel openldap-servers-sql</pre></div></div>


<h3>II. Get and unpack the source code for the MySQL samples</h3>

<ol>
    <li><a href="http://www.openldap.org/software/download/">Download the source tarball</a> to your machine</li>
    <li>Unpack it


<div class="wp_syntax"><div class="code"><pre class="bash"><span class="kw2">tar</span> zxf openldap-stable-<span class="nu0">20080813</span>.tgz</pre></div></div>



</li>
</ol>

<h3>III. Create the database and tables</h3>

<p>Make sure the mysql server is running of course.  We&#8217;re assuming it&#8217;s on the same host as the LDAP server.</p>

<p>Create the database (<samp>my_db_name</samp>) and a password-having user (<samp>my_mysql_user</samp>) with all privileges to that database (I recommend the MySQL Administrator GUI for this, strangely enough since I usually dislike clicking on stuff).  You should be able to connect to your database like this now:</p>


<div class="wp_syntax"><div class="code"><pre class="bash">$ mysql -umy_mysql_user <span class="re5">-pxxxxxxxxxxxxx</span>  my_db_name</pre></div></div>


<p>Build the tables that configure how OpenLDAP talks to the database.</p>

<ol>
    <li><var>cd</var> into <var>servers/slapd/back-sql/rdbms_depend/mysql</var> from the unpacked openldap source</li>
    <li>Feed <var>backsql_create.sql</var> to the &#8216;mysql&#8217; client


<div class="wp_syntax"><div class="code"><pre class="bash">$ mysql -umy_mysql_user <span class="re5">-pxxxxxxxxxxxxx</span>  my_db_name <span class="sy0">&lt;</span> backsql_create.sql</pre></div></div>



</li>
</ol>

<h3>IV. Hack the basic config files</h3>

<h4>a. <var>slapd.conf</var></h4>

<p>Basically I&#8217;m starting from the <var>slapd.conf</var> in the rdbms_depend/mysql sample (see above), but folding in some RedHatese file locations from the distributed <var>slapd.conf</var>.</p>

<p>I define an adminstrative user with the <code>rootdn </code> and <code>rootpw</code> directives.  Use <var>slappasswd</var> to generate the hashed password:</p>


<div class="wp_syntax"><div class="code"><pre class="bash">$ slappasswd <span class="re5">-h</span> <span class="br0">&#123;</span>crypt<span class="br0">&#125;</span></pre></div></div>


<p>and type in the password.  It will spit out the string you need, which is prefixed by the algorithm in curly braces.</p>


<div class="wp_syntax"><div class="code"><pre class="ini"># See slapd.conf<span class="br0">&#40;</span><span class="nu0">5</span><span class="br0">&#41;</span> for details on configuration options.
# This file should NOT be world readable.
#
include         /etc/openldap/schema/core.schema
include         /etc/openldap/schema/cosine.schema
include         /etc/openldap/schema/inetorgperson.schema
&nbsp;
pidfile         /var/run/openldap/slapd.pid
argsfile        /var/run/openldap/slapd.args
# Timeout in seconds, <span class="nu0">0</span> <span class="sy0">=</span><span class="re2"> never</span>
idletimeout     <span class="nu0">30</span>
threads         <span class="nu0">32</span>
# Debuging level, <span class="nu0">0</span> <span class="sy0">=</span><span class="re2"> none</span>
loglevel        <span class="nu0">32</span>
&nbsp;
# THIS IS IMPORTANT-- without it you will get <span class="st0">&quot;&lt;database&gt; failed init (sql)!&quot;</span>
# Load dynamic backend modules:
modulepath    /usr/lib/openldap
# modules available in openldap-servers-sql RPM package:
moduleload back_sql.la
&nbsp;
&nbsp;
#######################################################################
# SQL Database Definitions
#######################################################################
&nbsp;
database        sql
suffix          <span class="st0">&quot;ou=Junk,ou=AK,DC=mycompany,DC=com&quot;</span>
rootdn          <span class="st0">&quot;cn=root,ou=Users,ou=Junk,ou=AK,DC=,mycompany,DC=com&quot;</span>
rootpw          <span class="br0">&#123;</span>CRYPT<span class="br0">&#125;</span>xxxxxxxxxxxx
&nbsp;
#  The name of the ODBC datasource <span class="br0">&#40;</span>from odbc.ini,
#     not necessarily the database instance<span class="br0">&#41;</span> to use.
dbname          my_database
dbuser          my_database_user
dbpasswd        xxxxxxxxxxxxxxxx
&nbsp;
# this is important because my simple view for the ldap_entries table
# has no 'organization' object.  See `man slapd-sql`.
baseObject
&nbsp;
subtree_cond    <span class="st0">&quot;ldap_entries.dn LIKE CONCAT('%',?)&quot;</span>
insentry_stmt   <span class="st0">&quot;INSERT INTO ldap_entries (dn,oc_map_id,parent,keyval) VALUES (?,?,?,?)&quot;</span>
has_ldapinfo_dn_ru      no
&nbsp;
&nbsp;
#######################################################################
# Access Control
#    see http://www.openldap.org/doc/admin24/access-control.html
#    and `man slapd.access`
#######################################################################
&nbsp;
# Here we define who can see which parts of the directory
&nbsp;
# Hide the password attribute <span class="br0">&#40;</span>except for the root user of course<span class="br0">&#41;</span> but
#   allow authentication against it
access to attrs<span class="sy0">=</span><span class="re2">userPassword</span>
        by anonymous auth
        by * none
&nbsp;
# don't allow anonymous access
# allow authenticated users to see their own entry
access to *
        by self read
        by * none</pre></div></div>


<p>I started without the Access Control section and added it after I had things basically working.</p>

<h4>b. ODBC config files</h4>

<p>In my case they are in <var>/etc/odbc.ini</var> and <var>/etc/odbcinst.ini</var> already existed and just needed a MySQL section added to <var>odbcinst.ini</var> and a section for this particular database in <var>odbc.ini</var>, but I had to get the paths right for Red Hat.  See the MySQL section of step 2 of the <a href="http://www.openldap.org/devel/cvsweb.cgi/~checkout~/servers/slapd/back-sql/rdbms_depend/README?rev=1.5&#038;hideattic=1&#038;sortbydate=0">samples README document</a></p>

<p><var>/etc/odbcinst.ini</var>:</p>


<div class="wp_syntax"><div class="code"><pre class="ini"># Driver from the MyODBC package
# Setup from the unixODBC package
<span class="re0"><span class="br0">&#91;</span>MySQL<span class="br0">&#93;</span></span>
<span class="re1">Description</span>     <span class="sy0">=</span><span class="re2"> ODBC for MySQL</span>
<span class="re1">Driver</span>          <span class="sy0">=</span><span class="re2"> /usr/lib/libmyodbc3.so  # note, filename includes a 3, differs from docs</span>
<span class="re1">Setup</span>           <span class="sy0">=</span><span class="re2"> /usr/lib/libodbcmyS.so</span>
<span class="re1">FileUsage</span>       <span class="sy0">=</span><span class="re2"> 1</span></pre></div></div>


<p><var>/etc/odbc.ini</var></p>


<div class="wp_syntax"><div class="code"><pre class="ini"><span class="re0"><span class="br0">&#91;</span>my_database<span class="br0">&#93;</span></span>
<span class="re1">Description</span>         <span class="sy0">=</span><span class="re2"> my database</span>
<span class="re1">Driver</span>              <span class="sy0">=</span><span class="re2"> MySQL</span>
<span class="re1">Trace</span>               <span class="sy0">=</span><span class="re2"> No</span>
<span class="re1">Database</span>            <span class="sy0">=</span><span class="re2"> my_db_name</span>
<span class="re1">Servername</span>          <span class="sy0">=</span><span class="re2"> localhost</span>
<span class="re1">UserName</span>            <span class="sy0">=</span><span class="re2"> my_database_user</span>
<span class="re1">Password</span>            <span class="sy0">=</span><span class="re2"> xxxxxxxxxxxxxxxxx</span>
<span class="re1">ReadOnly</span>            <span class="sy0">=</span><span class="re2"> No</span>
<span class="re1">RowVersioning</span>       <span class="sy0">=</span><span class="re2"> No</span>
<span class="re1">ShowSystemTables</span>    <span class="sy0">=</span><span class="re2"> No</span>
<span class="re1">ShowOidColumn</span>       <span class="sy0">=</span><span class="re2"> No</span>
<span class="re1">FakeOidIndex</span>        <span class="sy0">=</span><span class="re2"> No</span>
<span class="re1">ConnSettings</span>        <span class="sy0">=</span>
<span class="re1">SOCKET</span>              <span class="sy0">=</span><span class="re2"> /var/lib/mysql/mysql.sock</span></pre></div></div>


<p>(OK, here&#8217;s where I dinked with the example database and read lots of examples and stuff, but hopefully you can skip that because of this excellent documentation <img src='http://n8v.enteuxis.org/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' />  )</p>

<h3>V. Create and populate your user and group tables</h3>

<p>These are my basic tables of users and groups:</p>


<div class="wp_syntax"><div class="code"><pre class="sql"><span class="kw1">CREATE</span> <span class="kw1">TABLE</span> my_user <span class="br0">&#40;</span>
  id <span class="kw1">INT</span><span class="br0">&#40;</span><span class="nu0">10</span><span class="br0">&#41;</span> <span class="kw1">UNSIGNED</span> <span class="kw1">NOT</span> <span class="kw1">NULL</span> <span class="kw1">AUTO_INCREMENT</span><span class="sy0">,</span>
  username <span class="kw1">VARCHAR</span><span class="br0">&#40;</span><span class="nu0">45</span><span class="br0">&#41;</span> <span class="kw1">NOT</span> <span class="kw1">NULL</span><span class="sy0">,</span>
  password <span class="kw1">VARCHAR</span><span class="br0">&#40;</span><span class="nu0">45</span><span class="br0">&#41;</span> <span class="kw1">NOT</span> <span class="kw1">NULL</span><span class="sy0">,</span>
  first_name <span class="kw1">VARCHAR</span><span class="br0">&#40;</span><span class="nu0">45</span><span class="br0">&#41;</span> <span class="kw1">DEFAULT</span> <span class="kw1">NULL</span><span class="sy0">,</span>
  last_name <span class="kw1">VARCHAR</span><span class="br0">&#40;</span><span class="nu0">45</span><span class="br0">&#41;</span> <span class="kw1">DEFAULT</span> <span class="kw1">NULL</span><span class="sy0">,</span>
  <span class="kw1">PRIMARY</span> <span class="kw1">KEY</span>  <span class="br0">&#40;</span>id<span class="sy0">,</span>username<span class="br0">&#41;</span>
<span class="br0">&#41;</span>;
&nbsp;
<span class="kw1">CREATE</span> <span class="kw1">TABLE</span> my_group <span class="br0">&#40;</span>
  id <span class="kw1">INT</span><span class="br0">&#40;</span><span class="nu0">10</span><span class="br0">&#41;</span> <span class="kw1">UNSIGNED</span> <span class="kw1">NOT</span> <span class="kw1">NULL</span> <span class="kw1">AUTO_INCREMENT</span><span class="sy0">,</span>
  <span class="st0">`name`</span> <span class="kw1">VARCHAR</span><span class="br0">&#40;</span><span class="nu0">45</span><span class="br0">&#41;</span> <span class="kw1">NOT</span> <span class="kw1">NULL</span><span class="sy0">,</span>
  <span class="kw1">PRIMARY</span> <span class="kw1">KEY</span>  <span class="br0">&#40;</span>id<span class="br0">&#41;</span>
<span class="br0">&#41;</span>;
&nbsp;
<span class="kw1">CREATE</span> <span class="kw1">TABLE</span> my_group_member <span class="br0">&#40;</span>
  <span class="co1">-- unique id isn't necessary but easier for symfony</span>
  id <span class="kw1">INT</span><span class="br0">&#40;</span><span class="nu0">10</span><span class="br0">&#41;</span> <span class="kw1">UNSIGNED</span> <span class="kw1">NOT</span> <span class="kw1">NULL</span> <span class="kw1">AUTO_INCREMENT</span><span class="sy0">,</span>
  my_user_id <span class="kw1">INT</span><span class="br0">&#40;</span><span class="nu0">10</span><span class="br0">&#41;</span> <span class="kw1">UNSIGNED</span> <span class="kw1">NOT</span> <span class="kw1">NULL</span><span class="sy0">,</span>
  my_group_id <span class="kw1">VARCHAR</span><span class="br0">&#40;</span><span class="nu0">45</span><span class="br0">&#41;</span> <span class="kw1">NOT</span> <span class="kw1">NULL</span><span class="sy0">,</span>
  <span class="kw1">PRIMARY</span> <span class="kw1">KEY</span>  <span class="br0">&#40;</span>id<span class="br0">&#41;</span><span class="sy0">,</span>
  <span class="kw1">UNIQUE</span> <span class="kw1">KEY</span> uniquepair <span class="br0">&#40;</span>my_user_id<span class="sy0">,</span>my_group_id<span class="br0">&#41;</span>
<span class="br0">&#41;</span>;</pre></div></div>


<p>You&#8217;ll need to populate the tables with a user or two and at least one group, of course.</p>

<h3>VI. Insert the special configuration rows</h3>

<p>I entered these data sets using the <a href="http://dev.mysql.com/downloads/gui-tools/">MySQL Administrator</a> GUI.  The most helpful explanation was in the &#8220;METAINFORMATION USED&#8221; section of the <a href="http://linux.die.net/man/5/slapd-sql">slapd-sql man page</a>.</p>

<p>Basically, the <samp>ldap_*</samp> tables show the SQL backend how to map the database tables onto LDAP objects.  <a href="http://www.oav.net/mirrors/LDAP-ObjectClasses.html#atDisplayName">This is a nice reference to the common LDAP object schemas</a>.  That&#8217;s important because you can only map your database fields to fields in the schema for the objectClass you&#8217;re using (unless you want to start making your own schema, which is beyond my scope here).  In my case I&#8217;m using <var>inetOrgPerson</var>, which inherits from <var>organizationalPerson</var>, which inherits from <var>Person</var>, and for groups, <var>groupOfUniqueNames</var>.</p>

<p>The <strong>ldap_entry_objclasses</strong> table needs one row for each objectClass we&#8217;re using:</p>

<table border=1 cellspacing=1 cellpadding=0><tr>
<th>entry_id</th><th>oc_name</th></tr>
<tr>
<td>1</td><td>inetOrgPerson</td></tr>
<tr>
<td>2</td><td>groupOfUniqueNames</td></tr>
</table>

<p>The <strong>ldap_oc_mappings</strong> table also has a row for each objectClass, and it shows the sql backend how to find objects corresponding to those object classes.</p>

<table border=1 cellspacing=1 cellpadding=0><tr>
<th>id</th><th>name</th><th>keytbl</th><th>keycol</th><th>create_proc</th><th>delete_proc</th><th>expect_return</th></tr>
<tr>
<td>1</td><td>inetOrgPerson</td><td>my_user</td><td>id</td><td></td><td></td><td>0</td></tr>
<tr>
<td>2</td><td>groupOfUniqueNames</td><td>my_group</td><td>id</td><td></td><td></td><td>0</td></tr>
</table>

<p>The <strong>ldap_attr_mappings</strong> table is the most complex part.  Basically you&#8217;re showing the sql backend how to build queries that map the database columns onto LDAP attributes.</p>

<table border=1 cellspacing=1 cellpadding=0><tr>
<th>id</th><th>oc_<wbr />map_<wbr />id</th><th>name</th><th>sel_<wbr />expr</th><th>sel_<wbr />expr<wbr />_u</th><th>from_tbls</th><th>join_where</th><th>add_proc</th><th>delete_<wbr />proc</th><th>param_<wbr />order</th><th>expect_<wbr />return</th></tr>
<tr>
<td>1</td><td>1</td><td>cn</td><td>concat( first_name, &#8216; &#8216;, last_name )</td><td></td><td>my_user</td><td></td><td></td><td></td><td>3</td><td>0</td></tr>
<tr>
<td>2</td><td>1</td><td>givenName</td><td>first_name</td><td></td><td>my_user</td><td></td><td></td><td></td><td>3</td><td>0</td></tr>
<tr>
<td>3</td><td>1</td><td>sn</td><td>last_name</td><td></td><td>my_user</td><td></td><td></td><td></td><td>3</td><td>0</td></tr>
<tr>
<td>4</td><td>1</td><td>userPassword</td><td>password</td><td></td><td>my_user</td><td></td><td></td><td></td><td>3</td><td>0</td></tr>
<tr>
<td>5</td><td>1</td><td>uid</td><td>username</td><td></td><td>my_user</td><td></td><td></td><td></td><td>3</td><td>0</td></tr>
<tr>
<td>7</td><td>2</td><td>cn</td><td>name</td><td></td><td>my_group</td><td></td><td></td><td></td><td>3</td><td>0</td></tr>
<tr>
<td>8</td><td>2</td><td>uniqueMember</td><td>dn</td><td></td><td>my_group, my_<wbr />group_<wbr />member gu, my_<wbr />user u, ldap_<wbr />entries</td><td>my_group.id = gu.<wbr />my_<wbr />group_<wbr />id and gu.<wbr />my_<wbr />user_<wbr />id = u.id and oc_<wbr />map_<wbr />id = 1 and keyval = u.id</td><td></td><td></td><td>3</td><td>0</td></tr>
</table>

<h4>Create a view for the <abbrev title="Distinguished Name">DN</abbrev>s in ldap_entries</h4>

<p>I&#8217;m going to use the username field in my DN instead of the common name (CN) in most examples, because it&#8217;s more guaranteed to be unique, and <a href="http://www.zytrax.com/books/ldap/apa/dn-rdn.html">it is apparently OK to do so</a>.</p>


<div class="wp_syntax"><div class="code"><pre class="sql"><span class="kw1">DROP</span> <span class="kw1">TABLE</span> ldap_entries;
&nbsp;
<span class="kw1">CREATE</span> <span class="kw1">VIEW</span> ldap_entries <span class="kw1">AS</span>
  <span class="kw1">SELECT</span> 
    <span class="co1">-- differentiate id spaces here </span>
    <span class="br0">&#40;</span><span class="nu0">100000</span> <span class="sy0">+</span> my_user<span class="sy0">.</span>id<span class="br0">&#41;</span> <span class="kw1">AS</span> id<span class="sy0">,</span>
&nbsp;
    <span class="co1">-- calculate a DN for each user</span>
    ucase<span class="br0">&#40;</span>concat<span class="br0">&#40;</span>
     <span class="st0">'uid='</span><span class="sy0">,</span> my_user<span class="sy0">.</span>username<span class="sy0">,</span> 
     <span class="st0">',ou=Users,ou=Junk,ou=AK,DC=mycompany,DC=com'</span>
    <span class="br0">&#41;</span><span class="br0">&#41;</span> <span class="kw1">AS</span> dn<span class="sy0">,</span> 
&nbsp;
    <span class="co1">-- the id of the inetOrgUser objectClass in ldap_entry_objclasses</span>
    <span class="nu0">1</span> <span class="kw1">AS</span> oc_map_id<span class="sy0">,</span>    
&nbsp;
    <span class="co1">-- zero for every row </span>
    <span class="co1">-- (see baseObject in the `slapd-sql` man page)</span>
    <span class="nu0">0</span> <span class="kw1">AS</span> parent<span class="sy0">,</span>
&nbsp;
    <span class="co1">-- the real id in the user table</span>
    my_user<span class="sy0">.</span>id <span class="kw1">AS</span> keyval 
&nbsp;
  <span class="kw1">FROM</span>
    my_user 
&nbsp;
<span class="kw1">UNION</span> 
&nbsp;
  <span class="kw1">SELECT</span> 
    <span class="co1">-- a different id space</span>
    <span class="br0">&#40;</span><span class="nu0">200000</span> <span class="sy0">+</span> my_group<span class="sy0">.</span>id<span class="br0">&#41;</span> <span class="kw1">AS</span> id<span class="sy0">,</span>
&nbsp;
    <span class="co1">-- calculate a DN for each object in our group table </span>
    ucase<span class="br0">&#40;</span>concat<span class="br0">&#40;</span>
    <span class="st0">'cn='</span><span class="sy0">,</span>my_group<span class="sy0">.</span>name<span class="sy0">,</span>
     <span class="st0">',ou=Users,ou=Junk,ou=AK,DC=mycompany,DC=com'</span>
    <span class="br0">&#41;</span><span class="br0">&#41;</span> <span class="kw1">AS</span> dn
&nbsp;
    <span class="co1">-- the id of groupOfUniqueNames in ldap_entry_objclasses</span>
    <span class="nu0">2</span> <span class="kw1">AS</span>  oc_map_id<span class="sy0">,</span>
&nbsp;
    <span class="co1">-- like above</span>
    <span class="nu0">0</span> <span class="kw1">AS</span> parent<span class="sy0">,</span>
    my_group<span class="sy0">.</span>id <span class="kw1">AS</span> id 
&nbsp;
  <span class="kw1">FROM</span> 
    my_group</pre></div></div>


<h3>VII. Test and stuff</h3>

<h4>Launch the Slap daemon</h4>

<p>It&#8217;s nice to launch <var>slapd</var> in debug mode in one terminal.  This is pretty verbose and will hopefully tell you if you&#8217;ve got something wrong in your mapping tables or anything:</p>


<div class="wp_syntax"><div class="code"><pre class="bash"><span class="kw2">sudo</span> <span class="sy0">/</span>usr<span class="sy0">/</span>sbin<span class="sy0">/</span>slapd <span class="re5">-d</span> <span class="nu0">5</span></pre></div></div>


<h4>Test connectivity and data with an LDAP client</h4>

<p>Hint:  it would help to <strong>LEARN HOW TO USE <a href="http://linux.die.net/man/1/ldapsearch">ldapsearch</a> correctly.</strong></p>

<p>Connect anonymously and spit out all the LDAP data (if ACL allows it&#8211; by slapd.conf above allows anonymous connections but won&#8217;t show any of the data):</p>


<div class="wp_syntax"><div class="code"><pre class="bash">ldapsearch <span class="re5">-x</span> <span class="re5">-s</span> sub <span class="re5">-b</span> <span class="st0">&quot;ou=Junk,ou=AK,DC=mycompany,DC=com&quot;</span> <span class="st0">&quot;(objectClass=*)&quot;</span></pre></div></div>


<p>Connect as root (the special account defined in slapd.conf) and show all the data in the directory:</p>


<div class="wp_syntax"><div class="code"><pre class="bash">ldapsearch <span class="re5">-x</span> <span class="re5">-D</span> <span class="re2">uid</span>=root,<span class="re2">ou</span>=USERS,<span class="re2">OU</span>=JUNK,<span class="re2">OU</span>=AK,<span class="re2">DC</span>=MYCOMPANY,<span class="re2">DC</span>=COM <span class="re5">-w</span> xxxxxxxxx <span class="re5">-s</span> sub <span class="re5">-b</span> <span class="st0">&quot;ou=Junk,ou=AK,DC=mycompany,DC=com&quot;</span> <span class="st0">&quot;(objectClass=*)&quot;</span></pre></div></div>


<p>Here&#8217;s an example of what it shows me:</p>


<div class="wp_syntax"><div class="code"><pre class="ini"># extended LDIF
#
# LDAPv3
# base &lt;ou<span class="sy0">=</span><span class="re2">Junk,ou=AK,DC=mycompany,DC=com&gt; with scope subtree</span>
# filter: <span class="br0">&#40;</span>objectClass<span class="sy0">=</span><span class="re2">*<span class="br0">&#41;</span></span>
# requesting: ALL
#
&nbsp;
# Junk, AK, mycompany.com
dn: ou<span class="sy0">=</span><span class="re2">Junk,ou=AK,dc=mycompany,dc=com</span>
objectClass: extensibleObject
description: builtin baseObject for back-sql
description: all entries mapped in the <span class="st0">&quot;ldap_entries&quot;</span> table
description: must have <span class="st0">&quot;0&quot;</span> in the <span class="st0">&quot;parent&quot;</span> column
ou: Junk
&nbsp;
# TESTUSER, USERS, JUNK, AK, MYCOMPANY.COM
dn: uid<span class="sy0">=</span><span class="re2">TESTUSER,ou=USERS,OU=JUNK,OU=AK,DC=MYCOMPANY,DC=COM</span>
objectClass: inetOrgPerson
cn: test user
ou: guest
sn: user
uid: testuser
givenName: test
userPassword:: dGVzdHBhc3M<span class="sy0">=</span>
&nbsp;
# <span class="nu0">1234567890</span>, USERS, JUNK, AK, MYCOMPANY.COM
dn: uid<span class="sy0">=</span><span class="re2">1234567890,ou=USERS,ou=JUNK,ou=AK,DC=MYCOMPANY,DC=COM</span>
objectClass: inetOrgPerson
cn: test2 user2
ou: guest
sn: user2
uid: <span class="nu0">1234567890</span>
givenName: test2
userPassword:: MTIzNDU2Nzg5MA<span class="sy0">=</span><span class="re2">=</span>
&nbsp;
...
&nbsp;
# VALIDUSERS, GROUPS, JUNK, AK, MYCOMPANY.COM
dn: cn<span class="sy0">=</span><span class="re2">VALIDUSERS,ou=GROUPS,ou=JUNK,ou=AK,DC=MYCOMPANY,DC=COM</span>
objectClass: groupOfUniqueNames
cn: validUsers
uniqueMember: uid<span class="sy0">=</span><span class="re2">1234567890,ou=USERS,OU=JUNK,OU=AK,DC=MYCOMPANY,DC=COM</span>
...
uniqueMember: uid<span class="sy0">=</span><span class="re2">TESTUSER,ou=USERS,OU=JUNK,OU=AK,DC=MYCOMPANY,DC=COM</span>
&nbsp;
# search result
search: <span class="nu0">2</span>
result: <span class="nu0">0</span> Success
&nbsp;
# numResponses: <span class="nu0">8</span>
# numEntries: <span class="nu0">7</span></pre></div></div>


<p>Connect as a valid user and ask for everything, but according to my ACLs the user can only see itself:
<pre>
ldapsearch -x -D uid=TESTUSER,ou=USERS,OU=JUNK,OU=AK,DC=MYCOMPANY,DC=COM -w testpass -s sub -b "ou=Junk,ou=AK,DC=mycompany,DC=com" "(objectClass=*)"
</pre></p>

<h4>Additional Considerations</h4>

<p><var>chkconfig</var> is the Red Hat tool for managing init scripts&#8211; here&#8217;s how to make sure the slapd starts at boot time.</p>


<div class="wp_syntax"><div class="code"><pre class="bash">$ <span class="kw2">sudo</span> chkconfig <span class="re5">--add</span> ldap
$ <span class="kw2">sudo</span> chkconfig ldap on</pre></div></div>


<p>Ah, Mr. Flat Mountain reminds,</p>

<blockquote>
9) Ports for the Firewall

LDAP runs on port 389/tcp by default and LDAP over SSL is 636/tcp.
</blockquote>

<p>Securing communication between the LDAP client and OpenLDAP server with SSL/TLS is beyond my scope here, but it seems to be much better documented.</p>

<p>Good luck!</p>
]]></content:encoded>
			<wfw:commentRss>http://n8v.enteuxis.org/2009/02/ldap-powered-by-mysql-on-rhel5/feed/</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
		<item>
		<title>Fix for slashes hosing Symfony URLs</title>
		<link>http://n8v.enteuxis.org/2009/02/fix-for-slashes-hosing-symfony-urls/</link>
		<comments>http://n8v.enteuxis.org/2009/02/fix-for-slashes-hosing-symfony-urls/#comments</comments>
		<pubDate>Tue, 03 Feb 2009 22:54:31 +0000</pubDate>
		<dc:creator>nathan</dc:creator>
				<category><![CDATA[Figuring IT Out]]></category>
		<category><![CDATA[apache]]></category>
		<category><![CDATA[symfony]]></category>

		<guid isPermaLink="false">http://n8v.enteuxis.org/?p=117</guid>
		<description><![CDATA[I found this information in this forum topic, &#8220;Slash in parameter&#8221; which references this ticket, but had snags and wanted to write it all up cleanly here. The Problem Sometimes a parameter in a GET string contains slashes, which are interpreted as file path delimeters by Apache and/or parameter name/value separators by Symfony. So this [...]]]></description>
			<content:encoded><![CDATA[<p>I found this information in <a href="http://forum.symfony-project.org/index.php/m/41010/">this forum topic, &#8220;Slash in parameter&#8221;</a> which references <a href="http://trac.symfony-project.org/ticket/1482">this ticket</a>, but had snags and wanted to write it all up cleanly here.</p>

<h3>The Problem</h3>

<p>Sometimes a parameter in a GET string contains slashes, which are interpreted as file path delimeters by Apache and/or parameter name/value separators by Symfony.</p>

<p>So this URL works:
<samp>http://myapp/employee/bytitle/title/System+Engineer</samp></p>

<p>But this one returns a 404 (Page Not Found) error:
<samp>http://myapp/employee/bytitle/title/Programmer/analyst</samp></p>

<h4>Wrong solution</h4>

<p>Double-escaping the slash into &#8216;%252F&#8217; (e.g. <code>urlencode(str_replace('/', "%2f", $this-&gt;getTitle()))</code>) works <strong>if the URL isn&#8217;t rewritten by Apache</strong>.</p>

<p>This threw me because my URLs would work if they included the name of the controller file, <var>frontend_dev.php</var> or <var>backend.php</var> or even <var>index.php</var> like
<samp>http://myapp/<strong>index.php</strong>/employee/bytitle/title/Programmer%252Fanalyst</samp>,</p>

<p>but they wouldn&#8217;t work through the implicit, controllerless URLs  like
<samp>http://myapp/employee/bytitle/title/Programmer%252Fanalyst</samp></p>

<h3>Right solution</h3>

<p>Both</p>

<ol>
    <li>Turn on the <a href="http://httpd.apache.org/docs/2.2/en/mod/core.html#allowencodedslashes"><var>AllowEncodedSlashes</var> Apache directive</a> by adding this line to your server-wide section or the appropriate VirtualHost:
<pre> AllowEncodedSlashes On</pre>
<strong>AND</strong>,</li>
    <li><strong>DON&#8217;T</strong> double-escape the parameter as above, but do use <var>urlencode()</var> or equivalent to change slashes to <code>%2F</code>, eg. in the view layer,


<div class="wp_syntax"><div class="code"><pre class="php"><span class="kw2">&lt;?php</span> <span class="kw1">echo</span> link_to<span class="br0">&#40;</span>
    <span class="re0">$employee</span><span class="sy0">-&gt;</span><span class="me1">getTitle</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">,</span>
    <span class="st_h">'/employee/bytitle?title='</span><span class="sy0">.</span>
    <a href="http://www.php.net/urlencode"><span class="kw3">urlencode</span></a><span class="br0">&#40;</span><span class="re0">$employee</span><span class="sy0">-&gt;</span><span class="me1">getTitle</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="br0">&#41;</span>
<span class="br0">&#41;</span><span class="sy0">;</span>  <span class="sy1">?&gt;</span></pre></div></div>



</li>
</ol>

<h4>Additional Complication</h4>

<p>Browsers may treat the encoded slash in the URL differently.</p>

<p>IE 7, 8 and Firefox 3, and Safari 3.2.1 on Windows display the URL in the address bar with the %2F intact, as you&#8217;d expect.</p>

<p>In Google Chrome 1.0.154.43, the links work but the address bar displays a slash instead of the encoded %2F, so if you hit the &#8220;Go&#8221; button you get a 404 error.  Weird.  Bug submitted!</p>
]]></content:encoded>
			<wfw:commentRss>http://n8v.enteuxis.org/2009/02/fix-for-slashes-hosing-symfony-urls/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
	</channel>
</rss>

