Skip to content

Instantly share code, notes, and snippets.

@beberlei
Created March 25, 2012 08:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save beberlei/2192383 to your computer and use it in GitHub Desktop.
Save beberlei/2192383 to your computer and use it in GitHub Desktop.
pecl/http API suggestions
<?php
// Case 1: Current API - Untestable without direct HTTP Connection to Amazon
class MyAmazonService
{
public function getItems($searchTerm)
{
$request = (new http\Request\Factory)->createRequest();
$response = $reqeust->setUrl("http://www.amazon.de/search?terms=" . $searchTerm)->send();
$xml = $response->getBody();
// ...
// return $items;
}
}
// Case 2: Current API - Mocks can be written "by hand"
//
// Some dynamics maybe gained in this case by overwriting http\Request\Factory#createRequest()
// and build a mock framework for http request factories.
class MyAmazonService
{
private $httpFactory;
public function __construct(http\Request\Factory $factory)
{
$this->httpFactory = $factory;
}
public function getItems($searchTerm)
{
$request = $this->httpFactory->createRequest();
$response = $reqeust->setUrl("http://www.amazon.de/search?terms=" . $searchTerm)
->send();
$xml = $response->getBody();
// ...
// return $items;
}
}
class SpecialRequestFactoryMock1 extends http\Request\Factory {
{
// mock Request, but no way to inject responses
protected $requestClass = "SpecialRequestMock";
}
class SpecialRequestMock1 extends http\Request
{
public function send()
{
// build response "in code", but no way to inject respones
$response = new Response();
$response->setBody("my required response for test1");
return $response;
}
}
class MyAmazonServiceTest
{
public function testGetItemsSpecialRequest1()
{
$mockFactory = new SpecialRequestFactoryMock1();
$service = new MyAmazonService($mockFactory);
$items = $service->getItems("awesome things");
}
public funciton testGetItemsSpecialREquest2()
{
$mockFactory = new SpecialRequestFactoryMock2();
///...
}
}
/**
Case 3: Remove Factory and instead introduce Client
Goal:
* Avoid userland wrappers of pecl/http through carefully object-oriented crafted API.
* Avoid users having to write boilerplate to "mock" the http client functionality.
Big picture
* Seperate building request/response from actually sending it
* Achieve more consistent API by moving towards single responsibility of objects
* Introduce interfaces and build interaction between objects such that dependency injection is possible (not necessary).
Small steps
* Refactor http\Request\Factory to http\Request\Client (interface) and http\Request\CurlClient
Have a method http_client_create() that could create a default CurlClient, for very easy usage.
* Remove http\Request#send()
Not the request sends itself, a HTTP Client sends a request
* Remove http\Request#getResponse*()
Since a request can be fired more then once, it has no 1:1 relationship to a response.
Additionally with Client#send($request) returning a response it doesn't make sense to offer
the same functionality in two different ways. Just having one way is more consistent and easy
to learn.
* Remove http\Request#getHistory()
Not a request has a history, a client has the history
* Make http\Request a "value" object, no reference to client.
You can create request objects in your code with "new http\Request"
as they only contain values and no "client/sending" logic.
*/
namespace http;
/**
* Could be PHP-userland implemented if someone wants to, but main goal is being interface for other
* "client" types beside curl, and for mocking in tests.
*/
interface Client
{
/**
* @return http\Client\Pool interface
*/
public function createPool();
/**
* @return http\Client\DataShare interface
*/
public function createDataShare();
/**
* @param http\Request
* @return http\Response
*/
public function send(http\Request $request);
/**
* @return http\Client\History
*/
public function getHistory();
public function clearHistory();
public function setOptions(array $options);
public function getOptions();
}
namespace http\Request;
class CurlClient implements http\Request\Client
{
//...
}
class MyAmazonServiceTest extends PHPUnit_Framework_TestCase
{
public function testGetItemsSpecialRequest()
{
$myTestResponse = new http\Response;
$myTestResponse->setResponseBody("amazon xml here!");
$client = $this->getMock('http\Client');
$client->expects($this->once())
->method('send')
->will($this->returnValue($myTestResponse));
$service = new MyAmazonService($client);
$items = $service->getItems("awesome things");
}
}
class MyAmazonService
{
private $httpClient;
public function __construct(http\Client $client)
{
$this->httpClient = $client;
}
public function getItems($searchTerm)
{
$request = new http\Request();
$request->setUrl("http://www.amazon.de/search?terms=" . $searchTerm);
$request->setHeaders(array(....));
$response = $this->httpClient->send($request);
$xml = $response->getBody();
// ...
// return $items;
}
}
Simple use-cases:
// 1. Full code example
$client = new http\Client\CurlClient();
$request = new http\Request();
$request->setUrl("http://php.net");
$response = $client->send($request);
echo $response->getBody();
// 2. Have Request implement a sane default constructor
// and client with a functional factory
$client = http_client_create(); // new http\Client\CurlClient();
$response = $client->send(new http\Request("GET", "http://php.net", $postFields = array(), $headers = array(), $cookies = array()));
// 3. Simplifies to
$response = http_client_create()->send(new http\Request("GET", "http://php.net"));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment