Symfony2 data collectors

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’s MailChimp distribution list. Unsubscription could happen via a checkbox in the user’s account settings and this needed to work the same way behind the scenes.

I opted to use one of the available MailChimp bundles 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 Symfony2 data collectors.

The concept is simple – 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.

First step – create me a data collector:

<?php
 
namespace Jirafe\Bundle\MailChimpBundle\DataCollector;
 
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Jirafe\Bundle\MailChimpBundle\Connection\ConnectionInterface;
 
class MailChimpDataCollector extends DataCollector
{
    private $connection;
 
    public function __construct(ConnectionInterface $connection)
    {
        $this->connection = $connection;
    }
 
    public function collect(Request $request, Response $response, \Exception $exception = null)
    {
        $this->data = array(
            'requests'  => $this->connection->getRequests(),
        );
    }
 
    public function getRequestCount()
    {
        return count($this->data['requests']);
    }
 
    public function getRequests()
    {
        return $this->data['requests'];
    }
 
    public function getName()
    {
        return 'mail_chimp';
    }
}

The collect() method is the interesting part here – this is called whenever data collection is required – usually once per request. We collect the requests from the connection supplied to the constructor.

Next step, register this during the bundle’s load() method:

// Add connection to data collector
$definition = $container->getDefinition('mail_chimp.data_collector');
$definition->addArgument(new Reference(sprintf('mail_chimp.connection.%s', $config['connection'])));

The above uses the connection type specified in the bundle’s configuration option and previously registered as a service in the same method. If you’re creating your own custom collector, this is just the normal parameters you’d pass in to your service when it’s instantiated.

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 getRequests() method listed above in the data collector; called in the collect() method. Quick ‘n’ dirty here, since there would only ever be a single MailChimp request (possibly 2) made in any one request.

Once all this was in place, I could then test correctly. Note that I’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 getSymfonyProfiler() method detailed in a Behat/Mink cookbook entry, and proceeded to write my step (Behat 1.x-style):

$steps->Then('/^my email address is unsubscribed from the relevant MailChimp list$/', function($world) {
 
    $profiler = $world->getSymfonyProfiler();
    if (!$profiler)
    {
      throw new \RuntimeException("Symfony2 profiler not present for tests, wrong driver?");
    }
    $mcDataCollector = $profiler->getCollector("mail_chimp");
    $listID = $world->container->getParameter("mailchimp_list_id");
 
    // Check the requests made to make sure we have an unsubscribe
    assertEquals(1, $mcDataCollector->getRequestCount());
 
    $requests = $mcDataCollector->getRequests();
    $req1 = $requests[0];
    assertEquals("listUnsubscribe", $req1->getMethod());
 
    $params = $req1->getParams();
    assertEquals($listID, $params["id"]);
 
    // Fire the redirect on the client
    $world->getSession()->getDriver()->getClient()->followRedirect();
});

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’re subscribing eg via a form POST, then you’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’ve carried out your checks.

The implementation for the above is up on our Github MailChimpBundle fork here; I’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’s good to use even more features of Symfony2 ;-)

jQuery Conference, Eventbrite fees

Some people might be aware that we are organising jQuery UK, Europe’s first jQuery conference.

This is the first major conference we have put on and it is a learning curve for all of us. We’ve had quite a lot of feedback on the fees that Eventbrite charge us for using their system and credit card processing, with a common comparison to Ryanair!

We had originally split these out from the main costs to try and be transparent over the true costs of the conference, and to keep it in line with similar conferences in the States. So the website said £150 + VAT, where in reality it was £150 + VAT + Booking fee … in retrospect, not a great move.

So we have made a modification to the price which means that the full price, including booking fee, is now £160 + VAT. We have to take a little bit of a hit on this, but this feels like the right thing to do. Thankfully, it seems, this mistake hasn’t impacted ticket sales which are surpassing our expectations for our first week.

Thanks all for the feedback and see you in February.

MailChimp user interface testing

At White October we’re pretty fond of using MailChimp and recommending it where suitable to clients – it’s a user-friendly product that does one thing and does it really well. It’s also dead easy to integrate and use – whether it’s the custom forms, or an integration direct into your own site (eg via a footer “Sign up!” form). Plus, I snagged a free t-shirt last year in one of their Twitter-based giveaways, so I’m more than happy to recommend them ;-)

Yesterday I was privileged to be involved in a user testing and feedback session that they were running down in Brighton. Aarron Walter and Stephanie Troeth from MailChimp came down, and ran an hour’s session with me where we went through various aspects of the MailChimp interface, features that were available (including ones I wasn’t aware of), and various usability points were addressed. I felt it was incredibly beneficial to both sides – MailChimp, so that they can understand what works and what doesn’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’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 “designed by developers”. And we know how that turns out… 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.

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’t aware of – there’s a large number of different integrations available in the admin area – everything from Google Analytics (tracks MailChimp campaigns being sent out against page views, e-commerce goals etc, all inside your admin area) to Freshbooks (import contacts etc). I also discovered the MailChimp integrations directory – very cool. One very good point Aarron also raised was how I found out about new features – for example, just clicking around in the admin area through to email and Twitter updates from MailChimp etc. There’s no point having new features available if you don’t shout about them…!

Overall – more than happy to carry on recommending them where suitable – a company that listens to its users AND provides a great service too. And maybe the most interesting fact of the session? Learning Freddie the chimp’s full name…. not sure how common knowledge that is, so maybe I’ll keep that a secret for now ;-)

Sending emails in Silex via Swiftmailer spool

In Symfony2 it is relatively simple to send emails via a spool file, however in Silex we don’t have a console.

So we’ll have to make one and it’s very simple.

First you need to have the Symfony2 Console component setup in your Silex project.

The following console file is based on the example in this blog post and the Swiftmailer bundle console.

Make that file executable, or remove the first line and call it through the php cli.

You will also have to do a little change to your current Silex bootstrap file:

That should be it. This means you can now have a cron running to send emails and to the user they don’t have the wait while the SMTP server responds.

Functional testing sending emails in Silex

We are slowly starting to fall in love with Silex the Micro Framework built using Symfony2 here at White October towers.

Here is a quick code sample which shows how to test emails that are sent using Silex and Swiftmailer.

I would recommend using this example app to get Silex and Swiftmailer setup if you haven’t got a working setup.

The aim in the code is to stop sending of real email in the test environment and log the messages so we can test them.

Here is a quick gist of the Test class, testing for number of emails sent and the first emails subject.

I have copied out the Symfony2 MessageLogger.php class from the SwiftmailerBundle, which I’ve included in the gist for your convenience.

Getting into Behat with Symfony2

I’ve spent the past week starting a new sprint on our latest large project, and as we’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’re pretty big on Test-Driven Development here at WO Towers but sometimes it’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.

The beauty of BDD is that it’s written in plain everyday language, and this can be directly translated to code snippets. Therefore the client is happy working “in plain english”, and the developer can crack on with writing the actual code to test these parts.

We’ve chosen to use Symfony2 for the project (more on that in a separate post) and for the BDD aspect we’ve gone with Behat (coupled with Mink); both of which have bundles available for Symfony2. Essentially, BDD boils down to Features, consisting of a number of Scenarios, which in turn consist of a number of Steps. Here’s an example:

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

This could translate to code such as the following:

$steps->Given('/^I visit the admin area$/', function($world) {
 
    $world->getSession()->visit($world->getPathTo("/admin"));
});
 
$steps->When('/^I am shown the login form$/', function($world) {
 
    $doc = $world->getSession()->getPage();
 
    assertTrue($doc->hasField("_username"));
    assertTrue($doc->hasField("_password"));
});
 
$steps->And('/^I click login with my correct credentials$/', function($world) {
 
    $doc = $world->getSession()->getPage();
 
    $doc->fillField("_username", "joe");
    $doc->fillField("_password", "bloggs");
    $doc->clickButton("Log in");
});
 
$steps->Then('/^(?:|I )should be on (?P<page>.+)$/', function($world, $page) {
 
    assertEquals(
        parse_url($world->getPathTo($page), PHP_URL_PATH),
        parse_url($world->getSession()->getCurrentUrl(), PHP_URL_PATH)
    );
});

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’t implemented yet are given default code as per:

$steps->Given('/^I visit the admin area$/', function($world) {
    throw new \Behat\Behat\Exception\PendingException();
});

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.

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:

I go to /my/url:
 
$steps->When('/^(?:|I )go to (?P<page>.+)$/', function($world, $page) {
    $world->getSession()->visit($world->getPathTo($page));
});

and

I fill in "username" with "foo":
 
$steps->When('/^(?:|I )fill in "(?P<field>[^"]*)" with "(?P<value>[^"]*)"$/', function($world, $field, $value) {
    $world->getSession()->getPage()->fillField($field, $value);
});

which translates as even less code to write. Bonus!

Tips ‘n’ tricks

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’re integrating Facebook authentication using the FOS FacebookBundle, 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 – switch to using Goutte 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’ll write up in a separate blog post as it’s a bit epic for here.

The documentation mentions using a separate front controller for testing, with a separate environment configuration. This is definitely recommended, particularly if you’re using Goutte. If you need services to be available, for example your entity manager, pop something like this in your env.php file:

$kernel = new AppKernel('test', true);
$kernel->loadClassCache();
$service = $kernel->getContainer()->get("your.service.here");

You can also use the env.php file for setting up fixtures that need to be reset for each Scenario that you create, eg pre-defined user states and so on.

Caveats

Unless you’re using Sahi, you obviously can’t test things such as Javascript popups effectively. I’m in two minds as to whether this should be handled by a new type of Exception which can be thrown e.g. a TestInRealBrowserException(). 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.

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!

wocowork – Our first year of Coworking

If you come and visit White October, you’ll see this photo that I took in a restaurant in Wellington, New Zealand. It’s a little wind-up toy that came on the little plastic tray that had the bill on it.

When we moved offices about a year ago, the cobalt blue feature wall in the foyer was crying out for some type of decoration, so I used Rasterbator to produce an enlargement of the Robo Dino photo, printed out on 28 pieces of A4 paper. It was done as an experiment but for some reason it’s still up there. I suppose that dinosaurs are pretty cool, especially wind-up robo ones.

As previously mentioned on this blog, we were thinking of ideas of what to do with the extra office space in the new building. We hit on the idea of coworking, which takes the hot desking concept and adds an extra dimension by emphasising the creation of a community spirit. You can read more about the ethos on our main site.

The first non-White October cohabitant was Gareth. He’s a Flash developer whose desk is up on the first floor, embedded with all the other Octoberists. Later on, our first downstairs coworker was Paul, the head honcho of Perini a “forward-thinking content and media group”.

Since then we’ve had all sorts of folks come and work with us including, a travel writer, an IT support consultant, software developers, technology strategists, healthcare recruiters, a financial advisor, an engineer for Deutsche Bahn and a researcher from Xerox PARC. One or two come in every day, some one or twice a week and some people pop in for a couple of weeks before continuing with their travels. We’re very flexible.

Soon, we will be welcoming the “Head of Online” for an international marine conservation charity, which should be great. We are particularly keen to get more coworkers from non tech fields such as music management, graphic design, journalism and so forth. If you know anyone who fits the bill, they can register their interest online, or they can just give us a call.

We also started to host events in the space. One was the Oxford version of Open Data Day. Another was the inaugural meeting of Agile Oxford (the next meeting is on 9 March 2011). We also recently hosted a meeting of the User Experience Book Club Oxford.

Along the way, we’ve come to call our space “wocowork” for want of a better name. Robo Dino has become the de facto mascot and we have been gradually improving the space with more desks and chairs, shelves and some lockers. We’re still working on some comfy sofas and a meeting room.

It’s been an interesting year. We’ve also recently started Tweeting our 140 character thoughts under the name @wocowork, and we intend to continue blogging about our co-working journey.

How to test UTF-8 email subject lines in Symfony

(crossposted from Sebastian’s personal blog)

One of our clients needs to send emails with UTF-8 subject lines. Swift Mailer, which Symfony uses, makes this easy to do, but writing functional tests for it is another story. The problem is that, if non-ASCII characters are present in the subject line, Swift Mailer encodes them as required by RFC 2047 – for example, a string like “Transformación” turns into “=?utf-8?Q?Transformaci=C3=B3n?=”. How do we know that this is the right subject line?

There’s no immediately-available “convert this into a RFC 2047-compliant subject line” function, but we can get around this by making use of Swift_Message->toString and some choice preg functions.

First, create a dummy message containing the appropriate header. Swift_Message->toString() will contain our encoded header:

Next, we have to extract our encoded header – I’ve used preg_match() and a fairly simple regex.

Finally, now that the encoded header has been extracted, we need only wrap it in preg_quote() (because checkHeader() uses regex syntax) and test away.

Voila – now we can be confident that our test script can deal with Unicode email subjects.