diff --git a/README.md b/README.md index 1310e81..62975b3 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Uses [Trello API v1](https://trello.com/docs/index.html). The object API is very ## Requirements * PHP >= 7.4 with [cURL](http://php.net/manual/en/book.curl.php) extension, -* [Guzzle](https://github.com/guzzle/guzzle) library, +* [Guzzle](https://github.com/guzzle/guzzle) 5.3 library, * (optional) [PHPUnit](https://phpunit.de) to run tests. ## Installation diff --git a/lib/Trello/ClientInterface.php b/lib/Trello/ClientInterface.php index 5d2bfb6..9b1dc42 100644 --- a/lib/Trello/ClientInterface.php +++ b/lib/Trello/ClientInterface.php @@ -2,7 +2,10 @@ namespace Trello; +/** + * Interface ClientInterface + * @package Trello + */ interface ClientInterface { - } diff --git a/lib/Trello/HttpClient/HttpClient.php b/lib/Trello/HttpClient/HttpClient.php index cad6776..fc4e65c 100644 --- a/lib/Trello/HttpClient/HttpClient.php +++ b/lib/Trello/HttpClient/HttpClient.php @@ -4,13 +4,13 @@ use GuzzleHttp\Client as GuzzleClient; use GuzzleHttp\ClientInterface; +use GuzzleHttp\Event\SubscriberInterface; use GuzzleHttp\Message\Request; use GuzzleHttp\Message\Response; use Trello\Exception\ErrorException; use Trello\Exception\RuntimeException; -use Trello\HttpClient\Listener\AuthListener; -use Trello\HttpClient\Listener\ErrorListener; -use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Trello\HttpClient\Subscriber\AuthSubscriber; +use Trello\HttpClient\Subscriber\ErrorSubscriber; class HttpClient implements HttpClientInterface { @@ -41,7 +41,7 @@ public function __construct(array $options = [], ClientInterface $client = null) $client = $client ?: new GuzzleClient($this->options); $this->client = $client; - $this->addListener('request.error', [new ErrorListener(), 'onRequestError']); + $this->addSubscriber(new ErrorSubscriber()); $this->clearHeaders(); } @@ -81,7 +81,10 @@ public function addListener($eventName, $listener) $this->client->getEmitter()->on($eventName, $listener); } - public function addSubscriber(EventSubscriberInterface $subscriber) + /** + * @param SubscriberInterface $subscriber + */ + public function addSubscriber(SubscriberInterface $subscriber) { $this->client->getEmitter()->attach($subscriber); } @@ -164,10 +167,7 @@ public function request($path, $body = null, $httpMethod = 'GET', array $headers */ public function authenticate($tokenOrLogin, $password, $method) { - $this->addListener('request.before_send', [ - new AuthListener($tokenOrLogin, $password, $method), - 'onRequestBeforeSend', - ]); + $this->addSubscriber(new AuthSubscriber($tokenOrLogin, $password, $method)); } /** diff --git a/lib/Trello/HttpClient/Subscriber/AuthSubscriber.php b/lib/Trello/HttpClient/Subscriber/AuthSubscriber.php new file mode 100644 index 0000000..1ba71a1 --- /dev/null +++ b/lib/Trello/HttpClient/Subscriber/AuthSubscriber.php @@ -0,0 +1,99 @@ +tokenOrLogin = $tokenOrLogin; + $this->password = $password ?: null; + $this->method = $method; + } + + /** + * @inheritDoc + */ + public function getEvents() + { + return [ + 'before' => ['onRequestBeforeSend'] /** @see AuthSubscriber::onRequestBeforeSend() */ + ]; + } + + /** + * @param BeforeEvent $event + */ + public function onRequestBeforeSend(BeforeEvent $event) + { + $request = $event->getRequest(); + + // Skip by default + if (null === $this->method) { + return; + } + + switch ($this->method) { + case Client::AUTH_HTTP_PASSWORD: + $request->setHeader( + 'Authorization', + sprintf('Basic %s', base64_encode($this->tokenOrLogin . ':' . $this->password)) + ); + break; + + case Client::AUTH_HTTP_TOKEN: + $request->setHeader( + 'Authorization', + sprintf('token %s', $this->tokenOrLogin) + ); + break; + + case Client::AUTH_URL_CLIENT_ID: + $url = $request->getUrl(); + + $parameters = [ + 'key' => $this->tokenOrLogin, + 'token' => $this->password, + ]; + + $url .= (false === strpos($url, '?') ? '?' : '&'); + $url .= utf8_encode(http_build_query($parameters, '', '&')); + + $request->setUrl($url); + break; + + case Client::AUTH_URL_TOKEN: + $url = $request->getUrl(); + $url .= (false === strpos($url, '?') ? '?' : '&'); + $url .= utf8_encode(http_build_query( + ['token' => $this->tokenOrLogin, 'key' => $this->password], + '', + '&' + )); + + $request->setUrl($url); + break; + + default: + throw new RuntimeException(sprintf('%s not yet implemented', $this->method)); + } + } +} diff --git a/lib/Trello/HttpClient/Subscriber/ErrorSubscriber.php b/lib/Trello/HttpClient/Subscriber/ErrorSubscriber.php new file mode 100644 index 0000000..31027e6 --- /dev/null +++ b/lib/Trello/HttpClient/Subscriber/ErrorSubscriber.php @@ -0,0 +1,91 @@ + ['onRequestError'] /** @see ErrorSubscriber::onRequestError() */ + ]; + } + + /** + * @param ErrorEvent $event + * + * @throws ErrorException + * @throws ValidationFailedException + */ + public function onRequestError(ErrorEvent $event) + { + /** @var Response $response */ + $response = $event->getResponse(); + + switch ($response->getStatusCode()) { + case 429: + throw new ApiLimitExceedException('Wait a second.', 429); + break; + } + + $content = ResponseMediator::getContent($response); + if (is_array($content) && isset($content['message'])) { + if (400 == $response->getStatusCode()) { + throw new ErrorException($content['message'], 400); + } + + if (401 == $response->getStatusCode()) { + throw new PermissionDeniedException($content['message'], 401); + } + + if (422 == $response->getStatusCode() && isset($content['errors'])) { + $errors = []; + foreach ($content['errors'] as $error) { + switch ($error['code']) { + case 'missing': + $errors[] = sprintf('The %s %s does not exist, for resource "%s"', $error['field'], $error['value'], $error['resource']); + break; + + case 'missing_field': + $errors[] = sprintf('Field "%s" is missing, for resource "%s"', $error['field'], $error['resource']); + break; + + case 'invalid': + $errors[] = sprintf('Field "%s" is invalid, for resource "%s"', $error['field'], $error['resource']); + break; + + case 'already_exists': + $errors[] = sprintf('Field "%s" already exists, for resource "%s"', $error['field'], $error['resource']); + break; + + default: + $errors[] = $error['message']; + break; + + } + } + + throw new ValidationFailedException('Validation Failed: ' . implode(', ', $errors), 422); + } + } + + throw new RuntimeException(isset($content['message']) ? $content['message'] : $content, $response->getStatusCode()); + } +}