<?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>woblog &#187; Rich</title>
	<atom:link href="http://www.whiteoctober.co.uk/blog/author/rsage/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.whiteoctober.co.uk/blog</link>
	<description>great stuff about the web</description>
	<lastBuildDate>Mon, 05 Dec 2011 16:53:15 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.2.1</generator>
		<item>
		<title>Symfony2 data collectors</title>
		<link>http://www.whiteoctober.co.uk/blog/2011/12/05/symfony2-data-collectors/</link>
		<comments>http://www.whiteoctober.co.uk/blog/2011/12/05/symfony2-data-collectors/#comments</comments>
		<pubDate>Mon, 05 Dec 2011 16:35:48 +0000</pubDate>
		<dc:creator>Rich</dc:creator>
				<category><![CDATA[Test Driven Development]]></category>
		<category><![CDATA[Web development]]></category>
		<category><![CDATA[Web technologies]]></category>

		<guid isPermaLink="false">http://www.whiteoctober.co.uk/blog/?p=792</guid>
		<description><![CDATA[I spent some time last week working on automated MailChimp integration with one of our Symfony2 sites. The project uses Behat and Mink extensively for BDD testing, so I wanted to be able to test that when users registered on our site, they were automatically added to the client&#8217;s MailChimp distribution list. Unsubscription could happen [...]]]></description>
			<content:encoded><![CDATA[<p>I spent some time last week working on automated <a href="http://www.mailchimp.com/" title="MailChimp">MailChimp</a> integration with one of our <a href="http://symfony.com" title="Symfony">Symfony2</a> sites.  The project uses <a href="http://behat.org/">Behat</a> and <a href="http://mink.behat.org/" title="Mink">Mink</a> extensively for BDD testing, so I wanted to be able to test that when users registered on our site, they were automatically added to the client&#8217;s MailChimp distribution list.  Unsubscription could happen via a checkbox in the user&#8217;s account settings and this needed to work the same way behind the scenes.</p>
<p>I opted to use one of the <a href="https://github.com/jirafe/MailChimpBundle">available MailChimp bundles</a> to aid integration and with a bit of tweaking this worked great.  The bundle comes with a stub connection which is able to collate requests sent and make them available for inspection.  I wanted to be able to utilise this neatly inside a set of tests, and potentially also make it available in the dev environment.  A bit of skimming the Symfony2 cookbook led me to the entry on <a href="http://symfony.com/doc/current/cookbook/profiler/data_collector.html">Symfony2 data collectors</a>.</p>
<p>The concept is simple &#8211; a data collector is registered and is able to, well, collect data!  For example, the number of queries during a request, or the number of emails sent.  This can then be accessed via the profiler in the relevant environment (dev, test etc).  In my case I wanted to collect and subsequently inspect all requests sent to MailChimp.</p>
<p>First step &#8211; create me a data collector:</p>

<div class="wp_syntax"><div class="code"><pre class="php" style="font-family:monospace;"><span style="color: #000000; font-weight: bold;">&lt;?php</span>
&nbsp;
<span style="color: #000000; font-weight: bold;">namespace</span> Jirafe\Bundle\MailChimpBundle\DataCollector<span style="color: #339933;">;</span>
&nbsp;
use Symfony\Component\HttpKernel\DataCollector\DataCollector<span style="color: #339933;">;</span>
use Symfony\Component\HttpFoundation\Request<span style="color: #339933;">;</span>
use Symfony\Component\HttpFoundation\Response<span style="color: #339933;">;</span>
use Jirafe\Bundle\MailChimpBundle\Connection\ConnectionInterface<span style="color: #339933;">;</span>
&nbsp;
<span style="color: #000000; font-weight: bold;">class</span> MailChimpDataCollector <span style="color: #000000; font-weight: bold;">extends</span> DataCollector
<span style="color: #009900;">&#123;</span>
    <span style="color: #000000; font-weight: bold;">private</span> <span style="color: #000088;">$connection</span><span style="color: #339933;">;</span>
&nbsp;
    <span style="color: #000000; font-weight: bold;">public</span> <span style="color: #000000; font-weight: bold;">function</span> __construct<span style="color: #009900;">&#40;</span>ConnectionInterface <span style="color: #000088;">$connection</span><span style="color: #009900;">&#41;</span>
    <span style="color: #009900;">&#123;</span>
        <span style="color: #000088;">$this</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">connection</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$connection</span><span style="color: #339933;">;</span>
    <span style="color: #009900;">&#125;</span>
&nbsp;
    <span style="color: #000000; font-weight: bold;">public</span> <span style="color: #000000; font-weight: bold;">function</span> collect<span style="color: #009900;">&#40;</span>Request <span style="color: #000088;">$request</span><span style="color: #339933;">,</span> Response <span style="color: #000088;">$response</span><span style="color: #339933;">,</span> \Exception <span style="color: #000088;">$exception</span> <span style="color: #339933;">=</span> <span style="color: #009900; font-weight: bold;">null</span><span style="color: #009900;">&#41;</span>
    <span style="color: #009900;">&#123;</span>
        <span style="color: #000088;">$this</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">data</span> <span style="color: #339933;">=</span> <span style="color: #990000;">array</span><span style="color: #009900;">&#40;</span>
            <span style="color: #0000ff;">'requests'</span>  <span style="color: #339933;">=&gt;</span> <span style="color: #000088;">$this</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">connection</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">getRequests</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">,</span>
        <span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
    <span style="color: #009900;">&#125;</span>
&nbsp;
    <span style="color: #000000; font-weight: bold;">public</span> <span style="color: #000000; font-weight: bold;">function</span> getRequestCount<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span>
    <span style="color: #009900;">&#123;</span>
        <span style="color: #b1b100;">return</span> <span style="color: #990000;">count</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$this</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">data</span><span style="color: #009900;">&#91;</span><span style="color: #0000ff;">'requests'</span><span style="color: #009900;">&#93;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
    <span style="color: #009900;">&#125;</span>
&nbsp;
    <span style="color: #000000; font-weight: bold;">public</span> <span style="color: #000000; font-weight: bold;">function</span> getRequests<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span>
    <span style="color: #009900;">&#123;</span>
        <span style="color: #b1b100;">return</span> <span style="color: #000088;">$this</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">data</span><span style="color: #009900;">&#91;</span><span style="color: #0000ff;">'requests'</span><span style="color: #009900;">&#93;</span><span style="color: #339933;">;</span>
    <span style="color: #009900;">&#125;</span>
&nbsp;
    <span style="color: #000000; font-weight: bold;">public</span> <span style="color: #000000; font-weight: bold;">function</span> getName<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span>
    <span style="color: #009900;">&#123;</span>
        <span style="color: #b1b100;">return</span> <span style="color: #0000ff;">'mail_chimp'</span><span style="color: #339933;">;</span>
    <span style="color: #009900;">&#125;</span>
<span style="color: #009900;">&#125;</span></pre></div></div>

<p>The <code>collect()</code> method is the interesting part here &#8211; this is called whenever data collection is required &#8211; usually once per request.  We collect the requests from the connection supplied to the constructor.</p>
<p>Next step, register this during the bundle&#8217;s <code>load()</code> method:</p>

<div class="wp_syntax"><div class="code"><pre class="php" style="font-family:monospace;"><span style="color: #666666; font-style: italic;">// Add connection to data collector</span>
<span style="color: #000088;">$definition</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$container</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">getDefinition</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'mail_chimp.data_collector'</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
<span style="color: #000088;">$definition</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">addArgument</span><span style="color: #009900;">&#40;</span><span style="color: #000000; font-weight: bold;">new</span> Reference<span style="color: #009900;">&#40;</span><span style="color: #990000;">sprintf</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'mail_chimp.connection.%s'</span><span style="color: #339933;">,</span> <span style="color: #000088;">$config</span><span style="color: #009900;">&#91;</span><span style="color: #0000ff;">'connection'</span><span style="color: #009900;">&#93;</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span></pre></div></div>

<p>The above uses the connection type specified in the bundle&#8217;s configuration option and previously registered as a service in the same method.  If you&#8217;re creating your own custom collector, this is just the normal parameters you&#8217;d pass in to your service when it&#8217;s instantiated.</p>
<p>Finally, I added a method and subsequent implementation to the interface and related connection classes within the bundle, so that MailChimp request objects were collected in an array, and made available to the data collector upon request. This is the <code>getRequests()</code> method listed above in the data collector; called in the <code>collect()</code> method. Quick &#8216;n&#8217; dirty here, since there would only ever be a single MailChimp request (possibly 2) made in any one request.</p>
<p>Once all this was in place, I could then test correctly.  Note that I&#8217;d rather have done the other way round (test and then develop) but since this was experimentation, I figured it was OK ;-) In my Behat scenarios, I added the <code>getSymfonyProfiler()</code> method detailed in a Behat/Mink <a href="http://docs.behat.org/cookbook/using_the_profiler_with_minkbundle.html">cookbook entry</a>, and proceeded to write my step (Behat 1.x-style):</p>

<div class="wp_syntax"><div class="code"><pre class="php" style="font-family:monospace;"><span style="color: #000088;">$steps</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">Then</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'/^my email address is unsubscribed from the relevant MailChimp list$/'</span><span style="color: #339933;">,</span> <span style="color: #000000; font-weight: bold;">function</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$world</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
&nbsp;
    <span style="color: #000088;">$profiler</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$world</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">getSymfonyProfiler</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
    <span style="color: #b1b100;">if</span> <span style="color: #009900;">&#40;</span><span style="color: #339933;">!</span><span style="color: #000088;">$profiler</span><span style="color: #009900;">&#41;</span>
    <span style="color: #009900;">&#123;</span>
      throw <span style="color: #000000; font-weight: bold;">new</span> \RuntimeException<span style="color: #009900;">&#40;</span><span style="color: #0000ff;">&quot;Symfony2 profiler not present for tests, wrong driver?&quot;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
    <span style="color: #009900;">&#125;</span>
    <span style="color: #000088;">$mcDataCollector</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$profiler</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">getCollector</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">&quot;mail_chimp&quot;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
    <span style="color: #000088;">$listID</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$world</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">container</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">getParameter</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">&quot;mailchimp_list_id&quot;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
    <span style="color: #666666; font-style: italic;">// Check the requests made to make sure we have an unsubscribe</span>
    assertEquals<span style="color: #009900;">&#40;</span><span style="color: #cc66cc;">1</span><span style="color: #339933;">,</span> <span style="color: #000088;">$mcDataCollector</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">getRequestCount</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
    <span style="color: #000088;">$requests</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$mcDataCollector</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">getRequests</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
    <span style="color: #000088;">$req1</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$requests</span><span style="color: #009900;">&#91;</span><span style="color: #cc66cc;">0</span><span style="color: #009900;">&#93;</span><span style="color: #339933;">;</span>
    assertEquals<span style="color: #009900;">&#40;</span><span style="color: #0000ff;">&quot;listUnsubscribe&quot;</span><span style="color: #339933;">,</span> <span style="color: #000088;">$req1</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">getMethod</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
    <span style="color: #000088;">$params</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$req1</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">getParams</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
    assertEquals<span style="color: #009900;">&#40;</span><span style="color: #000088;">$listID</span><span style="color: #339933;">,</span> <span style="color: #000088;">$params</span><span style="color: #009900;">&#91;</span><span style="color: #0000ff;">&quot;id&quot;</span><span style="color: #009900;">&#93;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
    <span style="color: #666666; font-style: italic;">// Fire the redirect on the client</span>
    <span style="color: #000088;">$world</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">getSession</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">getDriver</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">getClient</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">followRedirect</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
<span style="color: #009900;">&#125;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span></pre></div></div>

<p>The final line here is as a result of disabling redirects in a previous step since the data collector operates on a per-request basis.  If you&#8217;re subscribing eg via a form POST, then you&#8217;ll need to disable redirects prior to submitting the form, otherwise your data collector will be empty when you check it.  Disable them and then redirect after you&#8217;ve carried out your checks.</p>
<p>The implementation for the above is up on our Github MailChimpBundle fork <a href="https://github.com/whiteoctober/MailChimpBundle">here</a>; I&#8217;m not particularly fond of having to hack about the connection classes, and ideally it would be nice to layer the data collector on top without having to adjust existing code. That said, it does the job fine here, and it&#8217;s good to use even more features of Symfony2 ;-)</p>
]]></content:encoded>
			<wfw:commentRss>http://www.whiteoctober.co.uk/blog/2011/12/05/symfony2-data-collectors/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>MailChimp user interface testing</title>
		<link>http://www.whiteoctober.co.uk/blog/2011/09/14/mailchimp-user-interface-testing/</link>
		<comments>http://www.whiteoctober.co.uk/blog/2011/09/14/mailchimp-user-interface-testing/#comments</comments>
		<pubDate>Wed, 14 Sep 2011 09:13:49 +0000</pubDate>
		<dc:creator>Rich</dc:creator>
				<category><![CDATA[User experience]]></category>
		<category><![CDATA[Web technologies]]></category>

		<guid isPermaLink="false">http://www.whiteoctober.co.uk/blog/?p=768</guid>
		<description><![CDATA[At White October we&#8217;re pretty fond of using MailChimp and recommending it where suitable to clients &#8211; it&#8217;s a user-friendly product that does one thing and does it really well. It&#8217;s also dead easy to integrate and use &#8211; whether it&#8217;s the custom forms, or an integration direct into your own site (eg via a [...]]]></description>
			<content:encoded><![CDATA[<p>At White October we&#8217;re pretty fond of using <a href="http://mailchimp.com">MailChimp</a> and recommending it where suitable to clients &#8211; it&#8217;s a user-friendly product that does one thing and does it really well.  It&#8217;s also dead easy to integrate and use &#8211; whether it&#8217;s the custom forms, or an integration direct into your own site (eg via a footer &#8220;Sign up!&#8221; form). Plus, I snagged a free t-shirt last year in one of their <a href="http://twitter.com/MailChimp">Twitter</a>-based giveaways, so I&#8217;m more than happy to recommend them ;-)</p>
<p>Yesterday I was privileged to be involved in a user testing and feedback session that they were running down in Brighton.  <a href="http://aarronwalter.com/">Aarron Walter</a> and Stephanie Troeth from MailChimp came down, and ran an hour&#8217;s session with me where we went through various aspects of the MailChimp interface, features that were available (including ones I wasn&#8217;t aware of), and various usability points were addressed.  I felt it was incredibly beneficial to both sides &#8211; MailChimp, so that they can understand what works and what doesn&#8217;t for users, and from my perspective seeing different features and having the opportunity to raise problems I had with usability.  Too many times I&#8217;ve used a product which feels unfriendly to use, that feels like it was designed for hardcore non-typical users of the product, or sometimes just &#8220;designed by developers&#8221;. And we know how that turns out&#8230; So it was great to be involved in contributing to any potential user interface changes; it definitely makes you feel more valued as a customer.</p>
<p>Aside from the obvious part of testing a user interface, it was good during the session to learn about different features that people maybe aren&#8217;t aware of &#8211; there&#8217;s a large number of different integrations available in the admin area &#8211; everything from <a href="http://www.google.co.uk/analytics">Google Analytics</a> (tracks MailChimp campaigns being sent out against page views, e-commerce goals etc, all inside your admin area) to <a href="http://www.freshbooks.com/">Freshbooks</a> (import contacts etc). I also discovered the <a href="http://connect.mailchimp.com/">MailChimp integrations directory</a> &#8211; very cool.  One very good point Aarron also raised was how I found out about new features &#8211; for example, just clicking around in the admin area through to email and Twitter updates from MailChimp etc.  There&#8217;s no point having new features available if you don&#8217;t shout about them&#8230;!</p>
<p>Overall &#8211; more than happy to carry on recommending them where suitable &#8211; a company that listens to its users AND provides a great service too.  And maybe the most interesting fact of the session? Learning <a href="http://mailchimp.com/about/">Freddie</a> the chimp&#8217;s full name&#8230;. not sure how common knowledge that is, so maybe I&#8217;ll keep that a secret for now ;-)</p>
]]></content:encoded>
			<wfw:commentRss>http://www.whiteoctober.co.uk/blog/2011/09/14/mailchimp-user-interface-testing/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Getting into Behat with Symfony2</title>
		<link>http://www.whiteoctober.co.uk/blog/2011/06/15/getting-into-behat-with-symfony2/</link>
		<comments>http://www.whiteoctober.co.uk/blog/2011/06/15/getting-into-behat-with-symfony2/#comments</comments>
		<pubDate>Wed, 15 Jun 2011 07:47:21 +0000</pubDate>
		<dc:creator>Rich</dc:creator>
				<category><![CDATA[Test Driven Development]]></category>
		<category><![CDATA[Uncategorised]]></category>
		<category><![CDATA[Web development]]></category>

		<guid isPermaLink="false">http://www.whiteoctober.co.uk/blog/?p=740</guid>
		<description><![CDATA[I&#8217;ve spent the past week starting a new sprint on our latest large project, and as we&#8217;re keen to get our clients as involved as possible during sprints, we decided to get down with some Behaviour-Driven Development (BDD) which allows them to contribute to writing the actual acceptance criteria for the user stories. We&#8217;re pretty [...]]]></description>
			<content:encoded><![CDATA[<p>I&#8217;ve spent the past week starting a new sprint on our latest large project, and as we&#8217;re keen to get our clients as involved as possible during sprints, we decided to get down with some <a href="http://en.wikipedia.org/wiki/Behavior_Driven_Development">Behaviour-Driven Development</a> (BDD) which allows them to contribute to writing the actual acceptance criteria for the user stories.  We&#8217;re pretty big on <a href="http://en.wikipedia.org/wiki/Test-driven_development">Test-Driven Developmen</a>t here at WO Towers but sometimes it&#8217;s necessary to strike a balance between testing every minute detail and no testing.  BDD seems to meet that balance whilst providing good code coverage with tests.  Note: here I speak of functional tests rather than unit tests.</p>
<p>The beauty of BDD is that it&#8217;s written in plain everyday language, and this can be directly translated to code snippets.  Therefore the client is happy working &#8220;in plain english&#8221;, and the developer can crack on with writing the actual code to test these parts.</p>
<p>We&#8217;ve chosen to use <a href="http://symfony.com/">Symfony2</a> for the project (more on that in a separate post) and for the BDD aspect we&#8217;ve gone with <a href="http://behat.org/">Behat</a> (coupled with <a href="https://github.com/Behat/Mink">Mink</a>); both of which have bundles available for Symfony2. Essentially, BDD boils down to <strong>Features</strong>, consisting of a number of <strong>Scenarios</strong>, which in turn consist of a number of <strong>Steps</strong>.  Here&#8217;s an example:</p>
<pre>Feature: Login to the admin area
	As an administrator
	I want to be able to log into an admin area
	So I can administer MyApp

	Scenario: Joe Bloggs logs into the admin area
	  Given I visit the admin area
	  When I am shown the login form
	  And I click login with my correct credentials
	  Then I should be on /admin/home
</pre>
<p>This could translate to code such as the following:</p>

<div class="wp_syntax"><div class="code"><pre class="php" style="font-family:monospace;"><span style="color: #000088;">$steps</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">Given</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'/^I visit the admin area$/'</span><span style="color: #339933;">,</span> <span style="color: #000000; font-weight: bold;">function</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$world</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
&nbsp;
    <span style="color: #000088;">$world</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">getSession</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">visit</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$world</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">getPathTo</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">&quot;/admin&quot;</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
<span style="color: #009900;">&#125;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
<span style="color: #000088;">$steps</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">When</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'/^I am shown the login form$/'</span><span style="color: #339933;">,</span> <span style="color: #000000; font-weight: bold;">function</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$world</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
&nbsp;
    <span style="color: #000088;">$doc</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$world</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">getSession</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">getPage</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
    assertTrue<span style="color: #009900;">&#40;</span><span style="color: #000088;">$doc</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">hasField</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">&quot;_username&quot;</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
    assertTrue<span style="color: #009900;">&#40;</span><span style="color: #000088;">$doc</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">hasField</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">&quot;_password&quot;</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
<span style="color: #009900;">&#125;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
<span style="color: #000088;">$steps</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">And</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'/^I click login with my correct credentials$/'</span><span style="color: #339933;">,</span> <span style="color: #000000; font-weight: bold;">function</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$world</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
&nbsp;
    <span style="color: #000088;">$doc</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$world</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">getSession</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">getPage</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
    <span style="color: #000088;">$doc</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">fillField</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">&quot;_username&quot;</span><span style="color: #339933;">,</span> <span style="color: #0000ff;">&quot;joe&quot;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
    <span style="color: #000088;">$doc</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">fillField</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">&quot;_password&quot;</span><span style="color: #339933;">,</span> <span style="color: #0000ff;">&quot;bloggs&quot;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
    <span style="color: #000088;">$doc</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">clickButton</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">&quot;Log in&quot;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
<span style="color: #009900;">&#125;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
<span style="color: #000088;">$steps</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">Then</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'/^(?:|I )should be on (?P&lt;page&gt;.+)$/'</span><span style="color: #339933;">,</span> <span style="color: #000000; font-weight: bold;">function</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$world</span><span style="color: #339933;">,</span> <span style="color: #000088;">$page</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
&nbsp;
    assertEquals<span style="color: #009900;">&#40;</span>
        <span style="color: #990000;">parse_url</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$world</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">getPathTo</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$page</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">,</span> PHP_URL_PATH<span style="color: #009900;">&#41;</span><span style="color: #339933;">,</span>
        <span style="color: #990000;">parse_url</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$world</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">getSession</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">getCurrentUrl</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">,</span> PHP_URL_PATH<span style="color: #009900;">&#41;</span>
    <span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
<span style="color: #009900;">&#125;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span></pre></div></div>

<p>The beauty of Behat is that the step definitions are reusable, meaning that if you stick to a standard way of describing your steps, the amount of code you need to write is vastly reduced, compared with normal functional TDD.  Steps that aren&#8217;t implemented yet are given default code as per:</p>

<div class="wp_syntax"><div class="code"><pre class="php" style="font-family:monospace;"><span style="color: #000088;">$steps</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">Given</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'/^I visit the admin area$/'</span><span style="color: #339933;">,</span> <span style="color: #000000; font-weight: bold;">function</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$world</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
    throw <span style="color: #000000; font-weight: bold;">new</span> \Behat\Behat\Exception\PendingException<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
<span style="color: #009900;">&#125;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span></pre></div></div>

<p>which will translate into the output shown as a step needing to be implemented.  Behat also takes note of the Pending exception being thrown and automatically skips any steps following this in the scenario, notifying you of this at the end of the test run.</p>
<p>Behat also comes with a number of standard step definitions built-in, although these are somewhat buried in the code and not in the docs :-(  So for example the following steps are part of the included ones:</p>

<div class="wp_syntax"><div class="code"><pre class="php" style="font-family:monospace;">I go to <span style="color: #339933;">/</span>my<span style="color: #339933;">/</span>url<span style="color: #339933;">:</span>
&nbsp;
<span style="color: #000088;">$steps</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">When</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'/^(?:|I )go to (?P&lt;page&gt;.+)$/'</span><span style="color: #339933;">,</span> <span style="color: #000000; font-weight: bold;">function</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$world</span><span style="color: #339933;">,</span> <span style="color: #000088;">$page</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
    <span style="color: #000088;">$world</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">getSession</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">visit</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$world</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">getPathTo</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$page</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
<span style="color: #009900;">&#125;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span></pre></div></div>

<p>and</p>

<div class="wp_syntax"><div class="code"><pre class="php" style="font-family:monospace;">I fill in <span style="color: #0000ff;">&quot;username&quot;</span> with <span style="color: #0000ff;">&quot;foo&quot;</span><span style="color: #339933;">:</span>
&nbsp;
<span style="color: #000088;">$steps</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">When</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'/^(?:|I )fill in &quot;(?P&lt;field&gt;[^&quot;]*)&quot; with &quot;(?P&lt;value&gt;[^&quot;]*)&quot;$/'</span><span style="color: #339933;">,</span> <span style="color: #000000; font-weight: bold;">function</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$world</span><span style="color: #339933;">,</span> <span style="color: #000088;">$field</span><span style="color: #339933;">,</span> <span style="color: #000088;">$value</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
    <span style="color: #000088;">$world</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">getSession</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">getPage</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">fillField</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$field</span><span style="color: #339933;">,</span> <span style="color: #000088;">$value</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
<span style="color: #009900;">&#125;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span></pre></div></div>

<p>which translates as even less code to write. Bonus!</p>
<h2>Tips &#8216;n&#8217; tricks</h2>
<p>The Mink (browser emulator abstraction layer) setup comes with a Symfony2 driver which is great for just testing normal usage.  In our project however we&#8217;re integrating Facebook authentication using the FOS <a href="https://github.com/FriendsOfSymfony/FacebookBundle">FacebookBundle</a>, and we needed a way to test that the user was being bounced to Facebook in order to log in.  The solution for this was simple &#8211; switch to using <a href="https://github.com/fabpot/Goutte">Goutte</a> as the driver, as per the BehatBundle docs, and voila, you can test the current URL contains eg facebook.com or whatever.  The actual testing of the authentication itself I&#8217;ll write up in a separate blog post as it&#8217;s a bit epic for here.</p>
<p>The documentation mentions using a separate front controller for testing, with a separate environment configuration.  This is definitely recommended, particularly if you&#8217;re using Goutte.  If you need services to be available, for example your entity manager, pop something like this in your env.php file:</p>

<div class="wp_syntax"><div class="code"><pre class="php" style="font-family:monospace;"><span style="color: #000088;">$kernel</span> <span style="color: #339933;">=</span> <span style="color: #000000; font-weight: bold;">new</span> AppKernel<span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'test'</span><span style="color: #339933;">,</span> <span style="color: #009900; font-weight: bold;">true</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
<span style="color: #000088;">$kernel</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">loadClassCache</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
<span style="color: #000088;">$service</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$kernel</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">getContainer</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">get</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">&quot;your.service.here&quot;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span></pre></div></div>

<p>You can also use the <code>env.php</code> file for setting up fixtures that need to be reset for each Scenario that you create, eg pre-defined user states and so on.</p>
<h2>Caveats</h2>
<p>Unless you&#8217;re using <a href="https://github.com/Behat/SahiClient">Sahi</a>, you obviously can&#8217;t test things such as Javascript popups effectively.  I&#8217;m in two minds as to whether this should be handled by a new type of Exception which can be thrown e.g. a <code>TestInRealBrowserException()</code>.  That way it could be caught and notified to the user running the tests, or flagged as part of an automated build process for manual testing.</p>
<p>Overall, getting into BDD has been a fun and rewarding experience.  At times the lack of docs make it somewhat frustrating, but this is easily solved by a) digging into the code and b) probably me getting off my ass and submitting some docs back.  The ease of which the steps can be written though is a massive time saver when writing your tests, coupled with the reusability of the code.  Big thumbs-up from me!</p>
]]></content:encoded>
			<wfw:commentRss>http://www.whiteoctober.co.uk/blog/2011/06/15/getting-into-behat-with-symfony2/feed/</wfw:commentRss>
		<slash:comments>8</slash:comments>
		</item>
		<item>
		<title>Stored functions with Symfony and Doctrine</title>
		<link>http://www.whiteoctober.co.uk/blog/2011/01/05/stored-functions-with-symfony-and-doctrine/</link>
		<comments>http://www.whiteoctober.co.uk/blog/2011/01/05/stored-functions-with-symfony-and-doctrine/#comments</comments>
		<pubDate>Wed, 05 Jan 2011 09:17:12 +0000</pubDate>
		<dc:creator>Rich</dc:creator>
				<category><![CDATA[Web development]]></category>
		<category><![CDATA[doctrine]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[Symfony]]></category>

		<guid isPermaLink="false">http://www.whiteoctober.co.uk/blog/?p=665</guid>
		<description><![CDATA[A Symfony project I’m working on currently requires a couple of MySQL stored functions to be available. In the interests of making the code I write reusable in future, some of it has been moved into a plugin, but this code relies on a couple of stored functions being available.]]></description>
			<content:encoded><![CDATA[<p>A <a href="http://www.symfony-project.org/">Symfony</a> project I&#8217;m working on currently requires a couple of <a href="http://www.mysql.com/">MySQL</a> stored functions to be available.  In the interests of making the code I write reusable in future, some of it has been moved into a plugin, but this code relies on a couple of stored functions being available.  As this code is also unit tested, I needed to make sure the stored functions were available on database drop-and-load which happens in the unit test bootstrap (via the <a href="http://www.doctrine-project.org/">Doctrine</a> tasks provided).</p>
<p>First of all, my stored functions are in separate .sql files in my plugin&#8217;s <code>data/sql/</code> directory in the interests of following convention within the project&#8217;s directory structure.</p>
<p>Secondly, in myPlugin/config/MyPluginConfiguration.class.php file, I have the following for the <code>initialize()</code> method.:</p>

<div class="wp_syntax"><div class="code"><pre class="php" style="font-family:monospace;"><span style="color: #000000; font-weight: bold;">public</span> <span style="color: #000000; font-weight: bold;">function</span> initialize<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span>
<span style="color: #009900;">&#123;</span>
  sfConfig<span style="color: #339933;">::</span><span style="color: #004000;">set</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">&quot;zz_myplugin_dir&quot;</span><span style="color: #339933;">,</span> <span style="color: #000088;">$this</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">getRootDir</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
  <span style="color: #000088;">$this</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">dispatcher</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">connect</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">&quot;command.post_command&quot;</span><span style="color: #339933;">,</span> <span style="color: #990000;">array</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$this</span><span style="color: #339933;">,</span> <span style="color: #0000ff;">'initializeStoredFuncs'</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
<span style="color: #009900;">&#125;</span></pre></div></div>

<p>We listen for the <code>command.post_command</code> event which we can then check to see if it&#8217;s a Doctrine insert-sql task being run.  We also check to see if the function already exists before attempting to create it &#8211; if not, then carry on.</p>

<div class="wp_syntax"><div class="code"><pre class="php" style="font-family:monospace;"><span style="color: #000000; font-weight: bold;">public</span> static <span style="color: #000000; font-weight: bold;">function</span> initializeStoredFuncs<span style="color: #009900;">&#40;</span>sfEvent <span style="color: #000088;">$event</span><span style="color: #009900;">&#41;</span>
<span style="color: #009900;">&#123;</span>
  static <span style="color: #000088;">$doneConfig</span> <span style="color: #339933;">=</span> <span style="color: #009900; font-weight: bold;">false</span><span style="color: #339933;">;</span>
&nbsp;
  <span style="color: #b1b100;">if</span> <span style="color: #009900;">&#40;</span><span style="color: #000088;">$doneConfig</span><span style="color: #009900;">&#41;</span>
  <span style="color: #009900;">&#123;</span>
    <span style="color: #666666; font-style: italic;">// Already done it.</span>
    <span style="color: #b1b100;">return</span><span style="color: #339933;">;</span>
  <span style="color: #009900;">&#125;</span>
&nbsp;
  <span style="color: #b1b100;">if</span> <span style="color: #009900;">&#40;</span><span style="color: #339933;">!</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$event</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">getSubject</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span> instanceof sfDoctrineInsertSqlTask<span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span>
  <span style="color: #009900;">&#123;</span>
    <span style="color: #666666; font-style: italic;">// Not inserting any SQL. No point continuing</span>
    <span style="color: #b1b100;">return</span><span style="color: #339933;">;</span>
  <span style="color: #009900;">&#125;</span>
&nbsp;
  <span style="color: #666666; font-style: italic;">// Get database connection</span>
  <span style="color: #000088;">$con</span> <span style="color: #339933;">=</span> Doctrine_Manager<span style="color: #339933;">::</span><span style="color: #004000;">connection</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
  <span style="color: #666666; font-style: italic;">// Configure function 1</span>
  <span style="color: #000000; font-weight: bold;">self</span><span style="color: #339933;">::</span><span style="color: #004000;">addStoredRoutine</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$con</span><span style="color: #339933;">,</span> <span style="color: #0000ff;">&quot;myFuncName1&quot;</span><span style="color: #339933;">,</span> <span style="color: #0000ff;">&quot;myFuncName1.sql&quot;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
  <span style="color: #666666; font-style: italic;">// Configure function 2</span>
  <span style="color: #000000; font-weight: bold;">self</span><span style="color: #339933;">::</span><span style="color: #004000;">addStoredRoutine</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$con</span><span style="color: #339933;">,</span> <span style="color: #0000ff;">&quot;someOtherFunc&quot;</span><span style="color: #339933;">,</span> <span style="color: #0000ff;">&quot;someOtherFunc.sql&quot;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
  <span style="color: #666666; font-style: italic;">// Set config completed flag for later</span>
  <span style="color: #000088;">$doneConfig</span> <span style="color: #339933;">=</span> <span style="color: #009900; font-weight: bold;">true</span><span style="color: #339933;">;</span>
<span style="color: #009900;">&#125;</span>
&nbsp;
<span style="color: #009933; font-style: italic;">/**
* Adds a stored procedure into the database
*
* @param string $con Connection to use
* @param string $routineName Routine name
* @param string $routineFile Filename inside our [plugin dir]/data/sql/
*/</span>
protected static <span style="color: #000000; font-weight: bold;">function</span> addStoredRoutine<span style="color: #009900;">&#40;</span><span style="color: #000088;">$con</span><span style="color: #339933;">,</span> <span style="color: #000088;">$routineName</span><span style="color: #339933;">,</span> <span style="color: #000088;">$routineFile</span><span style="color: #009900;">&#41;</span>
<span style="color: #009900;">&#123;</span>
  <span style="color: #000088;">$options</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$con</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">getOptions</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
  <span style="color: #000088;">$dsn</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$options</span><span style="color: #009900;">&#91;</span><span style="color: #0000ff;">&quot;dsn&quot;</span><span style="color: #009900;">&#93;</span><span style="color: #339933;">;</span>
  <span style="color: #990000;">preg_match</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">&quot;/dbname=([^;]+)[;]?/&quot;</span><span style="color: #339933;">,</span> <span style="color: #000088;">$dsn</span><span style="color: #339933;">,</span> <span style="color: #000088;">$matches</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
  <span style="color: #000088;">$dbname</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$matches</span><span style="color: #009900;">&#91;</span><span style="color: #cc66cc;">1</span><span style="color: #009900;">&#93;</span><span style="color: #339933;">;</span>
&nbsp;
  <span style="color: #000088;">$sql</span> <span style="color: #339933;">=</span> <span style="color: #0000ff;">&quot;SELECT COUNT(*) AS c FROM information_schema.routines WHERE routine_name = '<span style="color: #006699; font-weight: bold;">{$routineName}</span>' AND routine_schema = '<span style="color: #006699; font-weight: bold;">{$dbname}</span>'&quot;</span><span style="color: #339933;">;</span>
  try
  <span style="color: #009900;">&#123;</span>
    <span style="color: #000088;">$dbh</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$con</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">getDbh</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
  <span style="color: #009900;">&#125;</span>
  catch <span style="color: #009900;">&#40;</span>Exception <span style="color: #000088;">$e</span><span style="color: #009900;">&#41;</span>
  <span style="color: #009900;">&#123;</span>
    <span style="color: #666666; font-style: italic;">// No database available - can't carry on</span>
    <span style="color: #b1b100;">return</span><span style="color: #339933;">;</span>
  <span style="color: #009900;">&#125;</span>
&nbsp;
  <span style="color: #000088;">$resultSet</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$dbh</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">query</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$sql</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
  <span style="color: #000088;">$results</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$resultSet</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">fetch</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
  <span style="color: #b1b100;">if</span> <span style="color: #009900;">&#40;</span><span style="color: #000088;">$results</span><span style="color: #009900;">&#91;</span><span style="color: #0000ff;">&quot;c&quot;</span><span style="color: #009900;">&#93;</span> <span style="color: #339933;">==</span> <span style="color: #cc66cc;">0</span><span style="color: #009900;">&#41;</span>
  <span style="color: #009900;">&#123;</span>
    <span style="color: #666666; font-style: italic;">// Create the function</span>
    <span style="color: #000088;">$sqlRoutine</span> <span style="color: #339933;">=</span> <span style="color: #990000;">file_get_contents</span><span style="color: #009900;">&#40;</span>sfConfig<span style="color: #339933;">::</span><span style="color: #004000;">get</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">&quot;zz_myplugin_dir&quot;</span><span style="color: #009900;">&#41;</span> <span style="color: #339933;">.</span> <span style="color: #0000ff;">&quot;/data/sql/&quot;</span> <span style="color: #339933;">.</span> <span style="color: #000088;">$routineFile</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
    <span style="color: #000088;">$con</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">getDbh</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">prepare</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$sqlRoutine</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">execute</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
  <span style="color: #009900;">&#125;</span>
<span style="color: #009900;">&#125;</span></pre></div></div>

<p>I&#8217;m not a massive fan of the way the database name has been retrieved but it works for now :-) I&#8217;m pretty sure there&#8217;s a neater way of accomplishing the above too, but this is working consistently and allows the stored functions to become part of a plugin, so that if the plugin is removed at a later date, then any subsequent database builds will not have unnecessary routines hanging around.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.whiteoctober.co.uk/blog/2011/01/05/stored-functions-with-symfony-and-doctrine/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Symfony &#8211; file uploads with custom names and paths</title>
		<link>http://www.whiteoctober.co.uk/blog/2010/10/07/symfony-file-uploads-with-custom-names-and-paths/</link>
		<comments>http://www.whiteoctober.co.uk/blog/2010/10/07/symfony-file-uploads-with-custom-names-and-paths/#comments</comments>
		<pubDate>Thu, 07 Oct 2010 08:03:25 +0000</pubDate>
		<dc:creator>Rich</dc:creator>
				<category><![CDATA[Web development]]></category>
		<category><![CDATA[Web technologies]]></category>
		<category><![CDATA[Symfony]]></category>

		<guid isPermaLink="false">http://www.whiteoctober.co.uk/blog/?p=617</guid>
		<description><![CDATA[I&#8217;ve been experimenting and doing a bit of digging with the Symfony file uploading in forms functionality. Out of the box, with eg. a Doctrine model, Symfony will save the filename only of an uploaded file, and it usually generates a random filename consisting of a hash of the original name together with a random [...]]]></description>
			<content:encoded><![CDATA[<p>I&#8217;ve been experimenting and doing a bit of digging with the <a href="http://www.symfony-project.org">Symfony</a> file uploading in forms functionality.  Out of the box, with eg. a Doctrine model, Symfony will save the filename <strong>only</strong> of an uploaded file, and it usually generates a random filename consisting of a hash of the original name together with a random number, and then appending the extension to the end.  So your database entry ends up looking something like:</p>
<p><code>id:  1<br />
name: Joe Bloggs<br />
profile_picture: ffba81d3ac4ef1dcba70.txt</code></p>
<p>Great, if you&#8217;re not concerned about the appearance of the filename after uploading.  If however, you want to preserve the filename, then you can create a <code>generateFilename()</code> method in your form object and Symfony will use that. But, what if you want to store more than the filename in your database, for example part of a path including multiple levels deep in directories? Or you want to do this each time, but don&#8217;t want to have to recreate/copy the same method multiple times? Or both?! Enter the use of <code>sfValidatedFile</code>.</p>
<p>In my experimentation, I wanted a custom file upload path, something like this:</p>
<ul>
<li><code>/home/rich/myproject/web/uploads/entries/4cad77fd2da01/1/filename.txt</code></li>
<li><code>/home/rich/myproject/web/uploads/entries/4cad77fd2da01/2/otherfile.jpg</code></li>
</ul>
<p>but I wanted to be able to store everything from 4cad&#8230; onwards as the filename, as everything up to &#8216;entries&#8217; was a Symfony configuration value.</p>
<p>So, in your form <code>configure()</code> method, do something like the following:<br />
<code><br />
// Generate your unique path here<br />
$path = sfConfig::get("sf_my_upload_path") . DIRECTORY_SEPARATOR . uniqid();<br />
</code><code><br />
$this->validatorSchema["myFileUpload"] = new sfValidatorFile(array("path" => $thisPath, "validated_file_class" => "myValidatedFile");<br />
</code></p>
<p>This tells Symfony to use a custom path to save, but also to use the <code>myValidatedFile</code> class to handle the processing of the uploaded file &#8211; move it to the correct place and so on.  The <code>myValidatedFile</code> class is what we&#8217;re going to use to accomplish the above challenges.</p>
<p>Next, create the file <code>lib/vendor/myValidatedFile.class.php</code>:</p>
<p><code><br />
class myValidatedFile extends sfValidatedFile<br />
{<br />
&nbsp;&nbsp;public function generateFilename()<br />
&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;return $this->path . DIRECTORY_SEPARATOR . $this->getOriginalName();<br />
&nbsp;&nbsp;}<br />
</code><code><br />
&nbsp;&nbsp;public function save($file = null, $fileMode = 0666, $create = true, $dirMode = 0777)<br />
&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;$result = parent::save($file, $fileMode, $create, $dirMode);<br />
</code><code><br />
&nbsp;&nbsp;&nbsp;&nbsp;// Alter the savedName to reflect what we're after<br />
&nbsp;&nbsp;&nbsp;&nbsp;$this->savedName = str_replace(sfConfig::get("sf_my_upload_path") . DIRECTORY_SEPARATOR, "", $this->savedName);<br />
</code><code><br />
&nbsp;&nbsp;&nbsp;&nbsp;return null === $this->path ? $this->savedName : str_replace($this->path.DIRECTORY_SEPARATOR, '', $this->savedName);<br />
&nbsp;&nbsp;}<br />
}<br />
</code></p>
<p><a title="I cannot believe I am linking to this..." href="http://www.youtube.com/watch?v=4Ust9YBlEfY">Simples</a>. Let&#8217;s take each method in turn.</p>
<p>The first method simply returns the full path and original filename.  Symfony will use the directory of this when deciding where to save the file, and it will use the leaf part to determine the file&#8217;s name.  Part 1 accomplished!</p>
<p>The second method calls the original sfValidatedFile <code>save()</code> method. We&#8217;re not concerned with the mechanics of saving the file, so we&#8217;ll just do as normal (we&#8217;ve already set the path above).  What we are concerned with is creating the correct value to be saved into the database or whatever you do with the field value eventually.</p>
<p>The key parts are the final 2 lines.  Essentially here, we remove the part of the file&#8217;s path and filename that we have stored in the configuration value.  This means that what is left will be (in the example above), our <code>uniqid()</code> value and the filename.  This return value is what is saved/used elsewhere.</p>
<p>And then, your database entry will look like this:</p>
<p><code>id:  1<br />
name: Joe Bloggs<br />
profile_picture: 4cad77fd2da01/2/joes_picture.jpg<br />
</code></p>
<p>All you need to do now is set the &#8220;validated_file_class&#8221; option value on your file upload widget to <code>myValidatedFile</code> whenever you need this functionality, and job done!</p>
]]></content:encoded>
			<wfw:commentRss>http://www.whiteoctober.co.uk/blog/2010/10/07/symfony-file-uploads-with-custom-names-and-paths/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>woPad</title>
		<link>http://www.whiteoctober.co.uk/blog/2010/04/16/wopad/</link>
		<comments>http://www.whiteoctober.co.uk/blog/2010/04/16/wopad/#comments</comments>
		<pubDate>Fri, 16 Apr 2010 14:27:52 +0000</pubDate>
		<dc:creator>Rich</dc:creator>
				<category><![CDATA[Cool stuff]]></category>
		<category><![CDATA[tech]]></category>
		<category><![CDATA[Web technologies]]></category>

		<guid isPermaLink="false">http://www.whiteoctober.co.uk/blog/?p=559</guid>
		<description><![CDATA[Well, not quite, but it was good to check that our site worked without problems on Apple&#8216;s latest offering :-) I took a mini-break to New York last week, and had the chance to pop into the Apple store on 5th Avenue (I love how awesome that sounds). Obviously then I had to try out [...]]]></description>
			<content:encoded><![CDATA[<p>Well, not quite, but it was good to check that our site worked without problems on <a href="http://www.apple.com/">Apple</a>&#8216;s <a href="http://www.apple.com/ipad/">latest offering</a> :-)</p>
<p><a href="http://www.whiteoctober.co.uk/blog/wp-content/uploads/2010/04/IMG_8988.jpg"><img src="http://www.whiteoctober.co.uk/blog/wp-content/uploads/2010/04/IMG_8988-300x225.jpg" alt="The White October site rendered in Safari on the iPad" title="WO site on iPad" width="300" height="225" class="alignleft size-medium wp-image-560" /></a></p>
<p>I took a mini-break to New York last week, and had the chance to pop into the Apple store on 5th Avenue (I love how awesome that sounds).  Obviously then I had to try out the <a href="http://www.apple.com/ipad/">iPad</a> first-hand.  It was clearly the most popular item in-store as the gorgeous MacBook Pros and large iMac demonstration areas were practically empty, with large crowds around the iPad areas.</p>
<p><a href="http://www.whiteoctober.co.uk/blog/wp-content/uploads/2010/04/IMG_8989.jpg"><img src="http://www.whiteoctober.co.uk/blog/wp-content/uploads/2010/04/IMG_8989-e1271428029999-225x300.jpg" alt="Crowds around the iPad demo areas" title="Crowds around the iPad demo areas" width="225" height="300" class="alignleft size-medium wp-image-561" /></a></p>
<p>It&#8217;s a nice device, if a little large for my liking, but very responsive, particularly with regards to the keyboard.  Positioning the iPad horizontally on the provided slanted stand allowed for typing on the on-screen keyboard at the same speed as you would a normal computer keyboard, and was extremely nice.  I was personally quite sceptical back in January when the product was announced, but I did find myself experiencing iPad-envy after playing with the device; travelling back to our hotel on the subway, I noticed about 4 people using them for various activities including reading books and browsing the web.  Suffice to say I restrained myself and didn&#8217;t purchase one sadly!</p>
<p>It&#8217;ll be interesting to see the UK release; by then hopefully all the little niggles will have been ironed out and we&#8217;ll have a solid product.  Watch this space :-)</p>
<p>Oh, and I also came across a stack of paper versions of <a href="http://www.theonion.com/">The Onion</a> hidden in a subway corridor. Very bizarre!</p>
]]></content:encoded>
			<wfw:commentRss>http://www.whiteoctober.co.uk/blog/2010/04/16/wopad/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Symfony admin generator theming</title>
		<link>http://www.whiteoctober.co.uk/blog/2010/03/23/symfony-admin-generator-theming/</link>
		<comments>http://www.whiteoctober.co.uk/blog/2010/03/23/symfony-admin-generator-theming/#comments</comments>
		<pubDate>Tue, 23 Mar 2010 13:04:33 +0000</pubDate>
		<dc:creator>Rich</dc:creator>
				<category><![CDATA[Design]]></category>
		<category><![CDATA[Web development]]></category>
		<category><![CDATA[Symfony]]></category>

		<guid isPermaLink="false">http://www.whiteoctober.co.uk/blog/?p=502</guid>
		<description><![CDATA[From this&#8230; To this (at the click of a few generator.yml changes)! As part of one of our current Symfony projects in work, a colleague and I decided that rather than reinventing the wheel everytime we used a new admin-generated module in the application with regards to overriding partials and actions in pretty much the [...]]]></description>
			<content:encoded><![CDATA[<p>From this&#8230;<br />
<a href="http://www.whiteoctober.co.uk/blog/wp-content/uploads/2010/03/old-edit-user.jpg"><img class="alignleft size-medium wp-image-515" title="old-edit-user" src="http://www.whiteoctober.co.uk/blog/wp-content/uploads/2010/03/old-edit-user-300x154.jpg" alt="old-edit-user" width="300" height="154" /></a><br />
To this (at the click of a few <code>generator.yml</code> changes)!<br />
<a href="http://www.whiteoctober.co.uk/blog/wp-content/uploads/2010/03/new-edit-user.jpg"><img class="alignleft size-medium wp-image-516" title="new-edit-user" src="http://www.whiteoctober.co.uk/blog/wp-content/uploads/2010/03/new-edit-user-300x179.jpg" alt="new-edit-user" width="300" height="179" /></a></p>
<p>As part of one of our current <a href="http://www.symfony-project.org/">Symfony</a> projects in work, a colleague and I decided that rather than reinventing the wheel everytime we used a new <a href="http://www.symfony-project.org/jobeet/1_4/Doctrine/en/12">admin-generated module</a> in the application with regards to overriding partials and actions in pretty much the same way each time, it would be productive and easier in the long run to create an admin generator theme.  The documentation on the Symfony site for this is sparse to say the least, and when I get some time, I&#8217;ll look at contributing back to the Symfony docs a condensed version of this blog post so that it’s a bit more straightforward for the next person.  You have the ability to customise pretty much every part of the admin generator including batch actions, form actions, filters, table headers and so on, so it&#8217;s worth the work in the long run if you&#8217;re looking for quick admin-generated module deployments in line with your site&#8217;s template.</p>
<p>OK, so before I start, be warned this is nice and long (and hopefully explanatory)&#8230;!  I&#8217;ll also say that <strong>clearing your cache</strong> upon making changes to partials is <strong>vital</strong>.  I repeat, <strong>VITAL</strong>! :-)</p>
<h2>Getting started</h2>
<p>The first step I carried out, which is mentioned on the Symfony site, was to copy all the files from the default theme into your project.  In our case, we&#8217;re using <a href="http://www.doctrine-project.org/">Doctrine</a> as the ORM, so the files were found in <code>lib/vendor/symfony/plugins/sfDoctrinePlugin/data/generator/sfDoctrineModule/admin</code>, and they were copied to <code>data/generator/sfDoctrineModule/ourTheme</code>.  This directory contains 3 subdirectories – we&#8217;ll need all 3.  The names should be self-explanatory &#8211; &#8220;<code>parts</code>&#8221; consists of all the parts needed to dynamically generate the code, &#8220;<code>skeleton</code>&#8221; consists of skeleton files that form the basis of the final module (<code>generator.yml</code> etc), and &#8220;<code>template</code>&#8221; consists of the template files that use the files in &#8220;<code>parts</code>&#8220;. Simple!</p>
<p>That&#8217;s the basic step out of the way.  If you want to check that everything is being correctly read here, simply alter an existing generator.yml file as follows:</p>
<p><code>theme:    ourTheme</code></p>
<p><strong>Clear your cache</strong>, and reload your page.  You shouldn&#8217;t see any change – this is because the files are exactly the same as the default theme.  Now comes the fun part!</p>
<p>For the following paths, I&#8217;ll assume the prefix of <code>data/generator/sfDoctrineModule/ourTheme/</code> to avoid typing it out each time.</p>
<h2>Adding configuration options</h2>
<p>The first change I wanted to carry out was to add some extra configuration options via the <code>generator.yml</code> file.  One major gripe I have with the Symfony admin generator is that once you&#8217;ve added a new object, or edited one, the default destination is the edit page, rather than the list page.  Personally I see returning to the list as expected behaviour, and every project I use the admin generator in, I end up overriding the <code>processForm()</code> action and making this change.  I wanted to be able to specify this destination via a configuration option on the &#8216;new&#8217; and &#8216;edit&#8217; contexts, with a &#8216;return_to&#8217; option.  Carrying out this change involves overriding some other classes in Symfony, most notably the <code>sfModelGeneratorConfiguration</code> class.  I copied this class from the Symfony core to <code>lib/generator/myModelGeneratorConfiguration.class.php</code>.</p>
<p>Once you&#8217;ve done this, alter <code>parts/configuration.php</code> so that the generated class extends from <code>myModelGeneratorConfiguration</code> instead of <code>sfModelGeneratorConfiguration</code>.</p>
<p>Open up this class, and you&#8217;ll see a lot of abstract methods defined near the top.  These methods are created during the generation process, and are located in the <code>parts/fieldsConfiguration.php</code> file.  You&#8217;ll see that it consists of what looks like PHP within PHP – this is a common theme throughout the development of an admin generator theme, and can get a tad confusing if you forget what context you&#8217;re in when you&#8217;re writing code!  You can use the example methods to build your own.  In my case, I added the following:</p>
<p><code>public function getNewReturnTo()<br />
{<br />
return '&lt;?php echo $this-&gt;escapeString(isset($this-&gt;config['new']['return_to']) ? $this-&gt;config['new']['return_to'] : 'edit') ?&gt;';<br />
&lt;?php unset($this-&gt;config['new']['return_to']) ?&gt;<br />
}</code><br />
<code><br />
public function getEditReturnTo()<br />
{<br />
return '&lt;?php echo $this-&gt;escapeString(isset($this-&gt;config['edit']['return_to']) ? $this-&gt;config['edit']['return_to'] : 'edit') ?&gt;';<br />
&lt;php unset($this-&amp;gtconfig['edit']['return_to']) ?&gt;<br />
}<br />
</code><br />
These methods both return a configuration value if it exists in the YAML file, or the defaults of &#8216;new&#8217; and &#8216;edit&#8217; respectively if no configuration value is specified.</p>
<p>In order to make this option available in the configuration, the <code>myModelGeneratorConfiguration-&gt;compile()</code> method should be altered to allow this option:</p>
<p><code>// ...<br />
'new'    =&gt; array(<br />
'fields'  =&gt; array(),<br />
'title'   =&gt; $this-&gt;getNewTitle(),<br />
'actions' =&gt; $this-&gt;getNewActions() ? $this-&gt;getNewActions() : $this-&gt;getFormActions(),<br />
'return_to' =&gt; $this-&gt;getNewReturnTo()<br />
),<br />
// …<br />
</code></p>
<p>and the same for the edit option.  The final change is to actually action this change in the <code>processForm()</code> method, conveniently found in <code>parts/processFormAction.php</code>.  This file (along with all the other <code>*Action.php</code> files) are included in the generated <code>sfActions</code> class specific to the module you&#8217;re creating.  I removed the final redirect line which redirected back to the &#8216;edit&#8217; URL, and replaced it with the following changes:</p>
<p><code>// ...<br />
if ($form-&gt;isValid())<br />
{<br />
$thisContext = $form-&gt;getObject()-&gt;isNew() ? 'new' : 'edit';<br />
$notice = $form-&gt;getObject()-&gt;isNew() ? 'The item was created successfully.' : 'The item was updated successfully.';</code><br />
<code><br />
// ...<br />
$this-&gt;redirect('@&lt;?php echo $this-&gt;getUrlForAction('new') ?&gt;');<br />
}<br />
else<br />
{<br />
$route = '&lt;?php echo $this-&gt;getSingularName(); ?&gt;';<br />
$returnTo = strtolower($this-&gt;configuration-&gt;getValue($thisContext . '.return_to'));<br />
if ($returnTo != 'list')<br />
{<br />
$route .= '_' . $returnTo;<br />
}</code><br />
<code><br />
$returnArray = array('sf_route' =&gt; $route);<br />
if ($returnTo != 'list')<br />
{<br />
$returnArray['sf_subject'] = $&lt;?php echo $this-&gt;getSingularName(); ?&gt;;<br />
}<br />
$this-&gt;redirect($returnArray);<br />
</code></p>
<p>That&#8217;s it! Add the configuration option into your generator.yml as follows:<br />
<code>new:<br />
return_to: list<br />
</code></p>
<p><strong>Clear your cache</strong>, reload the page and test it out.  After creating a new object, you should be returned to the list.  This is restricted to eg <code>my_model_ACTION</code>-type routes, but you can easily enhance the above code to redirect to any other route, check for an &#8220;@&#8221; symbol if needs be and so on.</p>
<h2>New &#8220;contexts&#8221;</h2>
<p>I wanted to implement an Ajax edit form on a &#8220;view&#8221; page for my model, so that the user wasn’t taken away.  The templates I was working from had this as a pop-over box, but instead of redirecting the user afterwards, I wanted the edit and update to be seamless.  In terms of the form, the ajax form was similar (but slightly different) to the normal admin-generated edit form, so I wanted the ability to be able to configure the form separately from the edit page.</p>
<p>I experimented with configuring it as part of my &#8216;view&#8217; context but ran into problems with nested array configurations, so I decided to abstract it out and create a new &#8220;context&#8221; with which I could configure items in the same way as a normal context (&#8216;new&#8217;, &#8216;edit&#8217;, &#8216;list&#8217; and so on).  I termed this &#8216;ajaxedit&#8217;;  NB I’m calling them &#8216;contexts&#8217; here as I’m sure I read that’s what they were called somewhere! Correct me if not ;-)</p>
<p>The creation of this is very similar to the above changes for adding a configuration value.  In <code>myModelGeneratorConfiguration.class.php</code>, add the following in to the configuration array in <code>compile()</code>:</p>
<p><code>'ajaxedit' =&gt; array(<br />
'title'   =&gt; $this-&gt;getAjaxeditTitle(),<br />
'actions' =&gt; $this-&gt;getAjaxeditActions() ? $this-&gt;getAjaxeditActions() : $this-&gt;getFormActions(),<br />
'fields'  =&gt; array()<br />
</code></p>
<p>I added this after the &#8216;edit&#8217; one currently there.  If you proceed down through this method, I also duplicated the &#8216;edit&#8217; line in the first <code>foreach()</code> loop, where the fields are configured using <code>sfModelGeneratorConfigurationField</code>, and altered &#8216;edit&#8217; to &#8216;ajaxedit&#8217; where appropriate.  Same in the &#8216;virtual fields&#8217; configuration, the form actions, the field configuration (duplicated from &#8216;list field configuration&#8217;), and the <code>$this-&gt;parseVariables()</code> lines.</p>
<p>You should also add &#8216;ajaxedit&#8217; to the credentials array near the end of the <code>compile()</code> method, so that you can control access to the popup/context with credentials, and again into the <code>getConfig()</code> method at the end of the class.</p>
<p>You then need to create the relevant abstract methods as before, so that the context can be correctly configured.  Again, I used the &#8216;edit&#8217; context as an example to base my changes and additions on.</p>
<p>The next step is to configure the templates and partials so that they recognise this.  I used the default <code>_form.php</code> partial so that I could re-use code where possible, but added a &#8216;context&#8217; variable to the partial when calling it, eg. in my view page:</p>
<p><code><br />
[?php include_partial('&lt;?php echo $this-&gt;getModuleName() ?&gt;/form', array('&lt;?php echo $this-&gt;getSingularName() ?&gt;' =&gt; $&lt;?php echo $this-&gt;getSingularName() ?&gt;, 'form' =&gt; $form, 'configuration' =&gt; $configuration, 'helper' =&gt; $helper, 'context' =&gt; 'ajaxedit')) ?]<br />
</code></p>
<p>In my form partial, I then altered the <code>getFormFields()</code> call to the following:</p>
<p><code><br />
[?php foreach ($configuration-&gt;getFormFields($form, $form-&gt;isNew() ? 'new' : 'edit') as $fieldset =&gt; $fields): ?]<br />
</code></p>
<p>so that I was using the correct set of fields for the form display.  Don&#8217;t forget to <strong>clear your cache</strong> after making these changes.</p>
<p>You can handle the ajax post however you like; I used <a href="http://jquery.com/">jQuery</a> and a callback handler to adjust details and display new values etc where required.  The handling of the form data was done in the same way as a normal Symfony action; details of this (and Ajax-specific stuff) are in the main Symfony documentation.</p>
<p>Here&#8217;s the result:<br />
<a href="http://www.whiteoctober.co.uk/blog/wp-content/uploads/2010/03/ajax-edit.jpg"><img class="alignleft size-medium wp-image-522" title="ajax-edit" src="http://www.whiteoctober.co.uk/blog/wp-content/uploads/2010/03/ajax-edit-300x196.jpg" alt="ajax-edit" width="300" height="196" /></a></p>
<h2>Conclusion</h2>
<p>The one major thing to remember is that <strong>clearing your cache</strong> is <strong>VITAL</strong> to a speedy admin generator theme development.  If you&#8217;re running in dev mode via your front controller, then Symfony will only regenerate partials that are directly related to your <code>generator.yml</code> on the fly.  It won&#8217;t recreate action templates if there is no need to from <code>generator.yml</code> changes.  This means that you will need to manually call &#8220;<code>./symfony cc</code>&#8221; from the command line whenever you make changes to partials.  I lost count of how many times I ran that task during my theme development :-)</p>
<p>I&#8217;d imagine there are nicer ways to do some of the above – I&#8217;m not particularly a fan of the overriding of classes and having them separate from the theme, although I know that Symfony&#8217;s autoloading can be pretty clever sometimes, so it would probably find the classes with a bit of tweaking, if I put them as part of the theme.  It&#8217;s also a bit tricky to get your head around the whole PHP-creating-PHP idea, and remembering whereabouts you are, and what variables you have available to you.  I lost a good few hours trying to use variables that were in the top-level PHP, in my generated partials, and wondering why they weren’t working.</p>
<p>However, the end result is definitely worth it and saves a lot of time in the long run if you&#8217;re looking to re-use templates and code across a number of admin-generated modules!</p>
]]></content:encoded>
			<wfw:commentRss>http://www.whiteoctober.co.uk/blog/2010/03/23/symfony-admin-generator-theming/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>PHP UK Conference 2010</title>
		<link>http://www.whiteoctober.co.uk/blog/2010/03/05/php-uk-conference-2010/</link>
		<comments>http://www.whiteoctober.co.uk/blog/2010/03/05/php-uk-conference-2010/#comments</comments>
		<pubDate>Fri, 05 Mar 2010 17:15:41 +0000</pubDate>
		<dc:creator>Rich</dc:creator>
				<category><![CDATA[Web development]]></category>
		<category><![CDATA[Web technologies]]></category>

		<guid isPermaLink="false">http://www.whiteoctober.co.uk/blog/?p=491</guid>
		<description><![CDATA[Last Friday (26 February 2010), myself and Ed Lucas from White October took a trip down the M40 to the PHP UK Conference 2010, hosted at the Business Design Centre in London.  The purpose of the trip was three-fold &#8211; firstly to attend some great talks, secondly to socialise and meet other members of the [...]]]></description>
			<content:encoded><![CDATA[<p>Last Friday (26 February 2010), myself and <a title="Ed Lucas" href="http://www.whiteoctober.co.uk/blog/author/elucas/">Ed Lucas</a> from White October took a trip down the M40 to the <a title="PHP UK Conference 2010" href="http://www.phpconference.co.uk/">PHP UK Conference 2010</a>, hosted at the Business Design Centre in London.  The purpose of the trip was three-fold &#8211; firstly to attend some great talks, secondly to socialise and meet other members of the PHP community, and thirdly to see if we could recruit any budding new junior PHP developers (we failed on the last part but <a href="http://www.whiteoctober.co.uk/contact-_3/">get in touch</a> if you&#8217;re interested in working for a great web agency in Oxford).</p>
<p>The conference kicked off with the keynote speech delivered by <a href="http://joshholmes.com/">Josh Holmes</a>, on &#8220;The lost art of simplicity&#8221;.  This was a great keynote &#8211; some of the feedback I&#8217;ve read since the conference describes it as &#8220;nothing new&#8221;, or &#8220;knew this already&#8221; but regardless of whether you knew it already or not, it&#8217;s all too easy to forget about making things simple.  An example that stuck in my mind from the keynote was if a client asks for a report&#8230; and they&#8217;re delivered a report system.  Error, not what the client asked &#8211; they don&#8217;t care about a system to do it, they just want to know how many pencils they have in stock etc.  This seemed to ring true with a large majority in the room! It&#8217;s something that we at White October try and build into systems/sites we produce, so that the client has as much hands-on control as they need, without fancy technology or &#8220;cool ideas&#8221; getting in the way.</p>
<p>After the keynote, Ed and I split off to attend talks in our areas of interest.  My first stop was <a href="http://www.leftontheweb.com/">Stefan Koopmanschap</a>&#8216;s talk on documentation.  Another area which seems to get forgotten about! Stefan gave some great examples of how to get documentation done effectively, eg by getting your users to write the user manual (NOT the developers!).  In particular, he included &#8220;obvious&#8221; documentation methods which may be forgotten about, such as commit messages, <a href="http://en.wikipedia.org/wiki/PHPDoc">DocBlock</a> comments in code and unit/functional tests (we all do those, right?).  These all make for a much more maintainable system.  Great talk &#8211; extremely useful.</p>
<p>The lunchtime talk was given by <a href="http://www.symfony-project.org/">Symfony</a> legend <a href="http://fabien.potencier.org">Fabien Potencier</a>, on new features in PHP 5.3 and how to solve real problems using new features.  The ubiquitous &#8220;lambda functions&#8221; came up, used as part of the example of the <a href="http://symfony-reloaded.org/">Symfony 2</a> <a href="http://components.symfony-project.org/dependency-injection/">Dependency Injection</a> container.  Thoughts are divided in the WO camp on dependency injection, but I think once we shift to Symfony 2 and start using it in practice, it will make much more sense.  We also ambushed Fabien at lunch regarding the possibility of open-sourcing/releasing Sismo (the Symfony continuous integration server) but sadly it appears that&#8217;s not meant to be!</p>
<p>After lunch I attended the &#8220;hidden features&#8221; talk by <a href="http://schlueters.de/">Johannes Schluter</a> which was interesting, if a little disjointed.  In particular, I discovered useful applications of <a href="http://uk2.php.net/streams">streams</a> eg for processing zip files, and  the PECL &#8216;<a href="http://pecl.php.net/package/inclued">inclued</a>&#8216; package which shows you your require/include() calls. Handy stuff.  This talk was followed by &#8220;Regex-fu&#8221;, by <a href="http://www.adviesenzo.nl/">Juliette Folmer</a> &#8211; lots of sweet-throwing around the room for correct answers to questions; highly entertaining! I picked up a good few tips here, and enjoyed the explanations of how regular expressions work internally in terms of optimizing them etc.  I haven&#8217;t seen a talk with quite so many random characters being drawn on the presentation whiteboard before however&#8230;</p>
<p>The final talk we both attended was &#8220;Best practices for web service design&#8221; by <a href="http://www.lornajane.net/">Lorna Jane Mitchell</a>.  This was an excellent talk, littered with real-world anecdotes from Lorna, and included again some &#8220;obvious&#8221; ideas/guidelines which I&#8217;d imagine we all forget to do from time to time! Things like stacking errors, presenting a consistent interface in terms of response codes etc, keeping as small an API as is needed for operation &#8211; all useful stuff, and a good way to end the talks of the day.</p>
<p>After the talks finished, <a href="http://www.facebook.com/">Facebook</a> kindly provided a large amount of free beverages, which we all were only to happy to partake in.  We met some interesting people during the next few hours, and tried our best to recruit budding junior developers&#8230; and failed.  Seems like everyone was recruiting at this year&#8217;s conference! We then headed back up to WO HQ in Oxford.</p>
<p>There were a reasonable number of stands, and varied ones at that, which was good.  The <a href="http://oreilly.com/">O&#8217;Reilly</a> stand seemed to get a large amount of attention; probably due to the great discount available! It was also good to see the <a href="http://www.data.gov.uk/">data.gov.uk</a> stand in attendance, if a little sparse each time I glided past&#8230;</p>
<p>Anyway, a massive thanks to the <a href="http://www.phplondon.org/">PHP London</a> guys, for putting on a great conference &#8211; definitely looking forward to next year&#8217;s!</p>
]]></content:encoded>
			<wfw:commentRss>http://www.whiteoctober.co.uk/blog/2010/03/05/php-uk-conference-2010/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Hudson and Symfony continuous integration</title>
		<link>http://www.whiteoctober.co.uk/blog/2010/02/05/hudson-and-symfony-continuous-integration/</link>
		<comments>http://www.whiteoctober.co.uk/blog/2010/02/05/hudson-and-symfony-continuous-integration/#comments</comments>
		<pubDate>Fri, 05 Feb 2010 15:02:34 +0000</pubDate>
		<dc:creator>Rich</dc:creator>
				<category><![CDATA[Agile]]></category>
		<category><![CDATA[Test Driven Development]]></category>
		<category><![CDATA[Web development]]></category>
		<category><![CDATA[Symfony]]></category>

		<guid isPermaLink="false">http://www.whiteoctober.co.uk/blog/?p=460</guid>
		<description><![CDATA[We&#8217;ve been experimenting this week with using Hudson as our continuous integration server for our latest Symfony project.  Previous experiences with CruiseControl via phpUnderControl and the symfonyUnderControl plugin were OK-ish but we&#8217;d occasionally experience CruiseControl dying on us with no warning apart from the lack of emails after checking our code in. So we decided [...]]]></description>
			<content:encoded><![CDATA[<p>We&#8217;ve been experimenting this week with using <a title="Hudson" href="http://hudson-ci.org/">Hudson</a> as our continuous integration server for our latest <a href="http://www.symfony-project.org/">Symfony</a> project.  Previous experiences with <a title="CruiseControl" href="http://cruisecontrol.sourceforge.net/">CruiseControl</a> via <a title="phpUnderControl" href="http://phpundercontrol.org/">phpUnderControl</a> and the <a title="symfonyUnderControl" href="http://www.symfony-project.org/plugins/symfonyUnderControlPlugin">symfonyUnderControl</a> plugin were OK-ish but we&#8217;d occasionally experience CruiseControl dying on us with no warning apart from the lack of emails after checking our code in.</p>
<p>So we decided it was time to perhaps investigate alternatives.  The obvious choice for Symfony would be to use <a title="Sismo" href="http://ci.symfony-project.org/">Sismo</a>, but sadly it hasn&#8217;t been released yet&#8230; <a title="Fabien Potencier" href="http://fabien.potencier.org">Fabien</a>, any clues as to a release date? ;-) It looks at the moment as if we&#8217;re stuck with a Java-based solution, so we decided to look at Hudson as an alternative.</p>
<p>Setting up Hudson was straightforward using <a title="Hudson and Symfony by Nicolas Perriault" href="http://prendreuncafe.com/blog/post/2009/10/06/Simple-Continuous-Integration-of-a-Symfony-Project-using-Hudson">Nicolas Perriault&#8217;s blog post</a> on exactly what we were trying to achieve, linking in with our Subversion repository.  The configuration of the projects took a bit of faffing &#8211; for some reason, Hudson liked to check out the whole repository rather than just eg the trunk folder  as specified, so this screwed up some of the paths.  A bit of trial and error later and we were up and running with a build every 30mins if there are new commits, as this pretty graph shows&#8230; (using the jUnit XML output available in Symfony 1.4).  Total number of tests in blue, failures in red.</p>
<p><img class="alignleft size-medium wp-image-461" title="Hudson build trend output" src="http://www.whiteoctober.co.uk/blog/wp-content/uploads/2010/02/build-trend-300x120.png" alt="Hudson build trend output" width="300" height="120" /></p>
<p>One thing to bear in mind if you&#8217;re getting test failures randomly, or perhaps always after a certain developer checks in (and it&#8217;s not a result of broken code) is to ensure that the permissions are correct for Symfony, as SVN likes to store permissions where it can.  The fix for this, apart from committing new permissions, is to add a build step in Hudson that carries out ./symfony project:permissions (Symfony 1.3+) shortly before the build.  This seems to solve it nicely, and explained the small red peaks in the graph above.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.whiteoctober.co.uk/blog/2010/02/05/hudson-and-symfony-continuous-integration/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Don&#8217;t be afraid to NOT use the ORM&#8230;</title>
		<link>http://www.whiteoctober.co.uk/blog/2009/10/16/dont-be-afraid-to-not-use-the-or/</link>
		<comments>http://www.whiteoctober.co.uk/blog/2009/10/16/dont-be-afraid-to-not-use-the-or/#comments</comments>
		<pubDate>Fri, 16 Oct 2009 15:06:03 +0000</pubDate>
		<dc:creator>Rich</dc:creator>
				<category><![CDATA[Web development]]></category>
		<category><![CDATA[Web technologies]]></category>
		<category><![CDATA[doctrine]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[SQL]]></category>
		<category><![CDATA[Symfony]]></category>

		<guid isPermaLink="false">http://www.whiteoctober.co.uk/blog/?p=353</guid>
		<description><![CDATA[I&#8217;ve been racking my brains the past couple of days with a Doctrine issue within a Symfony project. I was constructing a bank statement-esque page, which iterated over a &#8220;StatementEntry&#8221; table and displayed the results. Entries could either be of model Type A or model Type B, with the relevant model ID stored in the [...]]]></description>
			<content:encoded><![CDATA[<p>I&#8217;ve been racking my brains the past couple of days with a <a title="Doctrine" href="http://www.doctrine-project.org/">Doctrine</a> issue within a <a title="Symfony" href="http://www.symfony-project.org/">Symfony</a> project.  I was constructing a bank statement-esque page, which iterated over a &#8220;StatementEntry&#8221; table and displayed the results.  Entries could either be of model Type A or model Type B, with the relevant model ID stored in the StatementEntry table.  The relationship was defined by a &#8220;type&#8221; column.  Type A and B models also had other relationships after the initial one.</p>
<p>The problem I discovered was that obviously Doctrine didn&#8217;t know what the conditional relationships were, since it was dependant on the type column.  I researched some different approaches:</p>
<ol>
<li> <strong>Doctrine&#8217;s RawSql</strong>
<p>This approach worked for generating the correct SQL query without a problem, but the issue then arose of how Doctrine could hydrate the result set.  Answer was &#8211; it couldn&#8217;t :-) Well, I lie &#8211; I could use Doctrine::HYDRATE_SCALAR but I had issues with other related models, where I was joining to a table twice but under different aliases, which failed, since the scalar hydration would override earlier joins on the same table.</li>
<li> <strong>Column aggregation</strong>
<p>Column aggregation looked great initially &#8211; I could have a &#8220;TypeAEntry&#8221; and a &#8220;TypeBEntry&#8221;, and Doctrine would have handled the typing of the Entry automatically.  I couldn&#8217;t however see a way to query &#8220;in reverse&#8221; &#8211; get all Entries and their associated models.</li>
<li> <strong>Write a raw SQL query, and use Doctrine&#8217;s PDO instance to retrieve the data.</strong></li>
</ol>
<p>Option 3) was the one I settled for in the end.  It meant of course that I had to specify column names, and perform all the joins myself, but it did mean I could specify precisely what data I wanted back.  This came back in the form of a normal array.  I&#8217;d tried to stay away from this option intially, but after spending too long researching the other 2 and hitting brick walls, I bit the bullet and dropped down to the raw stuff.  And hey presto!</p>
<p>Note &#8211; this approach will only work if you want the data to be read-only.  If you want to then use Doctrine&#8217;s model goodness, you&#8217;ll need to work out how to transform that *back* into a model.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.whiteoctober.co.uk/blog/2009/10/16/dont-be-afraid-to-not-use-the-or/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

