How to leverage the power of Symfony Serializer to elegantly talk to REST APIs
How It Started
Lately, I needed to upgrade to ZohoCRM API v2.
– “Cool, their new API has a PHP SDK, that’s going to be implemented easy!”
Boy, was I wrong.
Those who haven’t tried this before – be warned – you will wander in the woods of abstract documentation, back and forth between their developer portal, Github source of their PHP SDK, trying to find some examples.
I lost two days banging my head on it and in the end decided to write a small API client as a wrapper for their REST API instead.
My usual routine when writing an API client would start with fetching a response with Guzzle,
json_decode-ing a response, a series of
if/else/switch-es and manual mapping of associative arrays to my entities.
PUT-ing data back, I’d have a similar, ugly block of code that would dump my entity into an associative array.
I’ve always looked at those pieces of my code as especially fragile and error prone. Let me give you a hint:
What’s Wrong With This?
- IDEs can’t really help you when working with associative arrays, since there’s no type information and it’s not really the OOP way
- mapping logic from API response and back to API request payload is duplicated (
That got me thinking – There must be a better way of doing this..
With Symfony Serializer
As you might have guessed, Symfony Serializer is a standalone component that can be installed via Composer:
composer require symfony/serializer
While PropertyAccess component isn’t necessarily required, if you notice that some of the property/method mappings while (de)serializing don’t work, try installing it too and let it do it’s magic:
composer require symfony/property-access
So, I rewrote my code to something like this:
- API responses and request payloads nicely fit in objects (
ProjectCollection), not associative arrays.
- Object properties are type hinted to their types in JSON, so there’s no doubt what’s their type and no incidental type-juggling.
If the type on the property doesn’t match the type in JSON, Serializer will throw an exception.
- Have you noticed I didn’t have to tell the Serializer to map to the
ProjectCollection::resultto an array of
Projectinstances, by reading the
- Mapping is done by
@SerializedNameannotation and it’s not duplicated
- On the downside, this method requires a separate class for every type of embedded object.
To Wrap Up
Now I felt that my API client code was more maintainable.
Exceptions were thrown early on, mapping was explicit and deduplicated.
Even though I ended up with more files and lines of code, I felt it was worth it.
Oh, and a bonus – wrapping it in objects, instead of associative arrays, made it easier for me to test them.