Skip to content

add phpstan to ci build #74

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Nov 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[*.yml]
indent_size = 2
21 changes: 21 additions & 0 deletions .github/workflows/static.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: Static analysis

on:
push:
branches:
- master
pull_request:

jobs:
phpstan:
name: PHPStan
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v2

- name: PHPStan
uses: docker://oskarstark/phpstan-ga
with:
args: analyze --no-progress
2 changes: 2 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ name: tests

on:
push:
branches:
- master
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this prevents us from running every build twice

pull_request:

jobs:
Expand Down
5 changes: 5 additions & 0 deletions phpstan.neon.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
parameters:
level: 8
paths:
- src
treatPhpDocTypesAsCertain: false
4 changes: 2 additions & 2 deletions src/Cache/Generator/HeaderCacheKeyGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ class HeaderCacheKeyGenerator implements CacheKeyGenerator
/**
* The header names we should take into account when creating the cache key.
*
* @var array
* @var string[]
*/
private $headerNames;

/**
* @param $headerNames
* @param string[] $headerNames
*/
public function __construct(array $headerNames)
{
Expand Down
125 changes: 56 additions & 69 deletions src/CachePlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Http\Client\Common\Plugin\Cache\Listener\CacheListener;
use Http\Message\StreamFactory;
use Http\Promise\FulfilledPromise;
use Http\Promise\Promise;
use Psr\Cache\CacheItemInterface;
use Psr\Cache\CacheItemPoolInterface;
use Psr\Http\Message\RequestInterface;
Expand Down Expand Up @@ -39,20 +40,20 @@ final class CachePlugin implements Plugin
private $streamFactory;

/**
* @var array
* @var mixed[]
*/
private $config;

/**
* Cache directives indicating if a response can not be cached.
*
* @var array
* @var string[]
*/
private $noCacheFlags = ['no-cache', 'private', 'no-store'];

/**
* @param StreamFactory|StreamFactoryInterface $streamFactory
* @param array $config {
* @param mixed[] $config {
*
* @var bool $respect_cache_headers Whether to look at the cache directives or ignore them
* @var int $default_ttl (seconds) If we do not respect cache headers or can't calculate a good ttl, use this
Expand All @@ -61,9 +62,9 @@ final class CachePlugin implements Plugin
* @var int $cache_lifetime (seconds) To support serving a previous stale response when the server answers 304
* we have to store the cache for a longer time than the server originally says it is valid for.
* We store a cache item for $cache_lifetime + max age of the response.
* @var array $methods list of request methods which can be cached
* @var array $blacklisted_paths list of regex for URLs explicitly not to be cached
* @var array $respect_response_cache_directives list of cache directives this plugin will respect while caching responses
* @var string[] $methods list of request methods which can be cached
* @var string[] $blacklisted_paths list of regex for URLs explicitly not to be cached
* @var string[] $respect_response_cache_directives list of cache directives this plugin will respect while caching responses
* @var CacheKeyGenerator $cache_key_generator an object to generate the cache key. Defaults to a new instance of SimpleGenerator
* @var CacheListener[] $cache_listeners an array of objects to act on the response based on the results of the cache check.
* Defaults to an empty array
Expand All @@ -78,7 +79,7 @@ public function __construct(CacheItemPoolInterface $pool, $streamFactory, array
$this->pool = $pool;
$this->streamFactory = $streamFactory;

if (isset($config['respect_cache_headers']) && isset($config['respect_response_cache_directives'])) {
if (\array_key_exists('respect_cache_headers', $config) && \array_key_exists('respect_response_cache_directives', $config)) {
throw new \InvalidArgumentException('You can\'t provide config option "respect_cache_headers" and "respect_response_cache_directives". Use "respect_response_cache_directives" instead.');
}

Expand All @@ -96,14 +97,14 @@ public function __construct(CacheItemPoolInterface $pool, $streamFactory, array
* cache responses with `private` cache directive.
*
* @param StreamFactory|StreamFactoryInterface $streamFactory
* @param array $config For all possible config options see the constructor docs
* @param mixed[] $config For all possible config options see the constructor docs
*
* @return CachePlugin
*/
public static function clientCache(CacheItemPoolInterface $pool, $streamFactory, array $config = [])
{
// Allow caching of private requests
if (isset($config['respect_response_cache_directives'])) {
if (\array_key_exists('respect_response_cache_directives', $config)) {
$config['respect_response_cache_directives'][] = 'no-cache';
$config['respect_response_cache_directives'][] = 'max-age';
$config['respect_response_cache_directives'] = array_unique($config['respect_response_cache_directives']);
Expand All @@ -119,7 +120,7 @@ public static function clientCache(CacheItemPoolInterface $pool, $streamFactory,
* cache responses with the `private`or `no-cache` directives.
*
* @param StreamFactory|StreamFactoryInterface $streamFactory
* @param array $config For all possible config options see the constructor docs
* @param mixed[] $config For all possible config options see the constructor docs
*
* @return CachePlugin
*/
Expand All @@ -128,6 +129,11 @@ public static function serverCache(CacheItemPoolInterface $pool, $streamFactory,
return new self($pool, $streamFactory, $config);
}

/**
* {@inheritdoc}
*
* @return Promise Resolves a PSR-7 Response or fails with an Http\Client\Exception (The same as HttpAsyncClient)
*/
protected function doHandleRequest(RequestInterface $request, callable $next, callable $first)
{
$method = strtoupper($request->getMethod());
Expand All @@ -146,22 +152,24 @@ protected function doHandleRequest(RequestInterface $request, callable $next, ca

if ($cacheItem->isHit()) {
$data = $cacheItem->get();
// The array_key_exists() is to be removed in 2.0.
if (array_key_exists('expiresAt', $data) && (null === $data['expiresAt'] || time() < $data['expiresAt'])) {
// This item is still valid according to previous cache headers
$response = $this->createResponseFromCacheItem($cacheItem);
$response = $this->handleCacheListeners($request, $response, true, $cacheItem);

return new FulfilledPromise($response);
}
if (is_array($data)) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

phpstan does not complain, but CacheItem::get is defined to return mixed. in some situations, the cache might return something that is not an array.

// The array_key_exists() is to be removed in 2.0.
if (array_key_exists('expiresAt', $data) && (null === $data['expiresAt'] || time() < $data['expiresAt'])) {
// This item is still valid according to previous cache headers
$response = $this->createResponseFromCacheItem($cacheItem);
$response = $this->handleCacheListeners($request, $response, true, $cacheItem);

return new FulfilledPromise($response);
}

// Add headers to ask the server if this cache is still valid
if ($modifiedSinceValue = $this->getModifiedSinceHeaderValue($cacheItem)) {
$request = $request->withHeader('If-Modified-Since', $modifiedSinceValue);
}
// Add headers to ask the server if this cache is still valid
if ($modifiedSinceValue = $this->getModifiedSinceHeaderValue($cacheItem)) {
$request = $request->withHeader('If-Modified-Since', $modifiedSinceValue);
}

if ($etag = $this->getETag($cacheItem)) {
$request = $request->withHeader('If-None-Match', $etag);
if ($etag = $this->getETag($cacheItem)) {
$request = $request->withHeader('If-None-Match', $etag);
}
}
}

Expand Down Expand Up @@ -207,22 +215,20 @@ protected function doHandleRequest(RequestInterface $request, callable $next, ca
$this->pool->save($cacheItem);
}

return $this->handleCacheListeners($request, $response, false, isset($cacheItem) ? $cacheItem : null);
return $this->handleCacheListeners($request, $response, false, $cacheItem);
});
}

/**
* Calculate the timestamp when this cache item should be dropped from the cache. The lowest value that can be
* returned is $maxAge.
*
* @param int|null $maxAge
*
* @return int|null Unix system time passed to the PSR-6 cache
*/
private function calculateCacheItemExpiresAfter($maxAge)
private function calculateCacheItemExpiresAfter(?int $maxAge): ?int
{
if (null === $this->config['cache_lifetime'] && null === $maxAge) {
return;
return null;
}

return $this->config['cache_lifetime'] + $maxAge;
Expand All @@ -232,14 +238,12 @@ private function calculateCacheItemExpiresAfter($maxAge)
* Calculate the timestamp when a response expires. After that timestamp, we need to send a
* If-Modified-Since / If-None-Match request to validate the response.
*
* @param int|null $maxAge
*
* @return int|null Unix system time. A null value means that the response expires when the cache item expires
*/
private function calculateResponseExpiresAt($maxAge)
private function calculateResponseExpiresAt(?int $maxAge): ?int
{
if (null === $maxAge) {
return;
return null;
}

return time() + $maxAge;
Expand Down Expand Up @@ -268,10 +272,8 @@ protected function isCacheable(ResponseInterface $response)

/**
* Verify that we can cache this request.
*
* @return bool
*/
private function isCacheableRequest(RequestInterface $request)
private function isCacheableRequest(RequestInterface $request): bool
{
$uri = $request->getUri()->__toString();
foreach ($this->config['blacklisted_paths'] as $regex) {
Expand All @@ -290,7 +292,7 @@ private function isCacheableRequest(RequestInterface $request)
*
* @return bool|string The value of the directive, true if directive without value, false if directive not present
*/
private function getCacheControlDirective(ResponseInterface $response, $name)
private function getCacheControlDirective(ResponseInterface $response, string $name)
{
$headers = $response->getHeader('Cache-Control');
foreach ($headers as $header) {
Expand All @@ -307,22 +309,19 @@ private function getCacheControlDirective(ResponseInterface $response, $name)
return false;
}

/**
* @return string
*/
private function createCacheKey(RequestInterface $request)
private function createCacheKey(RequestInterface $request): string
{
$key = $this->config['cache_key_generator']->generate($request);

return hash($this->config['hash_algo'], $key);
}

/**
* Get a ttl in seconds. It could return null if we do not respect cache headers and got no defaultTtl.
* Get a ttl in seconds.
*
* @return int|null
* Returns null if we do not respect cache headers and got no defaultTtl.
*/
private function getMaxAge(ResponseInterface $response)
private function getMaxAge(ResponseInterface $response): ?int
{
if (!in_array('max-age', $this->config['respect_response_cache_directives'], true)) {
return $this->config['default_ttl'];
Expand All @@ -333,7 +332,7 @@ private function getMaxAge(ResponseInterface $response)
if (!is_bool($maxAge)) {
$ageHeaders = $response->getHeader('Age');
foreach ($ageHeaders as $age) {
return $maxAge - ((int) $age);
return ((int) $maxAge) - ((int) $age);
}

return (int) $maxAge;
Expand All @@ -351,7 +350,7 @@ private function getMaxAge(ResponseInterface $response)
/**
* Configure an options resolver.
*/
private function configureOptions(OptionsResolver $resolver)
private function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'cache_lifetime' => 86400 * 30, // 30 days
Expand Down Expand Up @@ -398,10 +397,7 @@ private function configureOptions(OptionsResolver $resolver)
});
}

/**
* @return ResponseInterface
*/
private function createResponseFromCacheItem(CacheItemInterface $cacheItem)
private function createResponseFromCacheItem(CacheItemInterface $cacheItem): ResponseInterface
{
$data = $cacheItem->get();

Expand All @@ -415,22 +411,18 @@ private function createResponseFromCacheItem(CacheItemInterface $cacheItem)
throw new RewindStreamException('Cannot rewind stream.', 0, $e);
}

$response = $response->withBody($stream);

return $response;
return $response->withBody($stream);
}

/**
* Get the value of the "If-Modified-Since" header.
*
* @return string|null
* Get the value for the "If-Modified-Since" header.
*/
private function getModifiedSinceHeaderValue(CacheItemInterface $cacheItem)
private function getModifiedSinceHeaderValue(CacheItemInterface $cacheItem): ?string
{
$data = $cacheItem->get();
// The isset() is to be removed in 2.0.
if (!isset($data['createdAt'])) {
return;
return null;
}

$modified = new \DateTime('@'.$data['createdAt']);
Expand All @@ -441,33 +433,28 @@ private function getModifiedSinceHeaderValue(CacheItemInterface $cacheItem)

/**
* Get the ETag from the cached response.
*
* @return string|null
*/
private function getETag(CacheItemInterface $cacheItem)
private function getETag(CacheItemInterface $cacheItem): ?string
{
$data = $cacheItem->get();
// The isset() is to be removed in 2.0.
if (!isset($data['etag'])) {
return;
return null;
}

foreach ($data['etag'] as $etag) {
if (!empty($etag)) {
return $etag;
}
}

return null;
}

/**
* Call the cache listeners, if they are set.
*
* @param bool $cacheHit
* @param CacheItemInterface|null $cacheItem
*
* @return ResponseInterface
* Call the registered cache listeners.
*/
private function handleCacheListeners(RequestInterface $request, ResponseInterface $response, $cacheHit, $cacheItem)
private function handleCacheListeners(RequestInterface $request, ResponseInterface $response, bool $cacheHit, ?CacheItemInterface $cacheItem): ResponseInterface
{
foreach ($this->config['cache_listeners'] as $cacheListener) {
$response = $cacheListener->onCacheResponse($request, $response, $cacheHit, $cacheItem);
Expand Down