Create Request Builder wrapper around Artax

This commit is contained in:
Timothy Warren 2017-02-07 13:11:42 -05:00
parent 76c9adbc43
commit 20540963ff
6 changed files with 423 additions and 16 deletions

View File

@ -22,10 +22,12 @@ use Aura\Session\SessionFactory;
use Aviat\AnimeClient\API\Kitsu\{ use Aviat\AnimeClient\API\Kitsu\{
Auth as KitsuAuth, Auth as KitsuAuth,
ListItem as KitsuListItem, ListItem as KitsuListItem,
KitsuRequestBuilder,
Model as KitsuModel Model as KitsuModel
}; };
use Aviat\AnimeClient\API\MAL\{ use Aviat\AnimeClient\API\MAL\{
ListItem as MALListItem, ListItem as MALListItem,
MALRequestBuilder,
Model as MALModel Model as MALModel
}; };
use Aviat\AnimeClient\Model; use Aviat\AnimeClient\Model;
@ -48,13 +50,13 @@ return function(array $config_array = []) {
$app_logger = new Logger('animeclient'); $app_logger = new Logger('animeclient');
$app_logger->pushHandler(new RotatingFileHandler(__DIR__ . '/logs/app.log', Logger::NOTICE)); $app_logger->pushHandler(new RotatingFileHandler(__DIR__ . '/logs/app.log', Logger::NOTICE));
$kitsu_request_logger = new Logger('kitsu_request'); $kitsu_request_logger = new Logger('kitsu-request');
$kitsu_request_logger->pushHandler(new RotatingFileHandler(__DIR__ . '/logs/kitsu_request.log', Logger::NOTICE)); $kitsu_request_logger->pushHandler(new RotatingFileHandler(__DIR__ . '/logs/kitsu_request.log', Logger::NOTICE));
$mal_request_logger = new Logger('mal_request'); $mal_request_logger = new Logger('mal-request');
$mal_request_logger->pushHandler(new RotatingFileHandler(__DIR__ . '/logs/mal_request.log', Logger::NOTICE)); $mal_request_logger->pushHandler(new RotatingFileHandler(__DIR__ . '/logs/mal_request.log', Logger::NOTICE));
$container->setLogger($app_logger, 'default'); $container->setLogger($app_logger, 'default');
$container->setLogger($kitsu_request_logger, 'kitsu_request'); $container->setLogger($kitsu_request_logger, 'kitsu-request');
$container->setLogger($mal_request_logger, 'mal_request'); $container->setLogger($mal_request_logger, 'mal-request');
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// Injected Objects // Injected Objects
@ -115,21 +117,35 @@ return function(array $config_array = []) {
// Models // Models
$container->set('kitsu-model', function($container) { $container->set('kitsu-model', function($container) {
$requestBuilder = new KitsuRequestBuilder();
$requestBuilder->setLogger($container->getLogger('kitsu-request'));
$listItem = new KitsuListItem(); $listItem = new KitsuListItem();
$listItem->setContainer($container); $listItem->setContainer($container);
$listItem->setRequestBuilder($requestBuilder);
$model = new KitsuModel($listItem); $model = new KitsuModel($listItem);
$model->setContainer($container); $model->setContainer($container);
$model->setRequestBuilder($requestBuilder);
$cache = $container->get('cache'); $cache = $container->get('cache');
$model->setCache($cache); $model->setCache($cache);
return $model; return $model;
}); });
$container->set('mal-model', function($container) { $container->set('mal-model', function($container) {
$requestBuilder = new MALRequestBuilder();
$requestBuilder->setLogger($container->getLogger('mal-request'));
$listItem = new MALListItem(); $listItem = new MALListItem();
$listItem->setContainer($container); $listItem->setContainer($container);
$listItem->setRequestBuilder($requestBuilder);
$model = new MALModel($listItem); $model = new MALModel($listItem);
$model->setContainer($container); $model->setContainer($container);
$model->setRequestBuilder($requestBuilder);
return $model; return $model;
}); });
$container->set('api-model', function($container) { $container->set('api-model', function($container) {
return new Model\API($container); return new Model\API($container);
}); });

View File

@ -0,0 +1,217 @@
<?php declare(strict_types=1);
/**
* Anime List Client
*
* An API client for Kitsu and MyAnimeList to manage anime and manga watch lists
*
* PHP version 7
*
* @package AnimeListClient
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2017 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 4.0
* @link https://github.com/timw4mail/HummingBirdAnimeClient
*/
namespace Aviat\AnimeClient\API;
use Amp\Artax\{
Client,
FormBody,
Request
};
use Aviat\Ion\Di\ContainerAware;
use InvalidArgumentException;
use Psr\Log\LoggerAwareTrait;
/**
* Wrapper around Artex to make it easier to build API requests
*/
class APIRequestBuilder {
use LoggerAwareTrait;
/**
* Url prefix for making url requests
* @var string
*/
protected $baseUrl = '';
/**
* Url path of the request
* @var string
*/
protected $path = '';
/**
* Query string for the request
* @var string
*/
protected $query = '';
/**
* Default request headers
* @var array
*/
protected $defaultHeaders = [];
/**
* Valid HTTP request methos
* @var array
*/
protected $validMethods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'];
/**
* The current request
* @var \Amp\Promise
*/
protected $request;
/**
* Set body as form fields
*
* @param array $fields Mapping of field names to values
* @return self
*/
public function setFormFields(array $fields): self
{
$body = $this->fixBody((new FormBody)->addFields($createData));
$this->setBody($body);
return $this;
}
/**
* Set the request body
*
* @param FormBody|string $body
* @return self
*/
public function setBody($body): self
{
$this->request->setBody($body);
return $this;
}
/**
* Set a request header
*
* @param string $name
* @param string $value
* @return self
*/
public function setHeader(string $name, string $value): self
{
$this->request->setHeader($name, $value);
return $this;
}
/**
* Set multiple request headers
*
* name => value
*
* @param array $headers
* @return self
*/
public function setHeaders(array $headers): self
{
foreach ($headers as $name => $value)
{
$this->setHeader($name, $value);
}
return $this;
}
/**
* Append a query string in array format
*
* @param array $params
* @return self
*/
public function setQuery(array $params): self
{
$this->query = http_build_query($params);
return $this;
}
/**
* Return the promise for the current request
*
* @return \Amp\Promise
*/
public function getFullRequest()
{
$this->buildUri();
return $this->request;
}
/**
* Create a new http request
*
* @param string $type
* @param string $uri
* @return self
*/
public function newRequest(string $type, string $uri): self
{
if ( ! in_array($type, $this->validMethods))
{
throw new InvalidArgumentException('Invalid HTTP methods');
}
$this->resetState();
$this->request
->setMethod($type)
->setProtocol('1.1');
return $this;
}
/**
* Create the full request url
*
* @return void
*/
private function buildUri()
{
$url = (strpos($this->path, '//') !== FALSE)
? $this->path
: $this->baseUrl . $url;
if ( ! empty($this->query))
{
$url .= '?' . $this->query;
}
$this->request->setUri($url);
}
/**
* Unencode the dual-encoded ampersands in the body
*
* This is a dirty hack until I can fully track down where
* the dual-encoding happens
*
* @param FormBody $formBody The form builder object to fix
* @return string
*/
private function fixBody(FormBody $formBody): string
{
$rawBody = \Amp\wait($formBody->getBody());
return html_entity_decode($rawBody, \ENT_HTML5, 'UTF-8');
}
/**
* Reset the class state for a new request
*
* @return void
*/
private function resetState()
{
$this->path = '';
$this->query = '';
$this->request = new Request();
}
}

View File

@ -0,0 +1,55 @@
<?php declare(strict_types=1);
/**
* Anime List Client
*
* An API client for Kitsu and MyAnimeList to manage anime and manga watch lists
*
* PHP version 7
*
* @package AnimeListClient
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2017 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 4.0
* @link https://github.com/timw4mail/HummingBirdAnimeClient
*/
namespace Aviat\AnimeClient\API\Kitsu;
use Aviat\AnimeClient\API\APIRequestBuilder;
use Aviat\AnimeClient\API\Kitsu as K;
use Aviat\Ion\Json;
class KitsuRequestBuilder extends APIRequestBuilder {
/**
* The base url for api requests
* @var string $base_url
*/
protected $baseUrl = "https://kitsu.io/api/edge/";
/**
* HTTP headers to send with every request
*
* @var array
*/
protected $defaultHeaders = [
'User-Agent' => "Tim's Anime Client/4.0",
'Accept-Encoding' => 'application/vnd.api+json',
'Content-Type' => 'application/vnd.api+json',
'client_id' => 'dd031b32d2f56c990b1425efe6c42ad847e7fe3ab46bf1299f05ecd856bdb7dd',
'client_secret' => '54d7307928f63414defd96399fc31ba847961ceaecef3a5fd93144e960c0e151',
];
/**
* Set the request body
*
* @param array|FormBody|string $body
* @return self
*/
public function setJsonBody(array $body): self
{
$requestBody = Json::encode($body);
return $this->setBody($requestBody);
}
}

View File

@ -27,7 +27,24 @@ use InvalidArgumentException;
use RuntimeException; use RuntimeException;
trait KitsuTrait { trait KitsuTrait {
use GuzzleTrait;
/**
* The request builder for the MAL API
* @var MALRequestBuilder
*/
protected $requestBuilder;
/**
* The Guzzle http client object
* @var object
*/
protected $client;
/**
* Cookie jar object for api requests
* @var object
*/
protected $cookieJar;
/** /**
* The base url for api requests * The base url for api requests
@ -48,6 +65,18 @@ trait KitsuTrait {
'client_secret' => '54d7307928f63414defd96399fc31ba847961ceaecef3a5fd93144e960c0e151', 'client_secret' => '54d7307928f63414defd96399fc31ba847961ceaecef3a5fd93144e960c0e151',
]; ];
/**
* Set the request builder object
*
* @param KitsuRequestBuilder $requestBuilder
* @return self
*/
public function setRequestBuilder($requestBuilder): self
{
$this->requestBuilder = $requestBuilder;
return $this;
}
/** /**
* Set up the class properties * Set up the class properties
* *
@ -93,7 +122,7 @@ trait KitsuTrait {
'headers' => $this->defaultHeaders 'headers' => $this->defaultHeaders
]; ];
$logger = $this->container->getLogger('kitsu_request'); $logger = $this->container->getLogger('kitsu-request');
$sessionSegment = $this->getContainer() $sessionSegment = $this->getContainer()
->get('session') ->get('session')
->getSegment(AnimeClient::SESSION_SEGMENT); ->getSegment(AnimeClient::SESSION_SEGMENT);
@ -134,7 +163,7 @@ trait KitsuTrait {
$logger = null; $logger = null;
if ($this->getContainer()) if ($this->getContainer())
{ {
$logger = $this->container->getLogger('kitsu_request'); $logger = $this->container->getLogger('kitsu-request');
} }
$response = $this->getResponse($type, $url, $options); $response = $this->getResponse($type, $url, $options);
@ -183,7 +212,7 @@ trait KitsuTrait {
$logger = null; $logger = null;
if ($this->getContainer()) if ($this->getContainer())
{ {
$logger = $this->container->getLogger('kitsu_request'); $logger = $this->container->getLogger('kitsu-request');
} }
$response = $this->getResponse('POST', ...$args); $response = $this->getResponse('POST', ...$args);

View File

@ -0,0 +1,50 @@
<?php declare(strict_types=1);
/**
* Anime List Client
*
* An API client for Kitsu and MyAnimeList to manage anime and manga watch lists
*
* PHP version 7
*
* @package AnimeListClient
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2017 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 4.0
* @link https://github.com/timw4mail/HummingBirdAnimeClient
*/
namespace Aviat\AnimeClient\API\MAL;
use Aviat\AnimeClient\API\{
APIRequestBuilder,
MAL as M,
XML
};
class MALRequestBuilder extends APIRequestBuilder {
/**
* The base url for api requests
* @var string $base_url
*/
protected $baseUrl = M::BASE_URL;
/**
* HTTP headers to send with every request
*
* @var array
*/
protected $defaultHeaders = [
'Accept' => 'text/xml',
'Accept-Encoding' => 'gzip',
'Content-type' => 'application/x-www-form-urlencoded',
'User-Agent' => "Tim's Anime Client/4.0"
];
/**
* Valid HTTP request methos
* @var array
*/
protected $validMethods = ['GET', 'POST', 'DELETE'];
}

View File

@ -19,13 +19,21 @@ namespace Aviat\AnimeClient\API\MAL;
use Amp\Artax\{Client, FormBody, Request}; use Amp\Artax\{Client, FormBody, Request};
use Aviat\AnimeClient\API\{ use Aviat\AnimeClient\API\{
MAL as M, MAL as M,
APIRequestBuilder,
XML XML
}; };
use Aviat\AnimeClient\API\MALRequestBuilder;
use Aviat\Ion\Json; use Aviat\Ion\Json;
use InvalidArgumentException; use InvalidArgumentException;
trait MALTrait { trait MALTrait {
/**
* The request builder for the MAL API
* @var MALRequestBuilder
*/
protected $requestBuilder;
/** /**
* The base url for api requests * The base url for api requests
* @var string $base_url * @var string $base_url
@ -44,6 +52,18 @@ trait MALTrait {
'User-Agent' => "Tim's Anime Client/4.0" 'User-Agent' => "Tim's Anime Client/4.0"
]; ];
/**
* Set the request builder object
*
* @param MALRequestBuilder $requestBuilder
* @return self
*/
public function setRequestBuilder($requestBuilder): self
{
$this->requestBuilder = $requestBuilder;
return $this;
}
/** /**
* Unencode the dual-encoded ampersands in the body * Unencode the dual-encoded ampersands in the body
* *
@ -60,14 +80,14 @@ trait MALTrait {
} }
/** /**
* Make a request via Guzzle * Create a request object
* *
* @param string $type * @param string $type
* @param string $url * @param string $url
* @param array $options * @param array $options
* @return Response * @return \Amp\Promise
*/ */
private function getResponse(string $type, string $url, array $options = []) public function setUpRequest(string $type, string $url, array $options = [])
{ {
$this->defaultHeaders['User-Agent'] = $_SERVER['HTTP_USER_AGENT'] ?? $this->defaultHeaders; $this->defaultHeaders['User-Agent'] = $_SERVER['HTTP_USER_AGENT'] ?? $this->defaultHeaders;
@ -80,7 +100,7 @@ trait MALTrait {
} }
$config = $this->container->get('config'); $config = $this->container->get('config');
$logger = $this->container->getLogger('mal_request'); $logger = $this->container->getLogger('mal-request');
$headers = array_merge($this->defaultHeaders, $options['headers'] ?? [], [ $headers = array_merge($this->defaultHeaders, $options['headers'] ?? [], [
'Authorization' => 'Basic ' . 'Authorization' => 'Basic ' .
@ -109,6 +129,26 @@ trait MALTrait {
$request->setBody($options['body']); $request->setBody($options['body']);
} }
return $request;
}
/**
* Make a request
*
* @param string $type
* @param string $url
* @param array $options
* @return \Amp\Artax\Response
*/
private function getResponse(string $type, string $url, array $options = [])
{
$logger = null;
if ($this->getContainer())
{
$logger = $this->container->getLogger('mal-request');
}
$request = $this->setUpRequest($type, $url, $options);
$response = \Amp\wait((new Client)->request($request)); $response = \Amp\wait((new Client)->request($request));
$logger->debug('MAL api request', [ $logger->debug('MAL api request', [
@ -126,7 +166,7 @@ trait MALTrait {
} }
/** /**
* Make a request via Guzzle * Make a request
* *
* @param string $type * @param string $type
* @param string $url * @param string $url
@ -138,7 +178,7 @@ trait MALTrait {
$logger = null; $logger = null;
if ($this->getContainer()) if ($this->getContainer())
{ {
$logger = $this->container->getLogger('mal_request'); $logger = $this->container->getLogger('mal-request');
} }
$response = $this->getResponse($type, $url, $options); $response = $this->getResponse($type, $url, $options);
@ -176,7 +216,7 @@ trait MALTrait {
$logger = null; $logger = null;
if ($this->getContainer()) if ($this->getContainer())
{ {
$logger = $this->container->getLogger('mal_request'); $logger = $this->container->getLogger('mal-request');
} }
$response = $this->getResponse('POST', ...$args); $response = $this->getResponse('POST', ...$args);