Skip to content
This repository was archived by the owner on Jan 16, 2018. It is now read-only.

Commit eff73fb

Browse files
committed
Add emulate async client, to transform a http client into an async one
1 parent 67a45ea commit eff73fb

7 files changed

+366
-1
lines changed

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
],
1313
"require": {
1414
"php": ">=5.4",
15-
"php-http/httplug": "~1.0@dev",
15+
"php-http/httplug": "^1.0.0-alpha2@dev",
16+
"php-http/httplug-async": "~0.1@dev",
1617
"php-http/message-factory": "~0.2@dev"
1718
},
1819
"require-dev": {

spec/EmulateAsyncClientSpec.php

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
namespace spec\Http\Client\Utils;
4+
5+
use Http\Client\Exception\NetworkException;
6+
use Http\Client\HttpClient;
7+
use Http\Client\Promise;
8+
use PhpSpec\ObjectBehavior;
9+
use Psr\Http\Message\RequestInterface;
10+
use Psr\Http\Message\ResponseInterface;
11+
12+
class EmulateAsyncClientSpec extends ObjectBehavior
13+
{
14+
function it_is_initializable(HttpClient $httpClient)
15+
{
16+
$this->beAnInstanceOf('Http\Client\Utils\EmulateAsyncClient', [$httpClient]);
17+
$this->shouldImplement('Http\Client\HttpAsyncClient');
18+
}
19+
20+
function it_sends_async_request(HttpClient $httpClient, RequestInterface $request, ResponseInterface $response)
21+
{
22+
$this->beConstructedWith($httpClient);
23+
$httpClient->sendRequest($request)->shouldBeCalled()->willReturn($response);
24+
25+
$promise = $this->sendAsyncRequest($request);
26+
$promise->shouldReturnAnInstanceOf('Http\Client\Promise');
27+
$promise->shouldReturnAnInstanceOf('Http\Client\Utils\Promise\FulfilledPromise');
28+
$promise->getState()->shouldReturn(Promise::FULFILLED);
29+
$promise->getResponse()->shouldReturn($response);
30+
}
31+
32+
function it_returns_rejected_promise(HttpClient $httpClient, RequestInterface $request)
33+
{
34+
$this->beConstructedWith($httpClient);
35+
$exception = new NetworkException('', $request->getWrappedObject());
36+
$httpClient->sendRequest($request)->shouldBeCalled()->willThrow($exception);
37+
38+
$promise = $this->sendAsyncRequest($request);
39+
$promise->shouldReturnAnInstanceOf('Http\Client\Promise');
40+
$promise->shouldReturnAnInstanceOf('Http\Client\Utils\Promise\RejectedPromise');
41+
$promise->getState()->shouldReturn(Promise::REJECTED);
42+
$promise->getException()->shouldReturn($exception);
43+
}
44+
}

spec/Promise/FulfilledPromiseSpec.php

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<?php
2+
3+
namespace spec\Http\Client\Utils\Promise;
4+
5+
use Http\Client\Exception\NetworkException;
6+
use Http\Client\Promise;
7+
use PhpSpec\ObjectBehavior;
8+
use Prophecy\Argument;
9+
use Psr\Http\Message\RequestInterface;
10+
use Psr\Http\Message\ResponseInterface;
11+
12+
class FulfilledPromiseSpec extends ObjectBehavior
13+
{
14+
function it_is_initializable(ResponseInterface $response)
15+
{
16+
$this->beAnInstanceOf('Http\Client\Utils\Promise\FulfilledPromise', [$response]);
17+
$this->shouldImplement('Http\Client\Promise');
18+
}
19+
20+
function it_returns_fulfilled_promise(ResponseInterface $response)
21+
{
22+
$this->beConstructedWith($response);
23+
24+
$promise = $this->then(function (ResponseInterface $responseReceived) use($response) {
25+
if (Argument::is($responseReceived)->scoreArgument($response->getWrappedObject())) {
26+
return $response->getWrappedObject();
27+
}
28+
});
29+
30+
$promise->shouldReturnAnInstanceOf('Http\Client\Promise');
31+
$promise->shouldReturnAnInstanceOf('Http\Client\Utils\Promise\FulfilledPromise');
32+
$promise->getState()->shouldReturn(Promise::FULFILLED);
33+
$promise->getResponse()->shouldReturn($response);
34+
}
35+
36+
function it_returns_rejected_promise(RequestInterface $request, ResponseInterface $response)
37+
{
38+
$this->beConstructedWith($response);
39+
$exception = new NetworkException('', $request->getWrappedObject());
40+
41+
$promise = $this->then(function (ResponseInterface $responseReceived) use($response, $exception) {
42+
if (Argument::is($responseReceived)->scoreArgument($response->getWrappedObject())) {
43+
throw $exception;
44+
}
45+
});
46+
47+
$promise->shouldReturnAnInstanceOf('Http\Client\Promise');
48+
$promise->shouldReturnAnInstanceOf('Http\Client\Utils\Promise\RejectedPromise');
49+
$promise->getState()->shouldReturn(Promise::REJECTED);
50+
$promise->getException()->shouldReturn($exception);
51+
}
52+
53+
function it_returns_fulfilled_state(ResponseInterface $response)
54+
{
55+
$this->beConstructedWith($response);
56+
$this->getState()->shouldReturn(Promise::FULFILLED);
57+
}
58+
59+
function it_returns_response(ResponseInterface $response)
60+
{
61+
$this->beConstructedWith($response);
62+
$this->getResponse()->shouldReturn($response);
63+
}
64+
65+
function it_throws_exception_for_reason(ResponseInterface $response)
66+
{
67+
$this->beConstructedWith($response);
68+
$this->shouldThrow('\LogicException')->duringGetException();
69+
}
70+
}

spec/Promise/RejectedPromiseSpec.php

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<?php
2+
3+
namespace spec\Http\Client\Utils\Promise;
4+
5+
use Http\Client\Exception\NetworkException;
6+
use Http\Client\Exception\TransferException;
7+
use Http\Client\Exception;
8+
use Http\Client\Promise;
9+
use PhpSpec\ObjectBehavior;
10+
use Prophecy\Argument;
11+
use Psr\Http\Message\ResponseInterface;
12+
13+
class RejectedPromiseSpec extends ObjectBehavior
14+
{
15+
function it_is_initializable()
16+
{
17+
$this->beAnInstanceOf('Http\Client\Utils\Promise\RejectedPromise', [new TransferException()]);
18+
$this->shouldImplement('Http\Client\Promise');
19+
}
20+
21+
function it_returns_fulfilled_promise(ResponseInterface $response)
22+
{
23+
$exception = new TransferException();
24+
$this->beConstructedWith($exception);
25+
26+
$promise = $this->then(null, function (Exception $exceptionReceived) use($exception, $response) {
27+
if (Argument::is($exceptionReceived)->scoreArgument($exception)) {
28+
return $response->getWrappedObject();
29+
}
30+
});
31+
32+
$promise->shouldReturnAnInstanceOf('Http\Client\Promise');
33+
$promise->shouldReturnAnInstanceOf('Http\Client\Utils\Promise\FulfilledPromise');
34+
$promise->getState()->shouldReturn(Promise::FULFILLED);
35+
$promise->getResponse()->shouldReturn($response);
36+
}
37+
38+
function it_returns_rejected_promise()
39+
{
40+
$exception = new TransferException();
41+
$this->beConstructedWith($exception);
42+
43+
$promise = $this->then(null, function (Exception $exceptionReceived) use($exception) {
44+
if (Argument::is($exceptionReceived)->scoreArgument($exception)) {
45+
throw $exception;
46+
}
47+
});
48+
49+
$promise->shouldReturnAnInstanceOf('Http\Client\Promise');
50+
$promise->shouldReturnAnInstanceOf('Http\Client\Utils\Promise\RejectedPromise');
51+
$promise->getState()->shouldReturn(Promise::REJECTED);
52+
$promise->getException()->shouldReturn($exception);
53+
}
54+
55+
function it_returns_rejected_state()
56+
{
57+
$exception = new TransferException();
58+
$this->beConstructedWith($exception);
59+
$this->getState()->shouldReturn(Promise::REJECTED);
60+
}
61+
62+
function it_returns_exception()
63+
{
64+
$exception = new TransferException();
65+
$this->beConstructedWith($exception);
66+
$this->getException()->shouldReturn($exception);
67+
}
68+
69+
function it_throws_exception_for_response()
70+
{
71+
$exception = new TransferException();
72+
$this->beConstructedWith($exception);
73+
$this->shouldThrow('\LogicException')->duringGetResponse();
74+
}
75+
}

src/EmulateAsyncClient.php

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
namespace Http\Client\Utils;
4+
5+
use Http\Client\HttpAsyncClient;
6+
use Http\Client\HttpClient;
7+
use Http\Client\Exception;
8+
use Http\Client\Utils\Promise\FulfilledPromise;
9+
use Http\Client\Utils\Promise\RejectedPromise;
10+
use Psr\Http\Message\RequestInterface;
11+
12+
/**
13+
* Emulate an HttpAsyncClient on top of a HttpClient.
14+
*
15+
* This client sends request synchronously but then uses the Promise system
16+
* to allow a consistent codebase even with non-asynchronous capable clients.
17+
*
18+
* @author Joel Wurtz <[email protected]>
19+
*/
20+
class EmulateAsyncClient implements HttpAsyncClient
21+
{
22+
/** @var HttpClient Underlying HTTP Client */
23+
private $httpClient;
24+
25+
/**
26+
* @param HttpClient $httpClient Underlying HTTP Client
27+
*/
28+
public function __construct(HttpClient $httpClient)
29+
{
30+
$this->httpClient = $httpClient;
31+
}
32+
33+
/**
34+
* {@inheritdoc}
35+
*/
36+
public function sendAsyncRequest(RequestInterface $request)
37+
{
38+
try {
39+
return new FulfilledPromise($this->httpClient->sendRequest($request));
40+
} catch (Exception $e) {
41+
return new RejectedPromise($e);
42+
}
43+
}
44+
}

src/Promise/FulfilledPromise.php

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<?php
2+
3+
namespace Http\Client\Utils\Promise;
4+
5+
use Http\Client\Exception;
6+
use Http\Client\Promise;
7+
use Psr\Http\Message\ResponseInterface;
8+
9+
/**
10+
* A promise already fulfilled
11+
*
12+
* @author Joel Wurtz <[email protected]>
13+
*/
14+
class FulfilledPromise implements Promise
15+
{
16+
/** @var ResponseInterface */
17+
private $response;
18+
19+
public function __construct(ResponseInterface $response)
20+
{
21+
$this->response = $response;
22+
}
23+
24+
/**
25+
* {@inheritdoc}
26+
*/
27+
public function then(callable $onFulfilled = null, callable $onRejected = null)
28+
{
29+
try {
30+
return new FulfilledPromise($onFulfilled($this->response));
31+
} catch (Exception $e) {
32+
return new RejectedPromise($e);
33+
}
34+
}
35+
36+
/**
37+
* {@inheritdoc}
38+
*/
39+
public function getState()
40+
{
41+
return Promise::FULFILLED;
42+
}
43+
44+
/**
45+
* {@inheritdoc}
46+
*/
47+
public function getResponse()
48+
{
49+
return $this->response;
50+
}
51+
52+
/**
53+
* {@inheritdoc}
54+
*/
55+
public function getException()
56+
{
57+
throw new \LogicException("Fulfilled promise, response not available");
58+
}
59+
60+
/**
61+
* {@inheritdoc}
62+
*/
63+
public function wait()
64+
{
65+
}
66+
}

src/Promise/RejectedPromise.php

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php
2+
3+
namespace Http\Client\Utils\Promise;
4+
5+
use Http\Client\Exception;
6+
use Http\Client\Promise;
7+
8+
/**
9+
* A rejected promise
10+
*
11+
* @author Joel Wurtz <[email protected]>
12+
*/
13+
class RejectedPromise implements Promise
14+
{
15+
/** @var Exception */
16+
private $exception;
17+
18+
public function __construct(Exception $exception)
19+
{
20+
$this->exception = $exception;
21+
}
22+
23+
/**
24+
* {@inheritdoc}
25+
*/
26+
public function then(callable $onFulfilled = null, callable $onRejected = null)
27+
{
28+
try {
29+
return new FulfilledPromise($onRejected($this->exception));
30+
} catch (Exception $e) {
31+
return new RejectedPromise($e);
32+
}
33+
}
34+
35+
/**
36+
* {@inheritdoc}
37+
*/
38+
public function getState()
39+
{
40+
return Promise::REJECTED;
41+
}
42+
43+
/**
44+
* {@inheritdoc}
45+
*/
46+
public function getResponse()
47+
{
48+
throw new \LogicException("Promise is rejected, no response available");
49+
}
50+
51+
/**
52+
* {@inheritdoc}
53+
*/
54+
public function getException()
55+
{
56+
return $this->exception;
57+
}
58+
59+
/**
60+
* {@inheritdoc}
61+
*/
62+
public function wait()
63+
{
64+
}
65+
}

0 commit comments

Comments
 (0)