Recently I’ve had to interact with a number of SOAP web services, and I’ve come up with some techniques to help build good SOAP web service consumers.
The two most important goals when building a SOAP consumer is for the code to be maintainable and testable.
Dark Beginnings
A natural first approach is to write one class that handles everything necessary to interact with the web service:
<?php
namespace App;
use SoapClient;
class Consumer
{
protected $client;
public function __construct($wsdl, $username, $password)
{
$this->client = new SoapClient($wsdl, [
'login' => $this->username,
'password' => $this->password
]);
}
public function getBooks()
{
$response = $this->client->getBooks();
// Transform the response as appropriate...
return $response;
}
}
What we’ve written above is difficult to test because we’re constructing the SoapClient ourselves. Testing any methods on the consumer would mean making a real call to the web service.
We can do better:
<?php
namespace App;
class Consumer
{
protected $client;
public function __construct($client)
{
$this->client = $client
}
// ..
}
By having the consumer declare outright what it requires to work (an instance of a client) we’re now able to construct the consumer with mock instances of the SoapClient where we can fake real calls to the web service.
Bonus: the consumer is no longer responsible for constructing a SoapClient object with the correct credentials. It’s always a good sign to reduce a class’s responsibilities.
Writing a Test
Now that we have a way to inject a client into our consumer, let’s use Mockery to fake a web service call:
<?php
namespace Tests\Unit\WebServices\Consumer\Methods;
use App\Consumer;
use Mockery as m;
use Tests\TestCase;
class GetBooksTest extends TestCase
{
/** @test */
function it_gets_books()
{
// Mock the client to return our XML...
$client = m::mock()
->shouldReceive('getBooks')
->once()
->andReturn(simplexml_load_string($this->getXml()))
->getMock();
// Inject our mock SoapClient into the consumer
// and make the call that we're testing...
$response = (new Consumer($client))->getBooks();
// Assert that the response is what we would expect...
$this->assertEquals([
[
'title' => 'The Alchemist',
], [
'title' => 'Veronica Decides To Die',
], [
'title' => 'The Second Machine Age',
],
], $response);
}
private function getXml()
{
return <<<XML
<GetBooksResponse>
<Books>
<Book>
<Title>The Alchemist</Title>
</Book>
<Book>
<Title>Veronica Decides To Die</Title>
</Book>
<Book>
<Title>The Second Machine Age</Title>
</Book>
</Books>
<GetBooksResponse>
XML;
}
}
Since PHP’s SoapClient returns a SimpleXml object from a web service method call, that’s what we’ll have our mock object return too – except we’ll use our predefined XML snippet in order to control our testing environment.
We’re now free to test the response as we sit fit!
Avoiding a 4000 Line Long Consumer Class
As you write more and more code to consume the methods of the web service, your class will quickly grow long. This will happen especially quickly if there is any complex logic associated with formatting web service method requests and responses.
The approach I use to keep classes short is to write one class per web service method. Here’s how:
<?php
namespace App;
use Exception;
class Consumer
{
protected $client;
public function __construct($client)
{
$this->client = $client
}
public function __call($method, $parameters)
{
if (! class_exists($class = $this->getClassNameFromMethod($method))) {
throw new Exception("Method {$method} does not exist");
}
$instance = new $class($this->client);
// Delegate the handling of this method call to the appropriate class
return call_user_func_array([$instance, 'execute'], $parameters);
}
/**
* Get class name that handles execution of this method
*
* @param $method
* @return string
*/
private function getClassNameFromMethod($method)
{
return 'App\\Methods\\' . ucwords($method);
}
}
The refactored consumer class now looks for a class in the App\Methods\* namespace with the same name as the method being called. If found, it will create an instance of the class, and delegate to it.
In our example, a call to $consumer->getBooks() would internally be routed to another class called App\Methods\GetBooks .
No matter how many methods we need to consume, our consumer class will never get any bigger!
Bonus: our consumer class now conforms to the Open and Closed principle.
Here’s what our App\Methods\GetBooks class looks like:
<?php
namespace App\Methods;
class GetBooks
{
protected $client;
public function __construct($client)
{
$this->client = $client;
}
public function execute()
{
$response = $this->client->getBooks();
// Transform the response as appropriate...
return $response;
}
}
Caching Calls
A common optimization technique is to cache web service calls. With a small tweak to our consumer, we can allow our method classes to be responsible for their own caching:
<?php
namespace App;
use Exception;
use App\Cacheable;
class Consumer
{
protected $client;
public function __construct(client)
{
$this->client = $client
}
public function __call($method, $parameters)
{
if (! class_exists($class = $this->getClassNameFromMethod($method))) {
throw new Exception("Method {$method} does not exist");
}
$instance = new $class($this->client);
if ($instance instanceof Cacheable) {
return $instance->cache($parameters);
}
// Delegate the handling of this method call to the appropriate class
return call_user_func_array([$instance, 'execute'], $parameters);
}
/**
* Get class name that handles execution of this method
*
* @param $method
* @return string
*/
private function getClassNameFromMethod($method)
{
return 'App\\Methods\\' . ucwords($method);
}
}
Whenever a method class implements the App\Cacheable interface, the cache method will be called instead. If using Laravel, this could look like the below:
public function cache($parameters)
{
return app('cache')->remember('Ws.GetBooks', 10, function () use ($parameters) {
return call_user_func_array([$this, 'execute'], $parameters);
});
}
Logging Calls
Debugging will be infinitely easier if all your web service calls are logged. Since we’re injecting the SoapClient into our consumer, we can write a simple decorator to log all web service calls:
<?php
namespace App;
use SoapClient;
use Psr\Log\LoggerInterface;
class SoapClientLogger
{
protected $client;
protected $logger;
public function __construct(SoapClient $client, LoggerInterface $logger)
{
$this->client = $client;
$this->logger = $logger;
}
public function __call($method, $parameters)
{
$response = call_user_func_array([$this->client, $method], $parameters);
$this->logger->info("Request: {$method}. " . $this->client->__getLastRequest());
$this->logger->info("Response:", (array) $response);
return $response;
}
}
For the request to be logged correctly, we’ll need to enable tracing when configuring our SoapClient:
Of course, we can configure Laravel’s Container to correctly build our consumer class whenever we request it, by writing the following code in a service provider:
Dealing with SOAP web services can be a messy business. Use and improve upon the techniques written here to make the process a little bit more pleasant.
I recently needed to stress test a website to ensure that it could hold up in the real world – after some searching I stumbled upon Throng – a simple app for Mac. It allowed me to hit my website with concurrent requests for a specified period of time.
You can download the 7-day trial of Throng from this link.
The app allows you to specify a URL you’d like to hit, a time limit in seconds, and the number of concurrent users to simulate. Hit Start and it will immediately begin hitting your website – charting the average response time.
Alternatives
Load Impact – An online tool service to stress test your website. I tried it until my credits ran out – but I found that for my (simple) needs Throng was a better choice.
One of the updates I’m working on for WhichBeach is the ability for other website owners to embed WhichBeach widgets that will display up-to-date beach data. This is very much a work in progress, but I’d like to share.
<!-- Load Facebook SDK for JavaScript -->
<div id="fb-root"></div>
<script>(function(d, s, id) {
var js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) return;
js = d.createElement(s); js.id = id;
js.src = "//connect.facebook.net/en_US/sdk.js#xfbml=1";
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));</script>
<!-- Your like button code -->
<div class="fb-like"
data-href="http://www.your-domain.com/your-page.html"
data-layout="standard"
data-action="like"
data-show-faces="true">
</div>
To break it down, Facebook’s script injects a new script tag that pulls in their Javascript SDK. They’re also ensuring that the script is only injected once into the page.
I often find myself in the situation where a design calls for form inputs to transform the user’s text. It’s pretty easy to do nowadays:
input[type="text"] {
text-transform: uppercase;
}
This is what it looks like:
But – I’ve always found these types of interactions frustrating whenever I come across them. The expectation of the user is jarred when the user’s input changes unexpectedly. I decided to do some research and found a couple of posts on the subject.
Is text-transform uppercase frustrating when used in input form fields?
Here’s a discussion on UX Stack Exchange that suggests transforming the text after the field loses focus. This javascript solution does seem slightly better than a simple CSS transform – but I still find it awkward when I come across these forms.
The User Experience (UX) Of CSS Text-Transform On Form Input Fields
Ben Nadal wrote an interesting commentary about how CSS transforms on form inputs causes frustration and confusion. I agree with him here.
So what’s the best way to do it?
I say – don’t ever transform a user’s input in a form field. User’s have come to expect forms to act in a certain way, and changing that (even for the better) gives your form an extra learning curve.
If you really need to transform the user’s input somehow, I did come up with an elegant solution recently – by showing the user a summary of their form input (after or while they’re filling in the form) you can get the best of both worlds.
The user can refer to the form where the text that they have input remains unchanged. At the same time, they can see the summary where they text that they’ve input has been modified in some way.
I’d be interested to hear if anyone has any thoughts or findings on the topic in the comments!