diff --git a/.gitignore b/.gitignore index b90ffc5..c527c3a 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,9 @@ vendor/ .idea/ # Environment files -.env/*.* \ No newline at end of file +.env/*.* + +# Local configs +.php_cs +.php_cs.cache +phpunit.xml diff --git a/.php_cs.dist b/.php_cs.dist new file mode 100644 index 0000000..c906f2e --- /dev/null +++ b/.php_cs.dist @@ -0,0 +1,20 @@ +in(__DIR__ . '/lib'); + +return PhpCsFixer\Config::create() + ->setRiskyAllowed(true) + ->setRules([ + '@PSR2' => true, + '@Symfony' => true, + 'array_syntax' => ['syntax' => 'short'], + 'cast_spaces' => ['space' => 'single'], + 'yoda_style' => false, + 'concat_space' => ['spacing' => 'one'], + 'phpdoc_summary' => false, + 'combine_consecutive_unsets' => true, + 'final_internal_class' => true, + 'global_namespace_import' => ['import_classes' => false], + ]) + ->setFinder($finder); diff --git a/.travis.yml b/.travis.yml index 6caa598..63cbb5f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,16 +1,5 @@ language: php -before_script: - - if [ -n "$GIT_HUB_TOKEN" ]; then composer config -g github-oauth.github.com "$GIT_HUB_TOKEN"; fi; - - composer install - - if [ "$dependencies" = "lowest" ]; then composer update --prefer-lowest --prefer-stable -n; fi; - -script: - - make test - -after_success: - - bash <(curl -s https://codecov.io/bash) - php: - 5.6 - 7.0 @@ -20,8 +9,26 @@ php: - 7.4 env: - - dependencies=lowest - - dependencies=highest + matrix: + - dependencies=lowest + - dependencies=highest + global: + - composer_flags="--no-interaction --no-ansi --no-progress --no-suggest --verbose" + +before_install: + - composer self-update + - composer clear-cache + +install: + - if [ -n "$GIT_HUB_TOKEN" ]; then composer config -g github-oauth.github.com "$GIT_HUB_TOKEN"; fi; + - if [ "$dependencies" = "highest" ]; then composer update $composer_flags; fi; + - if [ "$dependencies" = "lowest" ]; then composer update $composer_flags --prefer-lowest --prefer-stable; fi; + +script: + - vendor/bin/phpunit test/unit + +after_success: + - bash <(curl -s https://codecov.io/bash) notifications: slack: diff --git a/Makefile b/Makefile index 260a6d0..8fe298f 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ clean: @rm -rf vendor composer.lock install: clean - composer install + composer install --no-suggest --no-scripts --no-progress --no-interaction test: install vendor/bin/phpunit test/unit diff --git a/composer.json b/composer.json index 146d016..8f79aeb 100644 --- a/composer.json +++ b/composer.json @@ -18,11 +18,14 @@ "require": { "php": ">=5.6", "ext-curl": "*", - "ext-json": "*" + "ext-json": "*", + "ext-mbstring": "*" }, "require-dev": { - "phpunit/phpunit": "~4.4", - "squizlabs/php_codesniffer": "~2.0" + "phpunit/phpunit": "^5.7", + "sebastian/version": "^1.0.6", + "squizlabs/php_codesniffer": "~2.0", + "friendsofphp/php-cs-fixer": "^2.16" }, "autoload": { "psr-4": { diff --git a/lib/Client.php b/lib/Client.php index 2609a39..77b092c 100644 --- a/lib/Client.php +++ b/lib/Client.php @@ -9,9 +9,7 @@ use SendGrid\Exception\InvalidRequest; /** - * * Class Client - * @package SendGrid * @version 3.9.5 * * Quickly and easily access any REST or REST-like API. @@ -76,7 +74,6 @@ * @method Client segments() * @method Client singlesends() * - * * Devices * @method Client devices() * @@ -206,24 +203,30 @@ class Client protected $retryOnLimit; /** - * These are the supported HTTP verbs + * Supported HTTP verbs. * * @var array */ private $methods = ['get', 'post', 'patch', 'put', 'delete']; /** - * Initialize the client - * - * @param string $host the base url (e.g. https://api.sendgrid.com) - * @param array $headers global request headers - * @param string $version api version (configurable) - this is specific to the SendGrid API - * @param array $path holds the segments of the url path - * @param array $curlOptions extra options to set during curl initialization - * @param bool $retryOnLimit set default retry on limit flag - */ - public function __construct($host, $headers = null, $version = null, $path = null, $curlOptions = null, $retryOnLimit = false) - { + * Initialize the client. + * + * @param string $host the base url (e.g. https://api.sendgrid.com) + * @param array $headers global request headers + * @param string $version api version (configurable) - this is specific to the SendGrid API + * @param array $path holds the segments of the url path + * @param array $curlOptions extra options to set during curl initialization + * @param bool $retryOnLimit set default retry on limit flag + */ + public function __construct( + $host, + $headers = null, + $version = null, + $path = null, + $curlOptions = null, + $retryOnLimit = false + ) { $this->host = $host; $this->headers = $headers ?: []; $this->version = $version; @@ -275,7 +278,7 @@ public function getCurlOptions() } /** - * Set extra options to set during curl initialization + * Set extra options to set during curl initialization. * * @param array $options * @@ -289,7 +292,7 @@ public function setCurlOptions(array $options) } /** - * Set default retry on limit flag + * Set default retry on limit flag. * * @param bool $retry * @@ -303,7 +306,7 @@ public function setRetryOnLimit($retry) } /** - * Set concurrent request flag + * Set concurrent request flag. * * @param bool $isConcurrent * @@ -317,7 +320,7 @@ public function setIsConcurrentRequest($isConcurrent) } /** - * Build the final URL to be passed + * Build the final URL to be passed. * * @param array $queryParams an array of all the query parameters * @@ -329,16 +332,17 @@ private function buildUrl($queryParams = null) if (isset($queryParams)) { $path .= '?' . http_build_query($queryParams); } + return sprintf('%s%s%s', $this->host, $this->version ?: '', $path); } /** * Creates curl options for a request - * this function does not mutate any private variables + * this function does not mutate any private variables. * * @param string $method - * @param array $body - * @param array $headers + * @param array $body + * @param array $headers * * @return array */ @@ -349,7 +353,7 @@ private function createCurlOptions($method, $body = null, $headers = null) CURLOPT_HEADER => true, CURLOPT_CUSTOMREQUEST => strtoupper($method), CURLOPT_SSL_VERIFYPEER => true, - CURLOPT_FAILONERROR => false + CURLOPT_FAILONERROR => false, ] + $this->curlOptions; if (isset($headers)) { @@ -369,9 +373,8 @@ private function createCurlOptions($method, $body = null, $headers = null) } /** - * @param array $requestData - * e.g. ['method' => 'POST', 'url' => 'www.example.com', 'body' => 'test body', 'headers' => []] - * @param bool $retryOnLimit + * @param array $requestData (method, url, body and headers) + * @param bool $retryOnLimit * * @return array */ @@ -401,9 +404,9 @@ private function createCurlMultiHandle(array $requests) } /** - * Prepare response object + * Prepare response object. * - * @param resource $channel the curl resource + * @param resource $channel the curl resource * @param string $content * * @return Response object @@ -413,9 +416,9 @@ private function parseResponse($channel, $content) $headerSize = curl_getinfo($channel, CURLINFO_HEADER_SIZE); $statusCode = curl_getinfo($channel, CURLINFO_HTTP_CODE); - $responseBody = substr($content, $headerSize); + $responseBody = mb_substr($content, $headerSize); - $responseHeaders = substr($content, 0, $headerSize); + $responseHeaders = mb_substr($content, 0, $headerSize); $responseHeaders = explode("\n", $responseHeaders); $responseHeaders = array_map('trim', $responseHeaders); @@ -423,7 +426,7 @@ private function parseResponse($channel, $content) } /** - * Retry request + * Retry request. * * @param array $responseHeaders headers from rate limited response * @param string $method the HTTP verb @@ -432,12 +435,14 @@ private function parseResponse($channel, $content) * @param array $headers original headers * * @return Response response object + * * @throws InvalidRequest */ private function retryRequest(array $responseHeaders, $method, $url, $body, $headers) { $sleepDurations = $responseHeaders['X-Ratelimit-Reset'] - time(); sleep($sleepDurations > 0 ? $sleepDurations : 0); + return $this->makeRequest($method, $url, $body, $headers, false); } @@ -452,6 +457,7 @@ private function retryRequest(array $responseHeaders, $method, $url, $body, $hea * @param bool $retryOnLimit should retry if rate limit is reach? * * @return Response object + * * @throws InvalidRequest */ public function makeRequest($method, $url, $body = null, $headers = null, $retryOnLimit = false) @@ -469,8 +475,9 @@ public function makeRequest($method, $url, $body = null, $headers = null, $retry $response = $this->parseResponse($channel, $content); - if ($response->statusCode() === self::TOO_MANY_REQUESTS_HTTP_CODE && $retryOnLimit) { + if ($retryOnLimit && $response->statusCode() === self::TOO_MANY_REQUESTS_HTTP_CODE) { $responseHeaders = $response->headers(true); + return $this->retryRequest($responseHeaders, $method, $url, $body, $headers); } @@ -480,7 +487,7 @@ public function makeRequest($method, $url, $body = null, $headers = null, $retry } /** - * Send all saved requests at once + * Send all saved requests at once. * * @param array $requests * @@ -504,11 +511,10 @@ public function makeAllRequests(array $requests = []) $responses = []; $sleepDurations = 0; foreach ($channels as $id => $channel) { - $content = curl_multi_getcontent($channel); $response = $this->parseResponse($channel, $content); - if ($response->statusCode() === self::TOO_MANY_REQUESTS_HTTP_CODE && $requests[$id]['retryOnLimit']) { + if ($requests[$id]['retryOnLimit'] && $response->statusCode() === self::TOO_MANY_REQUESTS_HTTP_CODE) { $headers = $response->headers(true); $sleepDurations = max($sleepDurations, $headers['X-Ratelimit-Reset'] - time()); $requestData = [ @@ -531,6 +537,7 @@ public function makeAllRequests(array $requests = []) sleep($sleepDurations > 0 ? $sleepDurations : 0); $responses = array_merge($responses, $this->makeAllRequests($retryRequests)); } + return $responses; } @@ -557,20 +564,22 @@ public function _($name = null) /** * Dynamically add method calls to the url, then call a method. - * (e.g. client.name.name.method()) + * (e.g. client.name.name.method()). * * @param string $name name of the dynamic method call or HTTP verb * @param array $args parameters passed with the method call * * @return Client|Response|Response[]|null object + * * @throws InvalidRequest */ public function __call($name, $args) { - $name = strtolower($name); + $name = mb_strtolower($name); if ($name === 'version') { $this->version = $args[0]; + return $this->_(); } @@ -579,7 +588,7 @@ public function __call($name, $args) return $this->makeAllRequests(); } - if (in_array($name, $this->methods, true)) { + if (\in_array($name, $this->methods, true)) { $body = isset($args[0]) ? $args[0] : null; $queryParams = isset($args[1]) ? $args[1] : null; $url = $this->buildUrl($queryParams); @@ -590,6 +599,7 @@ public function __call($name, $args) // save request to be sent later $requestData = ['method' => $name, 'url' => $url, 'body' => $body, 'headers' => $headers]; $this->savedRequests[] = $this->createSavedRequest($requestData, $retryOnLimit); + return null; } diff --git a/lib/Exception/InvalidRequest.php b/lib/Exception/InvalidRequest.php index 07c8e33..21ae2ac 100644 --- a/lib/Exception/InvalidRequest.php +++ b/lib/Exception/InvalidRequest.php @@ -6,17 +6,14 @@ * Class InvalidHttpRequest * * Thrown when invalid payload was constructed, which could not reach SendGrid server. - * - * @package SendGrid\Exception */ class InvalidRequest extends \Exception { public function __construct( - $message = "", + $message = '', $code = 0, \Exception $previous = null - ) - { + ) { $message = 'Could not send request to server. ' . 'CURL error ' . $code . ': ' . $message; parent::__construct($message, $code, $previous); diff --git a/lib/Response.php b/lib/Response.php index 9ee80da..376af50 100644 --- a/lib/Response.php +++ b/lib/Response.php @@ -27,11 +27,11 @@ class Response protected $headers; /** - * Setup the response data + * Setup the response data. * - * @param int $statusCode the status code. - * @param string $body the response body. - * @param array $headers an array of response headers. + * @param int $statusCode the status code + * @param string $body the response body + * @param array $headers an array of response headers */ public function __construct($statusCode = 200, $body = '', array $headers = []) { @@ -41,7 +41,7 @@ public function __construct($statusCode = 200, $body = '', array $headers = []) } /** - * The status code + * The status code. * * @return int */ @@ -51,7 +51,7 @@ public function statusCode() } /** - * The response body + * The response body. * * @return string */ @@ -61,7 +61,7 @@ public function body() } /** - * The response headers + * The response headers. * * @param bool $assoc * @@ -77,19 +77,18 @@ public function headers($assoc = false) } /** - * Returns response headers as associative array - * - * @param array $headers - * - * @return array - */ + * Returns response headers as associative array. + * + * @param array $headers + * + * @return array + */ private function prettifyHeaders(array $headers) { return array_reduce( array_filter($headers), - function ($result, $header) { - - if (false === strpos($header, ':')) { + static function ($result, $header) { + if (mb_strpos($header, ':') === false) { $result['Status'] = trim($header); return $result; diff --git a/test/unit/ClientTest.php b/test/unit/ClientTest.php index 4f506ec..16e8d25 100644 --- a/test/unit/ClientTest.php +++ b/test/unit/ClientTest.php @@ -79,7 +79,10 @@ public function testGetHost() public function testGetHeaders() { - $client = new Client('https://localhost:4010', ['Content-Type: application/json', 'Authorization: Bearer SG.XXXX']); + $client = new Client( + 'https://localhost:4010', + ['Content-Type: application/json', 'Authorization: Bearer SG.XXXX'] + ); $this->assertSame(['Content-Type: application/json', 'Authorization: Bearer SG.XXXX'], $client->getHeaders()); $client2 = new Client('https://localhost:4010'); @@ -92,7 +95,7 @@ public function testGetVersion() $this->assertSame('/v3', $client->getVersion()); $client = new Client('https://localhost:4010'); - $this->assertSame(null, $client->getVersion()); + $this->assertNull($client->getVersion()); } public function testGetPath() @@ -123,7 +126,7 @@ public function testCurlMulti() $client->get(null, ['limit' => 100, 'offset' => 0]); // returns 3 response object - $this->assertEquals(3, count($client->send())); + $this->assertCount(3, $client->send()); } public function testCreateCurlOptionsWithMethodOnly() @@ -199,8 +202,9 @@ public function testCreateCurlOptionsWithBodyAndHeaders() public function testThrowExceptionOnInvalidCall() { - $this->setExpectedException(InvalidRequest::class); - $client = new Client('invalid://url',['User-Agent: Custom-Client 1.0']); + $this->expectException(InvalidRequest::class); + + $client = new Client('invalid://url', ['User-Agent: Custom-Client 1.0']); $client->get(); }