From 3c5dd1a13304ebe5edbfe50eb046cdd788b78eb9 Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Sun, 19 Jul 2020 12:49:07 +0100 Subject: [PATCH 01/13] Fixed bug in the cookie plugin --- src/Plugin/CookiePlugin.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Plugin/CookiePlugin.php b/src/Plugin/CookiePlugin.php index 2ec5265..6c97b3c 100644 --- a/src/Plugin/CookiePlugin.php +++ b/src/Plugin/CookiePlugin.php @@ -167,13 +167,13 @@ private function createCookie(RequestInterface $request, string $setCookieHeader * * @param string $part A single cookie value in format key=value * - * @return string[] + * @return array{0:string, 1:?string} */ private function createValueKey(string $part): array { $parts = explode('=', $part, 2); $key = trim($parts[0]); - $value = isset($parts[1]) ? trim($parts[1]) : true; + $value = isset($parts[1]) ? trim($parts[1]) : null; return [$key, $value]; } From aa792427edcd959360fd023afbaa87ab9785b048 Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Sun, 19 Jul 2020 12:49:28 +0100 Subject: [PATCH 02/13] Fixed typo in phpdoc --- src/HttpMethodsClient.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/HttpMethodsClient.php b/src/HttpMethodsClient.php index 26c253d..f62b187 100644 --- a/src/HttpMethodsClient.php +++ b/src/HttpMethodsClient.php @@ -31,7 +31,7 @@ final class HttpMethodsClient implements HttpMethodsClientInterface private $streamFactory; /** - * @param RequestFactory|RequestFactoryInterface + * @param RequestFactory|RequestFactoryInterface $requestFactory */ public function __construct(ClientInterface $httpClient, $requestFactory, StreamFactoryInterface $streamFactory = null) { From f62596ae017277a753db4f8706ba3832bc0216e7 Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Sun, 19 Jul 2020 12:49:58 +0100 Subject: [PATCH 03/13] Clarified plugin client factory type information --- src/PluginClientFactory.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/PluginClientFactory.php b/src/PluginClientFactory.php index bbdc5c3..0c91fc3 100644 --- a/src/PluginClientFactory.php +++ b/src/PluginClientFactory.php @@ -16,7 +16,7 @@ final class PluginClientFactory { /** - * @var callable|null + * @var (callable(ClientInterface|HttpAsyncClient, Plugin[], array): PluginClient)|null */ private static $factory; @@ -28,8 +28,10 @@ final class PluginClientFactory * application execution. * * @internal + * + * @param callable(ClientInterface|HttpAsyncClient, Plugin[], array): PluginClient $factory */ - public static function setFactory(callable $factory) + public static function setFactory(callable $factory): void { static::$factory = $factory; } From 09979e6d786509c7e0234e938dbd292d0780300d Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Sun, 19 Jul 2020 12:50:19 +0100 Subject: [PATCH 04/13] Added extra type information to help phpstan --- src/Exception/HttpClientNoMatchException.php | 3 +++ src/HttpClientRouter.php | 6 ++---- src/Plugin/SeekableBodyPlugin.php | 6 ++++++ src/PluginClientBuilder.php | 3 +++ 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/Exception/HttpClientNoMatchException.php b/src/Exception/HttpClientNoMatchException.php index 437467c..682c5dd 100644 --- a/src/Exception/HttpClientNoMatchException.php +++ b/src/Exception/HttpClientNoMatchException.php @@ -14,6 +14,9 @@ */ final class HttpClientNoMatchException extends TransferException { + /** + * @var RequestInterface + */ private $request; public function __construct(string $message, RequestInterface $request, \Exception $previous = null) diff --git a/src/HttpClientRouter.php b/src/HttpClientRouter.php index 78a4e69..040d893 100644 --- a/src/HttpClientRouter.php +++ b/src/HttpClientRouter.php @@ -19,7 +19,7 @@ final class HttpClientRouter implements HttpClientRouterInterface { /** - * @var array + * @var (array{matcher: RequestMatcher, client: FlexibleHttpClient})[] */ private $clients = []; @@ -60,10 +60,8 @@ public function addClient($client, RequestMatcher $requestMatcher): void /** * Choose an HTTP client given a specific request. - * - * @return ClientInterface|HttpAsyncClient */ - private function chooseHttpClient(RequestInterface $request) + private function chooseHttpClient(RequestInterface $request): FlexibleHttpClient { foreach ($this->clients as $client) { if ($client['matcher']->matches($request)) { diff --git a/src/Plugin/SeekableBodyPlugin.php b/src/Plugin/SeekableBodyPlugin.php index 3b37b1c..5c20af3 100644 --- a/src/Plugin/SeekableBodyPlugin.php +++ b/src/Plugin/SeekableBodyPlugin.php @@ -12,8 +12,14 @@ */ abstract class SeekableBodyPlugin implements Plugin { + /** + * @var bool + */ protected $useFileBuffer; + /** + * @var int + */ protected $memoryBufferSize; /** diff --git a/src/PluginClientBuilder.php b/src/PluginClientBuilder.php index 87ee280..8746498 100644 --- a/src/PluginClientBuilder.php +++ b/src/PluginClientBuilder.php @@ -30,6 +30,9 @@ public function addPlugin(Plugin $plugin, int $priority = 0): self return $this; } + /** + * @param mixed $value + */ public function setOption(string $name, $value): self { $this->options[$name] = $value; From 07ce778f3dc19786da623534ad1871ba09ab8fcc Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Sun, 19 Jul 2020 12:50:54 +0100 Subject: [PATCH 05/13] Don't temprarily violate property types in the flexible http client --- src/FlexibleHttpClient.php | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/FlexibleHttpClient.php b/src/FlexibleHttpClient.php index 1a602c4..c1e327f 100644 --- a/src/FlexibleHttpClient.php +++ b/src/FlexibleHttpClient.php @@ -30,15 +30,7 @@ public function __construct($client) ); } - $this->httpClient = $client; - $this->httpAsyncClient = $client; - - if (!($this->httpClient instanceof ClientInterface)) { - $this->httpClient = new EmulatedHttpClient($this->httpClient); - } - - if (!($this->httpAsyncClient instanceof HttpAsyncClient)) { - $this->httpAsyncClient = new EmulatedHttpAsyncClient($this->httpAsyncClient); - } + $this->httpClient = $client instanceof ClientInterface ? $client : new EmulatedHttpClient($client); + $this->httpAsyncClient = $client instanceof HttpAsyncClient ? $client : new EmulatedHttpAsyncClient($client); } } From 8151f0fd36003831f797912434274e3cae0c99f8 Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Sun, 19 Jul 2020 12:51:41 +0100 Subject: [PATCH 06/13] Removed redundent information --- src/HttpClientPool/HttpClientPool.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/HttpClientPool/HttpClientPool.php b/src/HttpClientPool/HttpClientPool.php index 0665aa7..d6d1777 100644 --- a/src/HttpClientPool/HttpClientPool.php +++ b/src/HttpClientPool/HttpClientPool.php @@ -25,13 +25,14 @@ abstract class HttpClientPool implements HttpClientPoolInterface /** * Add a client to the pool. * - * @param ClientInterface|HttpAsyncClient|HttpClientPoolItem $client + * @param ClientInterface|HttpAsyncClient $client */ public function addHttpClient($client): void { - if (!$client instanceof ClientInterface && !$client instanceof HttpAsyncClient && !$client instanceof HttpClientPoolItem) { + // no need to check for HttpClientPoolItem here, since it extends the other interfaces + if (!$client instanceof ClientInterface && !$client instanceof HttpAsyncClient) { throw new \TypeError( - sprintf('%s::addHttpClient(): Argument #1 ($client) must be of type %s|%s|%s, %s given', self::class, ClientInterface::class, HttpAsyncClient::class, HttpClientPoolItem::class, get_debug_type($client)) + sprintf('%s::addHttpClient(): Argument #1 ($client) must be of type %s|%s, %s given', self::class, ClientInterface::class, HttpAsyncClient::class, get_debug_type($client)) ); } From 8e4b661b710359f9bd135de1aa1e812489fb0842 Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Sun, 19 Jul 2020 12:59:42 +0100 Subject: [PATCH 07/13] Removed unreachable plugin client code --- src/PluginClient.php | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/src/PluginClient.php b/src/PluginClient.php index 0d330b1..bfd9746 100644 --- a/src/PluginClient.php +++ b/src/PluginClient.php @@ -4,11 +4,8 @@ namespace Http\Client\Common; -use Http\Client\Exception as HttplugException; use Http\Client\HttpAsyncClient; use Http\Client\HttpClient; -use Http\Client\Promise\HttpFulfilledPromise; -use Http\Client\Promise\HttpRejectedPromise; use Psr\Http\Client\ClientInterface; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; @@ -24,7 +21,7 @@ final class PluginClient implements HttpClient, HttpAsyncClient /** * An HTTP async client. * - * @var HttpAsyncClient|HttpClient + * @var HttpAsyncClient */ private $client; @@ -71,22 +68,7 @@ public function __construct($client, array $plugins = [], array $options = []) */ public function sendRequest(RequestInterface $request): ResponseInterface { - // If we don't have an http client, use the async call - if (!($this->client instanceof ClientInterface)) { - return $this->sendAsyncRequest($request)->wait(); - } - - // Else we want to use the synchronous call of the underlying client, and not the async one in the case - // we have both an async and sync call - $pluginChain = $this->createPluginChain($this->plugins, function (RequestInterface $request) { - try { - return new HttpFulfilledPromise($this->client->sendRequest($request)); - } catch (HttplugException $exception) { - return new HttpRejectedPromise($exception); - } - }); - - return $pluginChain($request)->wait(); + return $this->sendAsyncRequest($request)->wait(); } /** From dd7952e0b5ec1336ffb1fc96d2f5dc38cc236eb3 Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Sun, 19 Jul 2020 12:59:57 +0100 Subject: [PATCH 08/13] Cast null to string for date parsing --- src/Plugin/CookiePlugin.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Plugin/CookiePlugin.php b/src/Plugin/CookiePlugin.php index 6c97b3c..d0c862b 100644 --- a/src/Plugin/CookiePlugin.php +++ b/src/Plugin/CookiePlugin.php @@ -117,7 +117,7 @@ private function createCookie(RequestInterface $request, string $setCookieHeader switch (strtolower($key)) { case 'expires': try { - $expires = CookieUtil::parseDate($value); + $expires = CookieUtil::parseDate((string) $value); } catch (UnexpectedValueException $e) { throw new TransferException( sprintf( From 5ea39f22f729da4be0f95d767fd5c415e3c3f115 Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Sun, 19 Jul 2020 13:00:09 +0100 Subject: [PATCH 09/13] Added additional type to help phpstan --- src/Deferred.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Deferred.php b/src/Deferred.php index 02fcc29..ef2b309 100644 --- a/src/Deferred.php +++ b/src/Deferred.php @@ -146,6 +146,7 @@ public function wait($unwrap = true) return $this->value; } + /** @var ClientExceptionInterface */ throw $this->failure; } } From 6ed68c440cada39b19f32fbeba383b70e9b9e864 Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Sun, 19 Jul 2020 13:02:34 +0100 Subject: [PATCH 10/13] Run phpstan on actions --- .gitattributes | 29 +++++++++++++++-------------- .github/workflows/static.yml | 21 +++++++++++++++++++++ .gitignore | 1 + phpstan.neon.dist | 26 ++++++++++++++++++++++++++ 4 files changed, 63 insertions(+), 14 deletions(-) create mode 100644 .github/workflows/static.yml create mode 100644 phpstan.neon.dist diff --git a/.gitattributes b/.gitattributes index e6f2cb1..9e28cc7 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,14 +1,15 @@ -.editorconfig export-ignore -.gitattributes export-ignore -/.github/ export-ignore -.gitignore export-ignore -/.php_cs export-ignore -/.scrutinizer.yml export-ignore -/.styleci.yml export-ignore -/behat.yml.dist export-ignore -/features/ export-ignore -/phpspec.ci.yml export-ignore -/phpspec.yml.dist export-ignore -/phpunit.xml.dist export-ignore -/spec/ export-ignore -/tests/ export-ignore +.editorconfig export-ignore +.gitattributes export-ignore +/.github/ export-ignore +.gitignore export-ignore +/.php_cs export-ignore +/.scrutinizer.yml export-ignore +/.styleci.yml export-ignore +/behat.yml.dist export-ignore +/features/ export-ignore +/phpspec.ci.yml export-ignore +/phpspec.yml.dist export-ignore +/phpunit.xml.dist export-ignore +/phpstan.neon.dist export-ignore +/spec/ export-ignore +/tests/ export-ignore diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml new file mode 100644 index 0000000..c8e6274 --- /dev/null +++ b/.github/workflows/static.yml @@ -0,0 +1,21 @@ +name: static + +on: + push: + pull_request: + +jobs: + phpstan: + name: PHPStan + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: PHPStan + uses: OskarStark/phpstan-ga@0.12.32 + env: + REQUIRE_DEV: false + with: + args: analyze --no-progress diff --git a/.gitignore b/.gitignore index 8ce13f3..bfae00b 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ /composer.lock /phpspec.yml /phpunit.xml +/phpstan.neon /vendor/ diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 0000000..8004d07 --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,26 @@ +parameters: + level: max + checkMissingIterableValueType: false + treatPhpDocTypesAsCertain: false + paths: + - src + ignoreErrors: + - + message: "#^Strict comparison using \\!\\=\\= between null and null will always evaluate to false\\.$#" + count: 1 + path: src/HttpMethodsClient.php + + - + message: "#^Cannot call method createStream\\(\\) on Psr\\\\Http\\\\Message\\\\StreamFactoryInterface\\|null\\.$#" + count: 1 + path: src/HttpMethodsClient.php + + - + message: "#^Method Http\\\\Client\\\\Common\\\\Plugin\\\\Journal\\:\\:addSuccess\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/Plugin/Journal.php + + - + message: "#^Method Http\\\\Client\\\\Common\\\\Plugin\\\\Journal\\:\\:addFailure\\(\\) has no return typehint specified\\.$#" + count: 1 + path: src/Plugin/Journal.php From 38077500a284d2c51fedd4b7595bbae6cc46d5bc Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Sun, 19 Jul 2020 13:24:31 +0100 Subject: [PATCH 11/13] Revert "Removed unreachable plugin client code" This reverts commit 8e4b661b710359f9bd135de1aa1e812489fb0842. --- src/PluginClient.php | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/PluginClient.php b/src/PluginClient.php index bfd9746..0d330b1 100644 --- a/src/PluginClient.php +++ b/src/PluginClient.php @@ -4,8 +4,11 @@ namespace Http\Client\Common; +use Http\Client\Exception as HttplugException; use Http\Client\HttpAsyncClient; use Http\Client\HttpClient; +use Http\Client\Promise\HttpFulfilledPromise; +use Http\Client\Promise\HttpRejectedPromise; use Psr\Http\Client\ClientInterface; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; @@ -21,7 +24,7 @@ final class PluginClient implements HttpClient, HttpAsyncClient /** * An HTTP async client. * - * @var HttpAsyncClient + * @var HttpAsyncClient|HttpClient */ private $client; @@ -68,7 +71,22 @@ public function __construct($client, array $plugins = [], array $options = []) */ public function sendRequest(RequestInterface $request): ResponseInterface { - return $this->sendAsyncRequest($request)->wait(); + // If we don't have an http client, use the async call + if (!($this->client instanceof ClientInterface)) { + return $this->sendAsyncRequest($request)->wait(); + } + + // Else we want to use the synchronous call of the underlying client, and not the async one in the case + // we have both an async and sync call + $pluginChain = $this->createPluginChain($this->plugins, function (RequestInterface $request) { + try { + return new HttpFulfilledPromise($this->client->sendRequest($request)); + } catch (HttplugException $exception) { + return new HttpRejectedPromise($exception); + } + }); + + return $pluginChain($request)->wait(); } /** From 0c0f1be74823f1fddd6f5dede06af436c0337c72 Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Sun, 19 Jul 2020 13:30:56 +0100 Subject: [PATCH 12/13] Clarified things --- phpstan.neon.dist | 5 +++++ src/PluginChain.php | 8 ++++---- src/PluginClient.php | 14 +++++++++----- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 8004d07..004d2f5 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -24,3 +24,8 @@ parameters: message: "#^Method Http\\\\Client\\\\Common\\\\Plugin\\\\Journal\\:\\:addFailure\\(\\) has no return typehint specified\\.$#" count: 1 path: src/Plugin/Journal.php + + - + message: "#^Call to an undefined method Http\\\\Client\\\\HttpAsyncClient\\:\\:sendRequest\\(\\)\\.$#" + count: 1 + path: src/PluginClient.php diff --git a/src/PluginChain.php b/src/PluginChain.php index 220ba36..db72332 100644 --- a/src/PluginChain.php +++ b/src/PluginChain.php @@ -14,7 +14,7 @@ final class PluginChain /** @var Plugin[] */ private $plugins; - /** @var callable */ + /** @var callable(RequestInterface): Promise */ private $clientCallable; /** @var int */ @@ -24,9 +24,9 @@ final class PluginChain private $restarts = 0; /** - * @param Plugin[] $plugins A plugin chain - * @param callable $clientCallable Callable making the HTTP call - * @param array $options { + * @param Plugin[] $plugins A plugin chain + * @param callable(RequestInterface): Promise $clientCallable Callable making the HTTP call + * @param array $options { * * @var int $max_restarts * } diff --git a/src/PluginClient.php b/src/PluginClient.php index 0d330b1..f3c9b23 100644 --- a/src/PluginClient.php +++ b/src/PluginClient.php @@ -9,6 +9,7 @@ use Http\Client\HttpClient; use Http\Client\Promise\HttpFulfilledPromise; use Http\Client\Promise\HttpRejectedPromise; +use Http\Promise\Promise; use Psr\Http\Client\ClientInterface; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; @@ -24,7 +25,7 @@ final class PluginClient implements HttpClient, HttpAsyncClient /** * An HTTP async client. * - * @var HttpAsyncClient|HttpClient + * @var HttpAsyncClient */ private $client; @@ -71,13 +72,13 @@ public function __construct($client, array $plugins = [], array $options = []) */ public function sendRequest(RequestInterface $request): ResponseInterface { - // If we don't have an http client, use the async call - if (!($this->client instanceof ClientInterface)) { + // If the client doesn't support sync calls, call async + if (!$this->client instanceof ClientInterface) { return $this->sendAsyncRequest($request)->wait(); } - // Else we want to use the synchronous call of the underlying client, and not the async one in the case - // we have both an async and sync call + // Else we want to use the synchronous call of the underlying client, + // and not the async one in the case we have both an async and sync call $pluginChain = $this->createPluginChain($this->plugins, function (RequestInterface $request) { try { return new HttpFulfilledPromise($this->client->sendRequest($request)); @@ -121,9 +122,12 @@ private function configure(array $options = []): array * * @param Plugin[] $plugins A plugin chain * @param callable $clientCallable Callable making the HTTP call + * + * @return callable(RequestInterface): Promise */ private function createPluginChain(array $plugins, callable $clientCallable): callable { + /** @var callable(RequestInterface): Promise */ return new PluginChain($plugins, $clientCallable, $this->options); } } From 9b7938fa6cac5a3b7642720cb5b0d8676943db1b Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Sun, 19 Jul 2020 13:33:52 +0100 Subject: [PATCH 13/13] Update .php_cs.dist --- .php_cs.dist | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.php_cs.dist b/.php_cs.dist index 3a0d4fc..24e9bb9 100644 --- a/.php_cs.dist +++ b/.php_cs.dist @@ -18,7 +18,7 @@ return PhpCsFixer\Config::create() 'syntax' => 'short', ], 'no_empty_phpdoc' => true, - 'no_superfluous_phpdoc_tags' => true, + 'phpdoc_to_comment' => false, 'single_line_throw' => false, ]) ->setFinder($finder);