Symfony / Web Services - part 3.1: Consuming, RequestHandler 11/03/2015
Deprecated: This series has been re-written - see The Ultimate Developer Guide to Symfony
This is the fifth article of the series on managing Web Services in a Symfony environment. Have a look at the four first ones:
Our purpose in this third section is to create an application that consumes the previously created web services.
But for now, we'll just bootstrap it, and start to create a RequestHandler!
Installation
We will follow the exact same steps as in the chapter 2.1:
- Installing the standard edition
- Twitching for tests
Instead of calling our application ws
, we'll call it cs
(like Consuming Service).
Making remote calls with Guzzle
Is the application boostrapped yet? If it is, then we can continue.
In order to consume web services, we need to be able to make remote requests. PHP
provides some native functions for this (file_get_contents
, stream_socket_client
,
fopen
, etc) and we can find many libraries as well (Buzz,
HTTP Full, React, etc).
For this series, we'll use Guzzle:
composer require guzzlehttp/guzzle:~5.0
Let's commit it for now:
git add -A
git commit -m 'Installed Guzzle'
Creating a Request Handler
Sometimes we need to decouple our application from the third party libraries it depends on.
For example let's say that we were using Guzzle 4, but we'd like to use Amazon Web Service (AWS) S3 in our project. The issue? It's version 2 depends on Guzzle 3 and its version 3 depends on Guzzle 5. We now need to upgrade our usage of Guzzle everywhere in our application.
To minimize this, we can centralize the usage of Guzzle in one single file. In order to be able to do so, we'll create a RequestHandler:
<?php
// File: src/AppBundle/RequestHandler/RequestHandler.php
namespace AppBundle\RequestHandler;
interface RequestHandler
{
// @return Response
public function handle(Request $request);
}
In our application we can rely on this interface: we own it and it has few chances to change. We'll now create an object that describes the request to send:
./bin/phpspec describe 'AppBundle\RequestHandler\Request'
A minimalistic raw HTTP request looks like the following:
GET /api/v1/profiles HTTP/1.1
Since we don't really care about the protocol's version we can define the constructor with two arguments:
// File: spec/AppBundle/RequestHandler/RequestSpec.php
function it_has_a_verb_and_an_uri()
{
$this->beConstructedWith('GET', '/api/v1/profiles');
$this->getVerb()->shouldBe('GET');
$this->getUri()->shouldBe('/api/v1/profiles');
}
Running the specifications will bootstrap the class for us:
./bin/phpspec run
We can now make the test pass by writing the code:
<?php
// File: src/AppBundle/RequestHandler/Request.php
namespace AppBundle\RequestHandler;
class Request
{
private $verb;
private $uri;
public function __construct($verb, $uri)
{
$this->verb = $verb;
$this->uri = $uri;
}
public function getVerb()
{
return $this->verb;
}
public function getUri()
{
return $this->uri;
}
}
Let's check if it's enough for now:
./bin/phpspec run
All green, we can commit:
git add -A
git commit -m 'Created Request'
Request headers
A request usually has headers:
// File: spec/AppBundle/RequestHandler/RequestSpec.php
function it_can_have_headers()
{
$this->beConstructedWith('GET', '/api/v1/profiles');
$this->setHeader('Content-Type', 'application/json');
$this->getHeaders()->shouldBe(array('Content-Type' => 'application/json'));
}
Let's boostrap them:
./bin/phpspec run
And complete the code:
// File: src/AppBundle/RequestHandler/Request.php
private $headers = array();
public function setHeader($name, $value)
{
$this->headers[$name] = $value;
}
public function getHeaders()
{
return $this->headers;
}
This makes the test pass:
./bin/phpspec run
That's worth a commit:
git add -A
git commit -m 'Added headers to Request'
Request body
The last addition to our request will be the possibility to add a body:
// File: spec/AppBundle/RequestHandler/RequestSpec.php
function it_can_have_a_body()
{
$this->beConstructedWith('GET', '/api/v1/profiles');
$this->setBody('{"wound":"just a flesh one"}');
$this->getBody()->shouldBe('{"wound":"just a flesh one"}');
}
As usual we bootstrap it:
./bin/phpspec run
And then we complete it:
// File: src/AppBundle/RequestHandler/Request.php
private $body;
public function setBody($body)
{
$this->body = $body;
}
public function getBody()
{
return $this->body;
}
Let's make our console green:
./bin/phpspec run
Let's make our console grin:
git add -A
git commit -m 'Added body to Request'
Creating a Response
RequestHandler
should return a Response
object:
./bin/phpspec describe 'AppBundle\RequestHandler\Response'
A minimalistic raw HTTP response looks like the following:
HTTP/1.1 204 NO CONTENT
Since we don't care about both the protocol's version and the reason, we can define the constructor with a single argument:
// File: spec/AppBundle/RequestHandler/ResponseSpec.php
function it_has_a_status_code()
{
$this->beConstructedWith(204);
$this->getStatusCode()->shouldBe(204);
}
Running the specifications will bootstrap the class for us:
./bin/phpspec run
We can now make the test pass by writing the code:
<?php
// File: src/AppBundle/RequestHandler/Response.php
namespace AppBundle\RequestHandler;
class Response
{
private $statusCode;
public function __construct($statusCode)
{
$this->statusCode = $statusCode;
}
public function getStatusCode()
{
return $this->statusCode;
}
}
Let's check if it's enough for now:
./bin/phpspec run
All green, we can commit:
git add -A
git commit -m 'Created Response'
Response headers
A response can also have headers:
// File: spec/AppBundle/RequestHandler/ResponseSpec.php
function it_can_have_headers()
{
$this->beConstructedWith(204);
$this->setHeaders(array('Content-Type' => 'application/json'));
$this->getHeader('Content-Type')->shouldBe('application/json');
}
Let's boostrap them:
./bin/phpspec run
And complete the code:
// File: src/AppBundle/RequestHandler/Response.php
private $headers = array();
public function setHeaders(array $headers)
{
$this->headers = $headers;
}
public function getHeader($name)
{
return (isset($this->headers[$name]) ? $this->headers[$name] : null);
}
This makes the test pass:
./bin/phpspec run
That's worth a commit:
git add -A
git commit -m 'Added headers to Response'
Response body
Last but not least, the response's body:
// File: spec/AppBundle/RequestHandler/ResponseSpec.php
function it_can_have_a_body()
{
$this->beConstructedWith(200);
$this->setBody('{"wound":"just a flesh one"}');
$this->getBody()->shouldBe('{"wound":"just a flesh one"}');
}
As usual we bootstrap it:
./bin/phpspec run
And then we complete it:
// File: src/AppBundle/RequestHandler/Response.php
private $body;
public function setBody($body)
{
$this->body = $body;
}
public function getBody()
{
return $this->body;
}
Let's make our console green:
./bin/phpspec run
Let's make our console grin:
git add -A
git commit -m 'Added body to Response'
Conclusion
We've bootstrapped an application, and created a RequestHandler which will help us to avoid coupling with Guzzle. In the next article, we'll talk about middleware and start to create some RequestHandler implementations (yes, more than one!).
Reference: see the phpspec reference article