Building the Laravel Campaign Monitor bundle

One of the projects I'd been working on called for Campaign Monitor integration. I'd used the API in various other frameworks, normally only implementing the API requests I required as I go along as some frameworks aren't particularly good at being extended with 3rd party libraries. (Not mentioning any names - CodeIgniter!)

Luckily for me I discovered Laravel which is designed to be extendable. It also has an ever growing collection of bundles which allow users to easily share and install extensions from other users. As Campaign Monitor is a large international company I guessed other Laravel developers may want to use their API, so I spent an extra bit of time creating a neatly formed, easy to update bundle.

Getting to grips with the Campaign Monitor API PHP wrapper

Campaign Monitor very kindly provide various wrappers for their API but I was only interested in the PHP one. Normally I would consider starting from scratch and building a bundle/module from the ground up but as the wrapper from Campaign Monitor covers all the API requests possible and appears to be written in a decent enough fashion I decided to keep it in it's original state as it makes it easier to update if/when Campaign Monitor update it. Another reason for this is that I presume that it's well tested and the less work I do on it, the less chances I have of breaking it!

If you've looked at the PHP wrapper you'll see that Campaign Monitor have broken it down into 7 classes that deal with different sections of their system. The classes for campaigns, clients, lists, segments, subscribers and templates are all quite self-explanatory. The general just deals with everything else that doesn't fit into the previous 6 classes. The wrapper also contains a directory called class which is home to 'helper' utility classes that are used.

Setting the structure of the bundle

First things first - first things last would be stupid! lol! I created a directory in my application's bundle directory and gave it the incredibly imaginative name of campaignmonitor. Laravel is pretty flexible about where you stick any externally sourced libraries so I plonked them in a directory called vendor - I'm sure this is a habit I picked up from using Kohana PHP or CodeIgniter a few versions ago but I really can't remember! The only things left to do then was create an empty CampaignMonitor class in campaignmonitor.php and add a mapping to it in the start.php file so Laravel knows where to look for it. Both of these are in the root of the campaignmonitor bundle directory.

Creating the wrapper class

The first obverse thing to do was to split they Campaign Monitor API key into a separate file as each install would need to use a different key. I created a directory called config with a sample config file called campaignmonitor-sample.php in it for the API key to go in. (The idea is that you copy the template file campaignmonitor-sample.php to your applications config directory and rename it campaignmonitor.php.)

I wanted to be able to access all of the Campaign Monitor methods, across all the 7 classes, via a single point of entry that would be simple and flexible enough so if/when Campaign Monitor ever extended or altered their API classes it wouldn't require me to update much of my code - it would just be a case of replacing their files.

Revision #1

My first concept was to load a Campaign Monitor class like this - where CLASS_NAME would match one of the classes such as clients, lists or general:

CampaignMonitor::factory('CLASS_NAME');

This would use a single method, factory(), in my CampaignMonitor class to load the API key, include the correct Campaign Monitor file and return the initiated classes so that all the methods from with the classes could then be accesses like so:

// Example 1
$general = CampaignMonitor::factory('general');
$general->get_timezones();

// Example 2
CampaignMonitor::factory('clients', $client_id)->get_lists();

The issue I had with the way of doing it was mainly down to the second example above, as to access some libraries you needs to provide the id/key for the item - such as a client id for a client or a list id for a list.

This method of retrieving the data worked perfectly correctly but it just didn't seem to be as elegant as I'd have liked it to be so I had another think!

Revision #2

I decided to drop the factory method and make use of the __callStatic magic method instead. It would it use much of the same logic as the factory() method but would allow for clearer, more semantic coding when using the bundle. As you can see from the examples below you now choose the Campaign Monitor class with the method name rather than the first argument. You now only need pass in 1 argument if the Campaign Monitor classes requires a id.

// Example 3
$general = CampaignMonitor::general();
$general->get_timezones();

// Example 4
CampaignMonitor::clients($client_id)->get_lists();

I did start to try and implement a way of using the singulars for the method names, rather than plurals, as these would have been more semantically correct but I ran into the age old problem of list() being a native PHP function, and it was getting rather late and I wanted to go to bed so this was short lived!

And finally...

Now the bundle was all complete and committed into my GitHub account, all that was left to do was login to the Laravel Bundles site via GitHub, fill in a quick form and submit the bundle. Now it has its own special little page and can be installed by any Laravel user with one simple command...

$ php artisan bundle:install campaignmonitor

The way bundles are handled through Laravel are probably the best and easier way I've seen by a PHP framework. What I found was from the whole process is that, yet again, Laravel is kick-ass and does what I've been longing for a framework to do for ages - easy bundle creation and sharing in a way that just makes sense.

EXTRA NOTE: By the time I actually finished writing this blog post my bundle has been released for over a month and has had 86 installs at the time of publishing - I must of done something right!