The Campaign Monitor API and the PHP wrapper

The vast majority of API support requests we receive relate to the way that the PHP wrapper for the Campaign Monitor API works (available here). This thread will address some of the most common questions about the PHP wrapper.

Firstly, it should be made very clear that the PHP wrapper itself is not actually our "API", but is a PHP wrapper for our XML-based API, which is documented here using WSDL.

Developers wanting to integrate with Campaign Monitor have two broad choices when it comes to consuming our API. You can consume the API directly by composing HTTP requests and processing HTTP responses using whatever tools you like, or you can choose to use a pre-built wrapper, which abstracts some of this processing away from you (e.g. the PHP wrapper).

The PHP wrapper was originally developed and maintained by independent PHP developers and Campaign Monitor now continues to maintain it due to the amount of usage it receives - see the Google Code project page for credits.

The PHP wrapper allows developers to specify the data to send to the Campaign Monitor API using PHP types rather than XML. This means that much of the work which the wrapper does is translating PHP arrays into XML and translating XML back into PHP arrays.

This process of translation between XML and PHP arrays is what causes some of the confusion about the PHP wrapper. Let's look at an example call to the Client.GetLists API method to explore this.

Making use of the CampaignMonitor class in CMBase.php, we can write the following PHP code:

require_once('CMBase.php');
$cm = new CampaignMonitor('xxxxxxxxApiKeyxxxxxxxx');
$result = $cm->clientGetLists('xxxxxxxxClientIDxxxxxxxx')

This ultimately results in the following raw HTTP request:

POST /api/api.asmx/Client.GetLists HTTP/1.0
Host: api.createsend.com
User-Agent: CMBase URL Handler 1.5
Content-Type: application/x-www-form-urlencoded
Content-Length: 81

ApiKey=xxxxxxxxApiKeyxxxxxxxx&ClientID=xxxxxxxxClientIDxxxxxxxx

Assuming our call above was successful and returned a single list for the client specified, we would end up with the following raw HTTP response:

HTTP/1.1 200 OK
Connection: close
Date: Tue, 29 Sep 2009 03:30:07 GMT
Server: Microsoft-IIS/6.0
X-AspNet-Version: 2.0.50727
Cache-Control: private, max-age=0
Content-Type: text/xml; charset=utf-8
Content-Length: 335

<?xml version="1.0" encoding="utf-8"?>
<anyType d1p1:type="ArrayOfList" xmlns:d1p1="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://api.createsend.com/api/">
  <List xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <ListID>xxxxxxxxListIDxxxxxxxx</ListID>
    <Name>List Name</Name>
  </List>
</anyType>

This response is translated by the PHP API wrapper into the following data structure (contained in our $result variable):

Array
(
    [anyType] => Array
        (
            [list] => Array
                (
                    [ListID] => xxxxxxxxListIDxxxxxxxx
                    [Name] => List Name
                )
        )
)

Let's now compare this with the response when we find multiple lists for a client. The raw HTTP response would be:

HTTP/1.1 200 OK
Connection: close
Date: Tue, 29 Sep 2009 03:39:08 GMT
Server: Microsoft-IIS/6.0
X-AspNet-Version: 2.0.50727
Cache-Control: private, max-age=0
Content-Type: text/xml; charset=utf-8
Content-Length: 435

<?xml version="1.0" encoding="utf-8"?>
<anyType d1p1:type="ArrayOfList" xmlns:d1p1="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://api.createsend.com/api/">
  <List xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <ListID>xxxxxxxxListID1xxxxxxxx</ListID>
    <Name>List Name 1</Name>
  </List>
  <List>
    <ListID>xxxxxxxxListID2xxxxxxxx</ListID>
    <Name>List Name 2</Name>
  </List>
</anyType>

Which is translated by the PHP wrapper into the following data structure (contained in our $result variable):

Array
(
    [anyType] => Array
        (
            [list] => Array
                (
                    [0] => Array
                        (
                            [ListID] => xxxxxxxxListID1xxxxxxxx
                            [Name] => List Name 1
                        )
                    [1] => Array
                        (
                            [ListID] => xxxxxxxxListID2xxxxxxxx
                            [Name] => List Name 2
                        )
                )
        )
)

You will notice a discrepancy between the PHP data structure for the first response containing a single list and the second response containing more than one list. Whenever there is more than a single list (or any other sub-element) an additional dimension is added to the array.

The PHP API wrapper treats each XML element generically when it converts the XML to a PHP data structure, so when it sees the List element, it creates a List element in the parent array. The problem is that when we only retrieve one element with a particular name (in this case "List") at the same level in the XML tree, we don't want that to become an array itself. On the other hand, when we retrieve more than one element with the same name at the same level in the tree, we need to create an array for that type.

The reason I say that we don't want to create a sub-array for single elements, is that every single element in the XML tree would have a sub-array in that case (including simple key-value-pairs like ListID, Name etc).

So, the process of converting XML to PHP arrays is not a simple problem as described by the dilemma above. This means that when you receive the output from your call, you need to check the structure of the array.

A good example of this is shown by the sample code from Joomailer in this thread:

...
// make sure the array looks the same with one entry and with several entries
if ( !$subs['anyType']['Subscriber'][0] ) {
    $data = $subs['anyType']['Subscriber'];
    $subs['anyType']['Subscriber'] = '';
    $subs['anyType']['Subscriber'][0] = $data;
}
...

Hope this clarifies how the PHP wrapper converts XML to PHP arrays and why things are the way they are.

Joomailer Joomailer, 6 years ago

Hello jamesd,

Thank you very much for taking the time to shed some light on this matter. It took me a while to understand whats going on with the arrays retrieved by the php wrapper, why they seem to be inconsistent in their structure and how to get around it. This post really helps to understand whats happening when you gather data from campaign monitor using the php wrapper.

Do you think the workaround I posted could be implemented into the cmbase.php at some point? I think it would make sense that all arrays the wrapper delivers would have the same structure no matter how many entries exist.

Kind regards,
Pete


-----------------------------------------------------------------------------------
Joomailer.com - Campaign Monitor Integration for Joomla!
-----------------------------------------------------------------------------------
jamesd jamesd, 6 years ago

Hi Joomailer,

Glad the details above were helpful.

Joomailer :

Do you think the workaround I posted could be implemented into the cmbase.php at some point? I think it would make sense that all arrays the wrapper delivers would have the same structure no matter how many entries exist.

Your suggestion is definitely something we're considering (on a generic basis for all methods).

I can't give a definite time by which this will be implemented, as it is obviously one of many things we are currently working on, but it's definitely something we'd like to add.

nasht00, 5 years ago

Hi,
I just downloaded the wrapper and I see this was posted a while ago.
Any updates? Is the wrapper pre-built with that work-around now or should I still do it myself?

jamesd jamesd, 5 years ago

Any changes to the current PHP wrapper are clearly visible through the commit history on GitHub.

To my knowledge, there hasn't been any change made to implement the idea I discussed above. It's open source and you're welcome to fork the repository and submit pull requests if you think something could be improved. We are currently working on a new RESTful version of our API and will be releasing officially supported wrappers when we release that. Can't say exactly when that will be yet though.

Join 200,000 companies around the world that use Campaign Monitor to run email marketing campaigns that deliver results for their business.

Get started for free
1-888-533-8098