RantFever 4

I pontificate but not in the pejorative sense of the word.

The Over-complexity of php-ews

Posted 24 April 2015
Written by Abinadi Ayerdis
Category PHP

The Situation

Let's say you want to write a simple ticketing web application. It won't have a lot of features, but hey, you don't need all the bells and whistles. One critical feature that it will need to support, however, is the ability to accept emails and input them into the system as a new ticket. Oh yeah, and imap and pop3 are not an option because IT refuses to do anything helpful... ever. So you are relagated to dealing with Exchange Web Services. This was my situation.

Initial Response

I had never dealt with EWS before, let alone with interacting with it via PHP. But I was hopeful. "No problem," I thought to myself. "After all, google knows everything and has been helpful in the past." 

So I go to google...

Google introduced me to a library called php-ews that allowed me (surprise, surprise) to interact with EWS via PHP. At this point, I thought: "Brilliant. Thanks google." And really, yes, let us all be thankful that this exists, because without it, I don't think I could get that feature I wanted. The first thing I note is that there is no auto loader. My app is written with Laravel 5 and I was making this into a console command so that I could run it in a cronjob. But no auto loader means lots of require_once() calls. No one likes that. No one.

Luckily, some good-hearted person on GitHub has adapted jamesiarmes' code to PSR-0 and made it available on packagist. So installing is as simple as adding

"php-ews/php-ews": "dev-master"

 to the require section of my composer.json file and then running a composer update. Now those classes will be auto-loaded. Thanks, adamelso.

php-ews

php-ews is something on the order of 300+ classes. You read that right, it is over three hundred classes... just to check an email address. Ridiculous. 

The problem lies with EWS, I think. It makes use of SOAP (Simple Object Access Protocol) to interact with it. The only problem is that SOAP sucks. To quote a guy from one of those links:

The acronym "SOAP" is a lie. It is not Simple, it is not Object-oriented, it defines no Access rules. It is, arguably, a Protocol. It is Don Box's worst spec ever, and that's quite a feat, as he's the man who perpetrated "COM".

SOAP uses XML to pass data back and forth. So php-ews has to take objects and turn them into XML. Here is an example from the site:

<?php
// NOTE: mind the version of exchange you are connecting to...
$ews = new ExchangeWebServices($host, $username, $password, ExchangeWebServices::VERSION_2007_SP1);

$request = new EWSType_FindItemType();

$request->ItemShape = new EWSType_ItemResponseShapeType();
$request->ItemShape->BaseShape = EWSType_DefaultShapeNamesType::DEFAULT_PROPERTIES;

$request->Traversal = EWSType_ItemQueryTraversalType::SHALLOW;

$request->ParentFolderIds = new EWSType_NonEmptyArrayOfBaseFolderIdsType();
$request->ParentFolderIds->DistinguishedFolderId = new EWSType_DistinguishedFolderIdType();
$request->ParentFolderIds->DistinguishedFolderId->Id = EWSType_DistinguishedFolderIdNameType::INBOX;

// sort order
$request->SortOrder = new EWSType_NonEmptyArrayOfFieldOrdersType();
$request->SortOrder->FieldOrder = array();
$order = new EWSType_FieldOrderType();
// sorts mails so that oldest appear first
// more field uri definitions can be found from types.xsd (look for UnindexedFieldURIType)
$order->FieldURI->FieldURI = 'item:DateTimeReceived'; 
$order->Order = 'Ascending'; 
$request->SortOrder->FieldOrder[] = $order;

$response = $ews->FindItem($request);
echo '<pre>'.print_r($response, true).'</pre>';

All that just to get a list of emails. And just so we're clear, it does not include the body of those emails. Arrggg! So you need to build up another xml request to get the content of the emails in question. Why? Just, why? Also, isn't it just ugly? And what the heck is a NonEmptyArrayOfWhatever? What does that even mean? 

Also, the $response is a plain old php object with a __clone() method. So how can you know what properties it has or what methods you can call on it? Essentially it is an array that is obfuscated in a stdObject. I turned it into an array using a hack. Then I could discover what properties it had by running a var_dump(array_keys($response)); Then doing it again for the result: var_dump(array_keys($response['ResponseMessages'])); and then again: var_dump(array_keys($response['ResponseMessage']['FindItemResponseMessage'])); and so on until I got to an actual email. (For the curious, it was something like this: $response['ResponseMessages']['FindItemResponseMessage']['RootFolder']['Items']['Message'].) And I was copying down the structure every step of the way. Talk about tedious.

There is a lot of bad to say, but I'll only add one more thing. It is possible to get a property called InReplyTo. It is great because if you have that property, you know that the email is a reply. It contains an itentifier for the email it is in reply to. Great, right? No. Not great because the identifier does not match the ItemId of the email. So you have an identifier of an email that you can't use because it is not even the correct format for an ItemId. What a useless feature. There is probably a way to use it, but I couldn't find it. 

Super frustrating.

In the end

At the end of the day, I made it work. It was hundreds of lines of code that would have been much shorter had I been able to use IMAP or POP3. The only up side is that now I have an application that will check an email box for new tickets automatically. 

Comments

Gravatar

24 April 2015

The hack to turn the object into an array: $response = json_decode(json_encode($response), true);

Posting comments after three months has been disabled.