diff --git a/composer.json b/composer.json index 8761c8d..3864297 100644 --- a/composer.json +++ b/composer.json @@ -17,14 +17,22 @@ "require": { "php": ">=5.5.0", "php-http/httplug": "dev-master", + "php-http/httplug-async": "^0.1", "guzzlehttp/guzzle": "^6.0" }, "require-dev": { "ext-curl": "*", - "php-http/adapter-integration-tests": "^0.2@dev" + "php-http/adapter-integration-tests": "dev-feature/async-test" }, + "repositories": [ + { + "type": "vcs", + "url": "https://github.com/joelwurtz/adapter-integration-tests" + } + ], "provide": { - "php-http/client-implementation": "1.0" + "php-http/client-implementation": "1.0", + "php-http/async-client-implementation": "1.0" }, "autoload": { "psr-4": { diff --git a/src/Guzzle6HttpAdapter.php b/src/Guzzle6HttpAdapter.php index 7786441..fa0b1ac 100644 --- a/src/Guzzle6HttpAdapter.php +++ b/src/Guzzle6HttpAdapter.php @@ -13,18 +13,17 @@ use GuzzleHttp\Client; use GuzzleHttp\ClientInterface; use GuzzleHttp\Exception as GuzzleExceptions; +use GuzzleHttp\Promise\PromiseInterface; use Http\Client\Exception; -use Http\Client\Exception\HttpException; -use Http\Client\Exception\NetworkException; use Http\Client\Exception\RequestException; -use Http\Client\Exception\TransferException; +use Http\Client\HttpAsyncClient; use Http\Client\HttpClient; use Psr\Http\Message\RequestInterface; /** * @author David de Boer */ -class Guzzle6HttpAdapter implements HttpClient +class Guzzle6HttpAdapter implements HttpClient, HttpAsyncClient { /** * @var ClientInterface @@ -44,42 +43,21 @@ public function __construct(ClientInterface $client = null) */ public function sendRequest(RequestInterface $request) { - try { - return $this->client->send($request); - } catch (GuzzleExceptions\SeekException $e) { - throw new RequestException($e->getMessage(), $request, $e); - } catch (GuzzleExceptions\GuzzleException $e) { - throw $this->handleException($e); + $promise = $this->sendAsyncRequest($request); + $promise->wait(); + + if ($promise->getState() == PromiseInterface::REJECTED) { + throw $promise->getException(); } + + return $promise->getResponse(); } /** - * Converts a Guzzle exception into an Httplug exception. - * - * @param GuzzleExceptions\GuzzleException $exception - * - * @return Exception + * {@inheritdoc} */ - private function handleException(GuzzleExceptions\GuzzleException $exception) + public function sendAsyncRequest(RequestInterface $request) { - if ($exception instanceof GuzzleExceptions\ConnectException) { - return new NetworkException($exception->getMessage(), $exception->getRequest(), $exception); - } - - if ($exception instanceof GuzzleExceptions\RequestException) { - // Make sure we have a response for the HttpException - if ($exception->hasResponse()) { - return new HttpException( - $exception->getMessage(), - $exception->getRequest(), - $exception->getResponse(), - $exception - ); - } - - return new RequestException($exception->getMessage(), $exception->getRequest(), $exception); - } - - return new TransferException($exception->getMessage(), 0, $exception); + return new Guzzle6Promise($this->client->sendAsync($request), $request); } } diff --git a/src/Guzzle6Promise.php b/src/Guzzle6Promise.php new file mode 100644 index 0000000..4433fe5 --- /dev/null +++ b/src/Guzzle6Promise.php @@ -0,0 +1,151 @@ +request = $request; + $this->state = self::PENDING; + $this->promise = $promise->then(function ($response) { + $this->response = $response; + $this->state = self::FULFILLED; + + return $response; + }, function ($reason) use ($request) { + if ($reason instanceof HttplugException) { + $this->state = self::REJECTED; + $this->exception = $reason; + + throw $this->exception; + } + + if (!($reason instanceof GuzzleExceptions\GuzzleException)) { + throw new \RuntimeException("Invalid reason"); + } + + $this->state = self::REJECTED; + $this->exception = $this->handleException($reason, $request); + + throw $this->exception; + }); + } + + /** + * {@inheritdoc} + */ + public function then(callable $onFulfilled = null, callable $onRejected = null) + { + return new static($this->promise->then($onFulfilled, $onRejected), $this->request); + } + + /** + * {@inheritdoc} + */ + public function getState() + { + return $this->state; + } + + + /** + * {@inheritdoc} + */ + public function getResponse() + { + if (self::FULFILLED !== $this->state) { + throw new \LogicException("Response not available for the current state"); + } + + return $this->response; + } + + /** + * {@inheritdoc} + */ + public function getException() + { + if (self::REJECTED !== $this->state) { + throw new \LogicException("Error not available for the current state"); + } + + return $this->exception; + } + + /** + * {@inheritdoc} + */ + public function wait() + { + $this->promise->wait(false); + } + + /** + * Converts a Guzzle exception into an Httplug exception. + * + * @param GuzzleExceptions\GuzzleException $exception + * @param RequestInterface $request + * + * @return HttplugException + */ + private function handleException(GuzzleExceptions\GuzzleException $exception, RequestInterface $request) + { + if ($exception instanceof GuzzleExceptions\SeekException) { + return new HttplugException\RequestException($exception->getMessage(), $request, $exception); + } + + if ($exception instanceof GuzzleExceptions\ConnectException) { + return new HttplugException\NetworkException($exception->getMessage(), $exception->getRequest(), $exception); + } + + if ($exception instanceof GuzzleExceptions\RequestException) { + // Make sure we have a response for the HttpException + if ($exception->hasResponse()) { + return new HttplugException\HttpException( + $exception->getMessage(), + $exception->getRequest(), + $exception->getResponse(), + $exception + ); + } + + return new HttplugException\RequestException($exception->getMessage(), $exception->getRequest(), $exception); + } + + return new HttplugException\TransferException($exception->getMessage(), 0, $exception); + } +} + \ No newline at end of file diff --git a/tests/Guzzle6CurlHttpAsyncAdapterTest.php b/tests/Guzzle6CurlHttpAsyncAdapterTest.php new file mode 100644 index 0000000..defb4d5 --- /dev/null +++ b/tests/Guzzle6CurlHttpAsyncAdapterTest.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please read the LICENSE + * file that was distributed with this source code. + */ + +namespace Http\Adapter\Tests; + +use GuzzleHttp\Handler\CurlHandler; + +/** + * @requires PHP 5.5 + * + * @author GeLo + */ +class Guzzle6CurlHttpAsyncAdapterTest extends Guzzle6HttpAsyncAdapterTest +{ + /** + * {@inheritdoc} + */ + protected function createHandler() + { + return new CurlHandler(); + } +} diff --git a/tests/Guzzle6HttpAdapterTest.php b/tests/Guzzle6HttpAdapterTest.php index 156bc74..6193fee 100644 --- a/tests/Guzzle6HttpAdapterTest.php +++ b/tests/Guzzle6HttpAdapterTest.php @@ -13,11 +13,12 @@ use GuzzleHttp\Client; use Http\Adapter\Guzzle6HttpAdapter; +use Http\Client\Tests\HttpClientTest; /** * @author GeLo */ -abstract class Guzzle6HttpAdapterTest extends HttpAdapterTest +abstract class Guzzle6HttpAdapterTest extends HttpClientTest { /** * {@inheritdoc} diff --git a/tests/Guzzle6HttpAsyncAdapterTest.php b/tests/Guzzle6HttpAsyncAdapterTest.php new file mode 100644 index 0000000..db303fc --- /dev/null +++ b/tests/Guzzle6HttpAsyncAdapterTest.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please read the LICENSE + * file that was distributed with this source code. + */ + +namespace Http\Adapter\Tests; + +use GuzzleHttp\Client; +use Http\Adapter\Guzzle6HttpAdapter; +use Http\Client\Tests\HttpAsyncClientTest; + +/** + * @author GeLo + */ +abstract class Guzzle6HttpAsyncAdapterTest extends HttpAsyncClientTest +{ + /** + * {@inheritdoc} + */ + protected function createHttpAsyncClient() + { + return new Guzzle6HttpAdapter(new Client(['handler' => $this->createHandler()])); + } + + /** + * Returns a handler for the client + * + * @return object + */ + abstract protected function createHandler(); +} diff --git a/tests/Guzzle6MultiCurlHttpAsyncAdapterTest.php b/tests/Guzzle6MultiCurlHttpAsyncAdapterTest.php new file mode 100644 index 0000000..a3c267a --- /dev/null +++ b/tests/Guzzle6MultiCurlHttpAsyncAdapterTest.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please read the LICENSE + * file that was distributed with this source code. + */ + +namespace Http\Adapter\Tests; + +use GuzzleHttp\Handler\CurlMultiHandler; + +/** + * @author GeLo + */ +class Guzzle6MultiCurlHttpAsyncAdapterTest extends Guzzle6HttpAsyncAdapterTest +{ + /** + * {@inheritdoc} + */ + protected function createHandler() + { + return new CurlMultiHandler(); + } +} diff --git a/tests/Guzzle6HttpAdapterExceptionTest.php b/tests/Guzzle6PromiseExceptionTest.php similarity index 76% rename from tests/Guzzle6HttpAdapterExceptionTest.php rename to tests/Guzzle6PromiseExceptionTest.php index 05aa0fd..4d3f13f 100644 --- a/tests/Guzzle6HttpAdapterExceptionTest.php +++ b/tests/Guzzle6PromiseExceptionTest.php @@ -12,56 +12,57 @@ namespace Http\Adapter\Tests; use GuzzleHttp\Exception as GuzzleExceptions; -use Http\Adapter\Guzzle6HttpAdapter; +use Http\Adapter\Guzzle6Promise; /** * @author Tobias Nyholm */ -class Guzzle6HttpAdapterExceptionTest extends \PHPUnit_Framework_TestCase +class Guzzle6PromiseExceptionTest extends \PHPUnit_Framework_TestCase { public function testGetException() { $request = $this->getMock('Psr\Http\Message\RequestInterface'); $response = $this->getMock('Psr\Http\Message\ResponseInterface'); + $promise = $this->getMock('GuzzleHttp\Promise\PromiseInterface'); - $adapter = new Guzzle6HttpAdapter(); - $method = new \ReflectionMethod('Http\Adapter\Guzzle6HttpAdapter', 'handleException'); + $adapter = new Guzzle6Promise($promise, $request); + $method = new \ReflectionMethod('Http\Adapter\Guzzle6Promise', 'handleException'); $method->setAccessible(true); - $outputException = $method->invoke($adapter, new GuzzleExceptions\ConnectException('foo', $request)); + $outputException = $method->invoke($adapter, new GuzzleExceptions\ConnectException('foo', $request), $request); $this->assertInstanceOf('Http\Client\Exception\NetworkException', $outputException, "Guzzle's ConnectException should be converted to a NetworkException"); - $outputException = $method->invoke($adapter, new GuzzleExceptions\TooManyRedirectsException('foo', $request)); + $outputException = $method->invoke($adapter, new GuzzleExceptions\TooManyRedirectsException('foo', $request), $request); $this->assertInstanceOf('Http\Client\Exception\RequestException', $outputException, "Guzzle's TooManyRedirectsException should be converted to a RequestException"); - $outputException = $method->invoke($adapter, new GuzzleExceptions\RequestException('foo', $request, $response)); + $outputException = $method->invoke($adapter, new GuzzleExceptions\RequestException('foo', $request, $response), $request); $this->assertInstanceOf('Http\Client\Exception\HttpException', $outputException, "Guzzle's RequestException should be converted to a HttpException"); - $outputException = $method->invoke($adapter, new GuzzleExceptions\BadResponseException('foo', $request, $response)); + $outputException = $method->invoke($adapter, new GuzzleExceptions\BadResponseException('foo', $request, $response), $request); $this->assertInstanceOf('Http\Client\Exception\HttpException', $outputException, "Guzzle's BadResponseException should be converted to a HttpException"); - $outputException = $method->invoke($adapter, new GuzzleExceptions\ClientException('foo', $request, $response)); + $outputException = $method->invoke($adapter, new GuzzleExceptions\ClientException('foo', $request, $response), $request); $this->assertInstanceOf('Http\Client\Exception\HttpException', $outputException, "Guzzle's ClientException should be converted to a HttpException"); - $outputException = $method->invoke($adapter, new GuzzleExceptions\ServerException('foo', $request, $response)); + $outputException = $method->invoke($adapter, new GuzzleExceptions\ServerException('foo', $request, $response), $request); $this->assertInstanceOf('Http\Client\Exception\HttpException', $outputException, "Guzzle's ServerException should be converted to a HttpException"); - $outputException = $method->invoke($adapter, new GuzzleExceptions\TransferException('foo')); + $outputException = $method->invoke($adapter, new GuzzleExceptions\TransferException('foo'), $request); $this->assertInstanceOf('Http\Client\Exception\TransferException', $outputException, "Guzzle's TransferException should be converted to a TransferException"); /* * Test RequestException without response */ - $outputException = $method->invoke($adapter, new GuzzleExceptions\RequestException('foo', $request)); + $outputException = $method->invoke($adapter, new GuzzleExceptions\RequestException('foo', $request), $request); $this->assertInstanceOf('Http\Client\Exception\RequestException', $outputException, "Guzzle's RequestException with no response should be converted to a RequestException"); - $outputException = $method->invoke($adapter, new GuzzleExceptions\BadResponseException('foo', $request)); + $outputException = $method->invoke($adapter, new GuzzleExceptions\BadResponseException('foo', $request), $request); $this->assertInstanceOf('Http\Client\Exception\RequestException', $outputException, "Guzzle's BadResponseException with no response should be converted to a RequestException"); - $outputException = $method->invoke($adapter, new GuzzleExceptions\ClientException('foo', $request)); + $outputException = $method->invoke($adapter, new GuzzleExceptions\ClientException('foo', $request), $request); $this->assertInstanceOf('Http\Client\Exception\RequestException', $outputException, "Guzzle's ClientException with no response should be converted to a RequestException"); - $outputException = $method->invoke($adapter, new GuzzleExceptions\ServerException('foo', $request)); + $outputException = $method->invoke($adapter, new GuzzleExceptions\ServerException('foo', $request), $request); $this->assertInstanceOf('Http\Client\Exception\RequestException', $outputException, "Guzzle's ServerException with no response should be converted to a RequestException"); } } \ No newline at end of file diff --git a/tests/Guzzle6StreamHttpAsyncAdapterTest.php b/tests/Guzzle6StreamHttpAsyncAdapterTest.php new file mode 100644 index 0000000..51dae06 --- /dev/null +++ b/tests/Guzzle6StreamHttpAsyncAdapterTest.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please read the LICENSE + * file that was distributed with this source code. + */ + +namespace Http\Adapter\Tests; + +use GuzzleHttp\Handler\StreamHandler; + +/** + * @author GeLo + */ +class Guzzle6StreamHttpAsyncAdapterTest extends Guzzle6HttpAsyncAdapterTest +{ + /** + * {@inheritdoc} + */ + protected function createHandler() + { + return new StreamHandler(); + } +}