This repository has been archived by the owner on Aug 9, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #3 from Rebilly/client-class
Implement HTTP Client
- Loading branch information
Showing
2 changed files
with
317 additions
and
0 deletions.
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,187 @@ | ||
<?php | ||
/** | ||
* This file is part of the HTTP Client package. | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
* | ||
* @see http://rebilly.com | ||
*/ | ||
|
||
namespace Rebilly\HttpClient; | ||
|
||
use BadMethodCallException; | ||
use Psr\Http\Message\RequestInterface as Request; | ||
use Psr\Http\Message\ResponseInterface as Response; | ||
use GuzzleHttp\Psr7\Request as GuzzleRequest; | ||
use GuzzleHttp\Psr7\Response as GuzzleResponse; | ||
use Rebilly\HttpClient\Adapter\Curl\CurlAdapter; | ||
use RuntimeException; | ||
|
||
/** | ||
* Class Client. | ||
* | ||
* Magic facades for HTTP methods: | ||
* | ||
* @see Client::__call() | ||
* @see Client::send() | ||
* | ||
* @method Response get($path, $params = [], $headers = [], $options = []) | ||
* @method Response head($path, $params = [], $headers = [], $options = []) | ||
* @method Response post($payload, $path, $params = [], $headers = [], $options = []) | ||
* @method Response put($payload, $path, $params = [], $headers = [], $options = []) | ||
* @method Response delete($path, $params = [], $headers = [], $options = []) | ||
* | ||
* @author Veaceslav Medvedev <veaceslav.medvedev@rebilly.com> | ||
* @version 0.1 | ||
*/ | ||
final class Client | ||
{ | ||
/** | ||
* @var Middleware | ||
*/ | ||
private $middleware; | ||
|
||
/** | ||
* @var Adapter | ||
*/ | ||
private $adapter; | ||
|
||
/** | ||
* @var array | ||
*/ | ||
private $requestOptions = []; | ||
|
||
/** | ||
* @var Request | ||
*/ | ||
private $lastRequest; | ||
|
||
/** | ||
* @var Response | ||
*/ | ||
private $lastResponse; | ||
|
||
/** | ||
* @param Adapter|callable|null $adapter | ||
* @param Middleware[]|callable[] $middleware | ||
*/ | ||
public function __construct($adapter = null, array $middleware = []) | ||
{ | ||
$this->middleware = empty($middleware) ? $this : new Middleware\Stack($middleware); | ||
$this->adapter = $adapter ?: new CurlAdapter(); | ||
} | ||
|
||
/** | ||
* Application (Client) should be a final layer in middleware stack. | ||
* | ||
* @param Request $request | ||
* @param Response $response | ||
* | ||
* @return Response | ||
*/ | ||
public function __invoke(Request $request, Response $response) | ||
{ | ||
// Send response to the adapter as a prototype | ||
$response = call_user_func($this->adapter, $request, $response, $this->requestOptions) ?: $response; | ||
|
||
if (!($response instanceof Response)) { | ||
throw new RuntimeException('Wrong response'); | ||
} | ||
|
||
return $response; | ||
} | ||
|
||
/** | ||
* Magic methods support. | ||
* | ||
* @see Client::send() | ||
* | ||
* @param string $name | ||
* @param array $arguments | ||
* | ||
* @throws RuntimeException | ||
* @throws BadMethodCallException | ||
* | ||
* @return mixed | ||
*/ | ||
public function __call($name, $arguments) | ||
{ | ||
switch (strtoupper($name)) { | ||
case 'HEAD': | ||
case 'GET': | ||
case 'DELETE': | ||
array_unshift($arguments, null); | ||
array_unshift($arguments, $name); | ||
return call_user_func_array([$this, 'send'], $arguments); | ||
case 'POST': | ||
case 'PUT': | ||
array_unshift($arguments, $name); | ||
return call_user_func_array([$this, 'send'], $arguments); | ||
} | ||
|
||
throw new BadMethodCallException(sprintf('Call unknown method %s::%s', __CLASS__, $name)); | ||
} | ||
|
||
/** | ||
* Send request. | ||
* | ||
* @param string $method The request method. | ||
* @param mixed $payload The request body. | ||
* @param string $uri The URL path or URL Template | ||
* @param array $headers The headers specific for request. | ||
* @param array $options The request options. | ||
* | ||
* @return Response | ||
*/ | ||
public function send($method, $payload, $uri, array $headers = [], array $options = []) | ||
{ | ||
$this->requestOptions = $options; | ||
$this->lastRequest = $this->createRequest($method, $uri, $payload, $headers); | ||
$this->lastResponse = $this->createResponse(); | ||
$this->lastResponse = call_user_func($this->middleware, $this->lastRequest, $this->lastResponse, $this); | ||
|
||
return $this->lastResponse; | ||
} | ||
|
||
/** | ||
* @return Request | ||
*/ | ||
public function getLastRequest() | ||
{ | ||
return $this->lastRequest; | ||
} | ||
|
||
/** | ||
* @return Response | ||
*/ | ||
public function getLastResponse() | ||
{ | ||
return $this->lastResponse; | ||
} | ||
|
||
/** | ||
* Factory method to create a new Request object. | ||
* | ||
* @param string $method | ||
* @param mixed $uri | ||
* @param mixed $payload | ||
* @param array $headers | ||
* | ||
* @return Request | ||
*/ | ||
private function createRequest($method, $uri, $payload, array $headers = []) | ||
{ | ||
return new GuzzleRequest($method, $uri, $headers, $payload); | ||
} | ||
|
||
/** | ||
* Factory method to create a new Response object. | ||
* | ||
* @return Response | ||
*/ | ||
private function createResponse() | ||
{ | ||
return new GuzzleResponse(); | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
<?php | ||
/** | ||
* This file is part of the HTTP Client package. | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
* | ||
* @see http://rebilly.com | ||
*/ | ||
|
||
namespace Rebilly\HttpClient; | ||
|
||
use BadMethodCallException; | ||
use Psr\Http\Message\RequestInterface as Request; | ||
use Psr\Http\Message\ResponseInterface as Response; | ||
use RuntimeException; | ||
|
||
/** | ||
* Class ClientTest. | ||
* | ||
* @author Veaceslav Medvedev <veaceslav.medvedev@rebilly.com> | ||
*/ | ||
class ClientTest extends TestCase | ||
{ | ||
/** | ||
* @test | ||
*/ | ||
public function useInvalidAdapter() | ||
{ | ||
$this->setExpectedException(RuntimeException::class); | ||
|
||
$handler = $this->getMockForAbstractClass(Adapter::class); | ||
$handler->expects($this->any())->method('__invoke')->willReturn('response'); | ||
|
||
$client = new Client($handler); | ||
$client->get('http://localhost'); | ||
} | ||
|
||
/** | ||
* @test | ||
* @dataProvider provideHttpMethods | ||
* | ||
* @param string $method | ||
*/ | ||
public function sendRequest($method) | ||
{ | ||
$handler = $this->getMockForAbstractClass(Adapter::class); | ||
$client = new Client($handler); | ||
|
||
$response = $client->send($method, [], 'http://localhost'); | ||
$this->assertInstanceOf(Response::class, $response); | ||
|
||
$this->assertInstanceOf(Request::class, $client->getLastRequest()); | ||
$this->assertInstanceOf(Response::class, $client->getLastResponse()); | ||
} | ||
|
||
/** | ||
* @test | ||
*/ | ||
public function sendRequestUsingFacades() | ||
{ | ||
$handler = $this->getMockForAbstractClass(Adapter::class); | ||
|
||
$client = new Client($handler); | ||
|
||
$response = $client->get('http://localhost'); | ||
$this->assertInstanceOf(Response::class, $response); | ||
|
||
$response = $client->post([], 'http://localhost'); | ||
$this->assertInstanceOf(Response::class, $response); | ||
|
||
$response = $client->put([], 'http://localhost'); | ||
$this->assertInstanceOf(Response::class, $response); | ||
|
||
$response = $client->delete('http://localhost'); | ||
$this->assertInstanceOf(Response::class, $response); | ||
} | ||
|
||
/** | ||
* @test | ||
*/ | ||
public function callUnknownFacade() | ||
{ | ||
$this->setExpectedException(BadMethodCallException::class); | ||
|
||
/** @var mixed $client */ | ||
$client = new Client(); | ||
$client->show('http://localhost'); | ||
} | ||
|
||
/** | ||
* @todo | ||
* @test | ||
* @dataProvider provideHttpExceptionCodes | ||
* | ||
* @param int $code | ||
*/ | ||
public function sendClientExceptions($code) | ||
{ | ||
} | ||
|
||
/** | ||
* @return array | ||
*/ | ||
public function provideHttpExceptionCodes() | ||
{ | ||
return [ | ||
[404], | ||
[410], | ||
[422], | ||
[400], | ||
[500], | ||
]; | ||
} | ||
|
||
/** | ||
* @return array | ||
*/ | ||
public function provideHttpMethods() | ||
{ | ||
return [ | ||
['OPTIONS'], | ||
['HEAD'], | ||
['GET'], | ||
['POST'], | ||
['PUT'], | ||
['DELETE'], | ||
]; | ||
} | ||
} |