Created
March 25, 2012 08:29
-
-
Save beberlei/2192383 to your computer and use it in GitHub Desktop.
pecl/http API suggestions
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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