diff --git a/composer.json b/composer.json index 4c03b298..67c8fac9 100644 --- a/composer.json +++ b/composer.json @@ -10,23 +10,20 @@ ], "require": { "php": "^7.2 || ^8.0", - "bear/resource": "^1.x-dev", - "bear/sunday": "1.x-dev", + "bear/resource": "^1.15", + "bear/sunday": "^1.5", "doctrine/annotations": "^1.8", "doctrine/cache": "^1.10", "mobiledetect/mobiledetectlib": "^2.8", - "ray/aop": "2.x-dev", - "ray/di": "2.x-dev" - }, - "config": { - "sort-packages": true + "ray/aop": "^2.10", + "ray/di": "2.11" }, "require-dev": { "ext-redis": "*", - "bamarni/composer-bin-plugin": "^1.4", - "koriym/attributes": "^0.2", "phpunit/phpunit": "^9.5", - "symfony/process": "^4.3" + "symfony/process": "^4.3", + "koriym/attributes": "^1.0", + "bamarni/composer-bin-plugin": "^1.4" }, "autoload": { "psr-4": { @@ -45,19 +42,29 @@ "tests/Fake/fake-app/src" ] }, - "files": ["tests/Fake/Memcached.php", "tests/Fake/Redis.php", "tests/syslog.php"] + "files": [ + "tests/Fake/Memcached.php", + "tests/Fake/Redis.php", + "tests/Fake/RedisException.php", + "tests/syslog.php" + ] + }, + "config": { + "sort-packages": true }, "scripts" :{ "post-install-cmd": ["@composer bin all install --ansi"], "post-update-cmd": ["@composer bin all update --ansi"], - "test": ["phpunit"], + "test": ["./vendor/bin/phpunit"], "tests": ["@cs", "@sa", "@test"], - "sa": ["./vendor/bin/phpstan analyse -l max src tests -c phpstan.neon --no-progress", "psalm --show-info=false"], - "coverage": ["php -dzend_extension=xdebug.so ./vendor/bin/phpunit --coverage-text --coverage-html=build/coverage"], - "cs": ["./vendor/bin/phpcs --standard=./phpcs.xml src"], - "cs-fix": ["./vendor/bin/phpcbf src"], - "metrics": ["./vendor/bin/phpmetrics --report-html=build/metrics --exclude=Exception --log-junit=build/junit.xml --junit=build/junit.xml src"], - "build": ["@cs", "@sa", "@coverage", "@metrics"], - "baseline": ["phpstan analyse --configuration phpstan.neon src/ tests/ --generate-baseline", "vendor/bin/psalm --set-baseline=psalm.baseline.xml"] + "coverage": ["php -dzend_extension=xdebug.so -dxdebug.mode=coverage ./vendor/bin/phpunit --coverage-text --coverage-html=build/coverage"], + "pcov": ["php -dextension=pcov.so -d pcov.enabled=1 ./vendor/bin/phpunit --coverage-text --coverage-html=build/coverage --coverage-clover=coverage.xml"], + "cs": ["./vendor/bin/phpcs"], + "cs-fix": ["./vendor/bin/phpcbf src tests"], + "clean": ["./vendor/bin/phpstan clear-result-cache", "./vendor/bin/psalm --clear-cache", "rm -rf tests/tmp/*.php"], + "sa": ["./vendor/bin/phpstan analyse -c phpstan.neon", "psalm --show-info=true"], + "metrics": ["./vendor/bin/phpmetrics --report-html=build/metrics --exclude=Exception --junit=build/junit.xml src"], + "phpmd": ["./vendor/bin/phpmd --exclude src/Annotation src text ./phpmd.xml"], + "build": ["@cs", "@sa", "@pcov", "@metrics"] } } diff --git a/phpcs.xml b/phpcs.xml index 2169f08b..c5599731 100755 --- a/phpcs.xml +++ b/phpcs.xml @@ -1,19 +1,76 @@ - - - - - - - + + + + + + + + + + + + + + + + src + tests + */tests/tmp/* + + + + + + + + + + + + + + + + + + + + + + + + + + */tests/Fake/* - - - - - - - - + + + + + + + + + + + + tests/* + + + + + + + + + + + + + + */Fake/* + */tmp/* diff --git a/phpstan.neon b/phpstan.neon index 2b97143a..28bad942 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,5 +1,3 @@ -includes: - - phpstan-baseline.neon parameters: level: max paths: diff --git a/psalm.xml b/psalm.xml index c4991e4e..b0e50c20 100644 --- a/psalm.xml +++ b/psalm.xml @@ -1,6 +1,6 @@ getThis(); + assert($ro instanceof ResourceObject); try { $stored = $this->repository->get($ro->uri); - } catch (LogicException | RuntimeException $e) { + } catch (LogicException $e) { throw $e; - } catch (\Exception $e) { - $this->triggerError($e); + } catch (Throwable $e) { + $this->triggerWarning($e); return $invocation->proceed(); } + if ($stored) { [$ro->uri, $ro->code, $ro->headers, $ro->body, $ro->view] = $stored; return $ro; } + /** @psalm-suppress MixedAssignment */ $ro = $invocation->proceed(); assert($ro instanceof ResourceObject); try { $ro->code === 200 ? $this->repository->put($ro) : $this->repository->purge($ro->uri); - } catch (LogicException | RuntimeException $e) { + } catch (LogicException $e) { throw $e; - } catch (\Exception $e) { - $this->triggerError($e); + } catch (Throwable $e) { + $this->triggerWarning($e); } return $ro; } /** - * Trigger error when cache server is down instead of throwing the exception + * Trigger warning + * + * When the cache server is down, it will issue a warning rather than an exception to continue service. */ - private function triggerError(\Exception $e) : void + private function triggerWarning(Throwable $e): void { $message = sprintf('%s: %s in %s:%s', get_class($e), $e->getMessage(), $e->getFile(), $e->getLine()); trigger_error($message, E_USER_WARNING); diff --git a/src/CacheVersionModule.php b/src/CacheVersionModule.php index 19ec6638..4a55e964 100644 --- a/src/CacheVersionModule.php +++ b/src/CacheVersionModule.php @@ -9,12 +9,10 @@ class CacheVersionModule extends AbstractModule { - /** - * @var string - */ + /** @var string */ private $version; - public function __construct(string $cacheVersion, AbstractModule $module = null) + public function __construct(string $cacheVersion, ?AbstractModule $module = null) { $this->version = $cacheVersion; parent::__construct($module); diff --git a/src/CliHttpCache.php b/src/CliHttpCache.php index ff1488a9..8d2f7857 100644 --- a/src/CliHttpCache.php +++ b/src/CliHttpCache.php @@ -4,15 +4,19 @@ namespace BEAR\QueryRepository; -use function assert; use BEAR\Sunday\Extension\Transfer\HttpCacheInterface; + use function is_string; +use function parse_str; +use function sprintf; +use function str_replace; +use function strtoupper; + +use const PHP_EOL; final class CliHttpCache implements HttpCacheInterface { - /** - * @var ResourceStorageInterface - */ + /** @var ResourceStorageInterface */ private $storage; public function __construct(ResourceStorageInterface $storage) @@ -23,11 +27,12 @@ public function __construct(ResourceStorageInterface $storage) /** * {@inheritdoc} */ - public function isNotModified(array $server) : bool + public function isNotModified(array $server): bool { - if (isset($server['argc']) && $server['argc'] === 4) { - assert(isset($server['argv'][3]) && is_string($server['argv'][3])); - $server = $this->setRequestHeaders($server, $server['argv'][3]); + /** @var array{HTTP_IF_NONE_MATCH: string}|array{argc: int, argv: array} $server */ + $hasRequestHeaderInCli = isset($server['argc']) && $server['argc'] === 4 && isset($server['argv']); + if ($hasRequestHeaderInCli) { + $server = $this->setRequestHeaders($server, $server['argv'][3]); // @phpstan-ignore-line } return isset($server['HTTP_IF_NONE_MATCH']) && is_string($server['HTTP_IF_NONE_MATCH']) && $this->storage->hasEtag($server['HTTP_IF_NONE_MATCH']); @@ -43,10 +48,14 @@ public function transfer() echo '304 Not Modified' . PHP_EOL . PHP_EOL; } - private function setRequestHeaders(array $server, string $query) : array + /** + * @param array $server + * + * @return array + */ + private function setRequestHeaders(array $server, string $query): array { - \parse_str($query, $headers); - /** @var array $headers */ + parse_str($query, $headers); foreach ($headers as $key => $header) { $server[$this->getServerKey($key)] = (string) $header; } @@ -54,8 +63,8 @@ private function setRequestHeaders(array $server, string $query) : array return $server; } - private function getServerKey(string $key) : string + private function getServerKey(string $key): string { - return sprintf('HTTP_%s', strtoupper(\str_replace('-', '_', $key))); + return sprintf('HTTP_%s', strtoupper(str_replace('-', '_', $key))); } } diff --git a/src/CommandInterceptor.php b/src/CommandInterceptor.php index d5f7b0a2..2fc14056 100644 --- a/src/CommandInterceptor.php +++ b/src/CommandInterceptor.php @@ -11,11 +11,11 @@ use Ray\Aop\MethodInterceptor; use Ray\Aop\MethodInvocation; +use function get_class; + class CommandInterceptor implements MethodInterceptor { - /** - * @var CommandInterface[] - */ + /** @var CommandInterface[] */ private $commands = []; /** @@ -38,7 +38,7 @@ public function invoke(MethodInvocation $invocation) /** @psalm-suppress MixedAssignment */ $ro = $invocation->proceed(); if (! $ro instanceof ResourceObject) { - throw new ReturnValueIsNotResourceObjectException(\get_class($invocation->getThis())); + throw new ReturnValueIsNotResourceObjectException(get_class($invocation->getThis())); } if ($ro->code >= Code::BAD_REQUEST) { diff --git a/src/CommandsProvider.php b/src/CommandsProvider.php index 5323f7f7..2f9c09cb 100644 --- a/src/CommandsProvider.php +++ b/src/CommandsProvider.php @@ -9,14 +9,10 @@ class CommandsProvider implements ProviderInterface { - /** - * @var QueryRepositoryInterface - */ + /** @var QueryRepositoryInterface */ private $repository; - /** - * @var ResourceInterface - */ + /** @var ResourceInterface */ private $resource; public function __construct( @@ -34,7 +30,7 @@ public function get() { return [ new RefreshSameCommand($this->repository), - new RefreshAnnotatedCommand($this->repository, $this->resource) + new RefreshAnnotatedCommand($this->repository, $this->resource), ]; } } diff --git a/src/EtagSetter.php b/src/EtagSetter.php index 9b6273fd..2c32ba81 100644 --- a/src/EtagSetter.php +++ b/src/EtagSetter.php @@ -6,24 +6,32 @@ use BEAR\RepositoryModule\Annotation\HttpCache; use BEAR\Resource\ResourceObject; + +use function assert; +use function crc32; +use function get_class; +use function gmdate; use function is_array; +use function serialize; +use function time; final class EtagSetter implements EtagSetterInterface { /** * {@inheritdoc} */ - public function __invoke(ResourceObject $ro, int $time = null, HttpCache $httpCache = null) + public function __invoke(ResourceObject $ro, ?int $time = null, ?HttpCache $httpCache = null) { - $time = $time === null ? \time() : $time; + $time = $time ?? time(); if ($ro->code !== 200) { return; } + $ro->headers['ETag'] = $this->getEtag($ro, $httpCache); - $ro->headers['Last-Modified'] = \gmdate('D, d M Y H:i:s', $time) . ' GMT'; + $ro->headers['Last-Modified'] = gmdate('D, d M Y H:i:s', $time) . ' GMT'; } - public function getEtagByPartialBody(HttpCache $httpCacche, ResourceObject $ro) : string + public function getEtagByPartialBody(HttpCache $httpCacche, ResourceObject $ro): string { $etag = ''; assert(is_array($ro->body)); @@ -36,9 +44,9 @@ public function getEtagByPartialBody(HttpCache $httpCacche, ResourceObject $ro) return $etag; } - public function getEtagByEitireView(ResourceObject $ro) : string + public function getEtagByEitireView(ResourceObject $ro): string { - return \get_class($ro) . \serialize($ro->view); + return get_class($ro) . serialize($ro->view); } /** @@ -48,10 +56,10 @@ public function getEtagByEitireView(ResourceObject $ro) : string * * @see https://cloud.google.com/storage/docs/hashes-etags */ - private function getEtag(ResourceObject $ro, HttpCache $httpCache = null) : string + private function getEtag(ResourceObject $ro, ?HttpCache $httpCache = null): string { $etag = $httpCache instanceof HttpCache && $httpCache->etag ? $this->getEtagByPartialBody($httpCache, $ro) : $this->getEtagByEitireView($ro); - return (string) \crc32(\get_class($ro) . $etag . (string) $ro->uri); + return (string) crc32(get_class($ro) . $etag . (string) $ro->uri); } } diff --git a/src/EtagSetterInterface.php b/src/EtagSetterInterface.php index f4f722f4..dcd954cc 100644 --- a/src/EtagSetterInterface.php +++ b/src/EtagSetterInterface.php @@ -14,5 +14,5 @@ interface EtagSetterInterface * * @return void */ - public function __invoke(ResourceObject $ro, int $time = null, HttpCache $httpCache = null); + public function __invoke(ResourceObject $ro, ?int $time = null, ?HttpCache $httpCache = null); } diff --git a/src/Exception/RedisConnectionException.php b/src/Exception/RedisConnectionException.php index 764897e4..8d96bc6a 100644 --- a/src/Exception/RedisConnectionException.php +++ b/src/Exception/RedisConnectionException.php @@ -4,6 +4,8 @@ namespace BEAR\QueryRepository\Exception; -class RedisConnectionException extends \RuntimeException +use RuntimeException; + +class RedisConnectionException extends RuntimeException { } diff --git a/src/Expiry.php b/src/Expiry.php index 2026e947..9b653498 100644 --- a/src/Expiry.php +++ b/src/Expiry.php @@ -4,10 +4,14 @@ namespace BEAR\QueryRepository; +use ArrayObject; + /** * Expiry time value object + * + * @extends ArrayObject */ -class Expiry extends \ArrayObject +class Expiry extends ArrayObject { public function __construct(int $short = 60, int $medium = 3600, int $long = 86400, int $never = 31536000) { diff --git a/src/HttpCache.php b/src/HttpCache.php index dadead82..e8ca48f0 100644 --- a/src/HttpCache.php +++ b/src/HttpCache.php @@ -6,6 +6,8 @@ use BEAR\QueryRepository\HttpCacheInterface as DeprecatedHttpCacheInterface; use BEAR\Sunday\Extension\Transfer\HttpCacheInterface; + +use function http_response_code; use function is_string; /** @@ -13,9 +15,7 @@ */ final class HttpCache implements HttpCacheInterface, DeprecatedHttpCacheInterface { - /** - * @var ResourceStorageInterface - */ + /** @var ResourceStorageInterface */ private $storage; public function __construct(ResourceStorageInterface $storage) @@ -26,7 +26,7 @@ public function __construct(ResourceStorageInterface $storage) /** * {@inheritdoc} */ - public function isNotModified(array $server) : bool + public function isNotModified(array $server): bool { return isset($server['HTTP_IF_NONE_MATCH']) && is_string($server['HTTP_IF_NONE_MATCH']) && $this->storage->hasEtag($server['HTTP_IF_NONE_MATCH']); } @@ -38,6 +38,6 @@ public function isNotModified(array $server) : bool */ public function transfer() { - \http_response_code(304); + http_response_code(304); } } diff --git a/src/HttpCacheInject.php b/src/HttpCacheInject.php index e9111ce6..29bd04d5 100644 --- a/src/HttpCacheInject.php +++ b/src/HttpCacheInject.php @@ -8,9 +8,7 @@ trait HttpCacheInject { - /** - * @var HttpCacheInterface - */ + /** @var HttpCacheInterface */ public $httpCache; /** diff --git a/src/HttpCacheInterceptor.php b/src/HttpCacheInterceptor.php index b79706a4..950b295f 100644 --- a/src/HttpCacheInterceptor.php +++ b/src/HttpCacheInterceptor.php @@ -9,6 +9,8 @@ use Ray\Aop\MethodInterceptor; use Ray\Aop\MethodInvocation; +use function assert; + class HttpCacheInterceptor implements MethodInterceptor { /** diff --git a/src/MobileEtagSetter.php b/src/MobileEtagSetter.php index 6d0afe39..57927ea7 100644 --- a/src/MobileEtagSetter.php +++ b/src/MobileEtagSetter.php @@ -6,17 +6,23 @@ use BEAR\RepositoryModule\Annotation\HttpCache; use BEAR\Resource\ResourceObject; +use Mobile_Detect; + +use function crc32; +use function gmdate; +use function serialize; +use function time; final class MobileEtagSetter implements EtagSetterInterface { - public function __invoke(ResourceObject $ro, int $time = null, HttpCache $httpCache = null) + public function __invoke(ResourceObject $ro, ?int $time = null, ?HttpCache $httpCache = null) { unset($httpCache); // etag] - $ro->headers['ETag'] = (string) \crc32($this->getDevice() . \serialize($ro->view) . \serialize($ro->body)); + $ro->headers['ETag'] = (string) crc32($this->getDevice() . serialize($ro->view) . serialize($ro->body)); // time - $time = $time === null ? \time() : $time; - $ro->headers['Last-Modified'] = \gmdate('D, d M Y H:i:s', $time) . ' GMT'; + $time = $time ?? time(); + $ro->headers['Last-Modified'] = gmdate('D, d M Y H:i:s', $time) . ' GMT'; } /** @@ -26,7 +32,7 @@ public function __invoke(ResourceObject $ro, int $time = null, HttpCache $httpCa */ private function getDevice() { - $detect = new \Mobile_Detect; + $detect = new Mobile_Detect(); return $detect->isMobile() && ! $detect->isTablet() ? 'mobile' : 'pc'; } diff --git a/src/NamespacedCacheProvider.php b/src/NamespacedCacheProvider.php index 1bd64d24..90ebd80c 100644 --- a/src/NamespacedCacheProvider.php +++ b/src/NamespacedCacheProvider.php @@ -12,9 +12,7 @@ class NamespacedCacheProvider implements ProviderInterface { - /** - * @var CacheProvider - */ + /** @var CacheProvider */ private $cache; /** diff --git a/src/QueryRepository.php b/src/QueryRepository.php index c55b88bd..d679067b 100644 --- a/src/QueryRepository.php +++ b/src/QueryRepository.php @@ -4,36 +4,36 @@ namespace BEAR\QueryRepository; -use function assert; use BEAR\QueryRepository\Exception\ExpireAtKeyNotExists; use BEAR\RepositoryModule\Annotation\Cacheable; use BEAR\RepositoryModule\Annotation\HttpCache; use BEAR\Resource\AbstractUri; -use BEAR\Resource\RequestInterface; use BEAR\Resource\ResourceObject; use Doctrine\Common\Annotations\Reader; +use ReflectionClass; +use ReflectionException; + +use function assert; +use function get_class; use function is_array; +use function is_string; +use function sprintf; +use function strpos; +use function strtotime; +use function time; final class QueryRepository implements QueryRepositoryInterface { - /** - * @var ResourceStorageInterface - */ + /** @var ResourceStorageInterface */ private $storage; - /** - * @var Reader - */ + /** @var Reader */ private $reader; - /** - * @var Expiry - */ + /** @var Expiry */ private $expiry; - /** - * @var EtagSetterInterface - */ + /** @var EtagSetterInterface */ private $setEtag; public function __construct( @@ -51,7 +51,7 @@ public function __construct( /** * {@inheritdoc} * - * @throws \ReflectionException + * @throws ReflectionException */ public function put(ResourceObject $ro) { @@ -63,6 +63,7 @@ public function put(ResourceObject $ro) if (isset($ro->headers['ETag'])) { $this->storage->updateEtag($ro, $lifeTime); } + $this->setMaxAge($ro, $lifeTime); if ($cacheable instanceof Cacheable && $cacheable->type === 'view') { return $this->saveViewCache($ro, $lifeTime); @@ -77,10 +78,12 @@ public function put(ResourceObject $ro) public function get(AbstractUri $uri) { $data = $this->storage->get($uri); + if ($data === false) { return false; } - $age = \time() - \strtotime($data[2]['Last-Modified']); + + $age = time() - strtotime($data[2]['Last-Modified']); $data[2]['Age'] = $age; return $data; @@ -97,54 +100,24 @@ public function purge(AbstractUri $uri) } /** - * @throws \ReflectionException + * @throws ReflectionException */ - private function getHttpCacheAnnotation(ResourceObject $ro) : ?HttpCache + private function getHttpCacheAnnotation(ResourceObject $ro): ?HttpCache { - $annotation = $this->reader->getClassAnnotation(new \ReflectionClass($ro), HttpCache::class); - if ($annotation instanceof HttpCache || $annotation === null) { - return $annotation; - } - - throw new \LogicException(); + return $this->reader->getClassAnnotation(new ReflectionClass($ro), HttpCache::class); } /** - * @throws \ReflectionException - * * @return ?Cacheable - */ - private function getCacheableAnnotation(ResourceObject $ro) - { - $annotation = $this->reader->getClassAnnotation(new \ReflectionClass($ro), Cacheable::class); - if ($annotation instanceof Cacheable || $annotation === null) { - return $annotation; - } - - throw new \LogicException(); - } - - /** - * @param mixed $body * - * @return mixed + * @throws ReflectionException */ - private function evaluateBody($body) + private function getCacheableAnnotation(ResourceObject $ro): ?Cacheable { - if (! \is_array($body)) { - return $body; - } - /** @psalm-suppress MixedAssignment $item */ - foreach ($body as &$item) { - if ($item instanceof RequestInterface) { - $item = ($item)(); - } - } - - return $body; + return $this->reader->getClassAnnotation(new ReflectionClass($ro), Cacheable::class); } - private function getExpiryTime(ResourceObject $ro, Cacheable $cacheable = null) : int + private function getExpiryTime(ResourceObject $ro, ?Cacheable $cacheable = null): int { if ($cacheable === null) { return 0; @@ -157,17 +130,18 @@ private function getExpiryTime(ResourceObject $ro, Cacheable $cacheable = null) return $cacheable->expirySecond ? $cacheable->expirySecond : (int) $this->expiry[$cacheable->expiry]; } - private function getExpiryAtSec(ResourceObject $ro, Cacheable $cacheable) : int + private function getExpiryAtSec(ResourceObject $ro, Cacheable $cacheable): int { if (! isset($ro->body[$cacheable->expiryAt])) { - $msg = \sprintf('%s::%s', \get_class($ro), $cacheable->expiryAt); + $msg = sprintf('%s::%s', get_class($ro), $cacheable->expiryAt); throw new ExpireAtKeyNotExists($msg); } + assert(is_array($ro->body)); $expiryAt = (string) $ro->body[$cacheable->expiryAt]; - return \strtotime($expiryAt) - \time(); + return strtotime($expiryAt) - time(); } /** @@ -178,7 +152,8 @@ private function setMaxAge(ResourceObject $ro, int $age) if ($age === 0) { return; } - $setMaxAge = \sprintf('max-age=%d', $age); + + $setMaxAge = sprintf('max-age=%d', $age); $noCacheControleHeader = ! isset($ro->headers['Cache-Control']); /** @var array $headers */ $headers = $ro->headers; @@ -187,16 +162,18 @@ private function setMaxAge(ResourceObject $ro, int $age) return; } + $isMaxAgeAlreadyDefined = strpos($headers['Cache-Control'], 'max-age') !== false; if ($isMaxAgeAlreadyDefined) { return; } + if (is_string($ro->headers['Cache-Control'])) { $ro->headers['Cache-Control'] .= ', ' . $setMaxAge; } } - private function saveViewCache(ResourceObject $ro, int $lifeTime) : bool + private function saveViewCache(ResourceObject $ro, int $lifeTime): bool { if (! $ro->view) { $ro->view = $ro->toString(); diff --git a/src/QueryRepositoryAopModule.php b/src/QueryRepositoryAopModule.php index 5104abe7..23a9c315 100644 --- a/src/QueryRepositoryAopModule.php +++ b/src/QueryRepositoryAopModule.php @@ -40,6 +40,7 @@ protected function configure() [RefreshInterceptor::class] ); } + $this->bindInterceptor( $this->matcher->annotatedWith(AbstractCacheControl::class), $this->matcher->startsWith('onGet'), diff --git a/src/QueryRepositoryInterface.php b/src/QueryRepositoryInterface.php index 77fd2d95..dac0cd29 100644 --- a/src/QueryRepositoryInterface.php +++ b/src/QueryRepositoryInterface.php @@ -15,7 +15,7 @@ interface QueryRepositoryInterface public function put(ResourceObject $ro); /** - * @return array|false [$code, $headers, $body, $view] + * @return array{0: AbstractUri, 1: int, 2: non-empty-array, 3: mixed, 4: mixed}|false */ public function get(AbstractUri $uri); diff --git a/src/QueryRepositoryModule.php b/src/QueryRepositoryModule.php index aba84928..998d9ae7 100644 --- a/src/QueryRepositoryModule.php +++ b/src/QueryRepositoryModule.php @@ -37,7 +37,7 @@ protected function configure() $this->bind()->annotatedWith(Commands::class)->toProvider(CommandsProvider::class); $this->bind()->annotatedWith(CacheVersion::class)->toInstance(''); $this->bind(RefreshInterceptor::class); - $this->install(new QueryRepositoryAopModule); + $this->install(new QueryRepositoryAopModule()); $this->bind(ResourceStorageInterface::class)->to(ResourceStorage::class); // BC $this->bind(DeprecatedHttpCacheInterface::class)->to(HttpCache::class); diff --git a/src/RefreshAnnotatedCommand.php b/src/RefreshAnnotatedCommand.php index 502fd026..076fb6c8 100644 --- a/src/RefreshAnnotatedCommand.php +++ b/src/RefreshAnnotatedCommand.php @@ -11,18 +11,15 @@ use BEAR\Resource\ResourceObject; use BEAR\Resource\Uri; use Ray\Aop\MethodInvocation; -use Ray\Aop\ReflectionMethod; + +use function is_array; final class RefreshAnnotatedCommand implements CommandInterface { - /** - * @var QueryRepositoryInterface - */ + /** @var QueryRepositoryInterface */ private $repository; - /** - * @var ResourceInterface - */ + /** @var ResourceInterface */ private $resource; public function __construct( @@ -38,7 +35,6 @@ public function __construct( */ public function command(MethodInvocation $invocation, ResourceObject $ro) { - /** @var ReflectionMethod $method */ $method = $invocation->getMethod(); $annotations = $method->getAnnotations(); foreach ($annotations as $annotation) { @@ -46,9 +42,9 @@ public function command(MethodInvocation $invocation, ResourceObject $ro) } } - private function getUri(ResourceObject $ro, AbstractCommand $annotation) : string + private function getUri(ResourceObject $ro, AbstractCommand $annotation): string { - $body = \is_array($ro->body) ? $ro->body : []; + $body = is_array($ro->body) ? $ro->body : []; $query = $body + $ro->uri->query; return uri_template($annotation->uri, $query); @@ -62,10 +58,12 @@ private function request(ResourceObject $ro, object $annotation) if (! $annotation instanceof AbstractCommand) { return; } + $uri = new Uri($this->getUri($ro, $annotation)); if ($annotation instanceof Purge) { $this->repository->purge($uri); } + if ($annotation instanceof Refresh) { $this->repository->purge($uri); $ro = $this->resource->get((string) $uri); diff --git a/src/RefreshInterceptor.php b/src/RefreshInterceptor.php index c395b915..859ef728 100644 --- a/src/RefreshInterceptor.php +++ b/src/RefreshInterceptor.php @@ -10,11 +10,11 @@ use Ray\Aop\MethodInterceptor; use Ray\Aop\MethodInvocation; +use function get_class; + final class RefreshInterceptor implements MethodInterceptor { - /** - * @var RefreshAnnotatedCommand - */ + /** @var RefreshAnnotatedCommand */ private $command; public function __construct(RefreshAnnotatedCommand $command) @@ -22,12 +22,15 @@ public function __construct(RefreshAnnotatedCommand $command) $this->command = $command; } + /** + * @return ResourceObject + */ public function invoke(MethodInvocation $invocation) { /** @psalm-suppress MixedAssignment */ $ro = $invocation->proceed(); if (! $ro instanceof ResourceObject) { - throw new ReturnValueIsNotResourceObjectException(\get_class($invocation->getThis())); + throw new ReturnValueIsNotResourceObjectException(get_class($invocation->getThis())); } if ($ro->code < Code::BAD_REQUEST) { diff --git a/src/RefreshSameCommand.php b/src/RefreshSameCommand.php index 7455755d..68e7acad 100644 --- a/src/RefreshSameCommand.php +++ b/src/RefreshSameCommand.php @@ -2,17 +2,23 @@ namespace BEAR\QueryRepository; -use function array_values; use BEAR\QueryRepository\Exception\UnmatchedQuery; use BEAR\Resource\ResourceObject; -use function is_callable; use Ray\Aop\MethodInvocation; +use ReflectionException; +use ReflectionMethod; + +use function array_values; +use function call_user_func_array; +use function get_class; +use function is_callable; +use function sprintf; + +// phpcs:ignoreFile SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing -- for call_user_func_array final class RefreshSameCommand implements CommandInterface { - /** - * @var QueryRepositoryInterface - */ + /** @var QueryRepositoryInterface */ private $repository; public function __construct(QueryRepositoryInterface $repository) @@ -29,6 +35,7 @@ public function command(MethodInvocation $invocation, ResourceObject $ro) if ($method === 'onGet' || $method === 'onPost') { return; } + unset($invocation); $getQuery = $this->getQuery($ro); $delUri = clone $ro->uri; @@ -41,26 +48,27 @@ public function command(MethodInvocation $invocation, ResourceObject $ro) $ro->uri->query = $getQuery; $get = [$ro, 'onGet']; if (is_callable($get)) { - \call_user_func_array($get, array_values($getQuery)); + call_user_func_array($get, array_values($getQuery)); } } /** - * @throws \ReflectionException - * * @return array + * + * @throws ReflectionException */ - private function getQuery(ResourceObject $ro) : array + private function getQuery(ResourceObject $ro): array { - $refParameters = (new \ReflectionMethod(\get_class($ro), 'onGet'))->getParameters(); + $refParameters = (new ReflectionMethod(get_class($ro), 'onGet'))->getParameters(); $getQuery = []; $query = $ro->uri->query; foreach ($refParameters as $parameter) { if (! isset($query[$parameter->name])) { throw new UnmatchedQuery(sprintf('%s %s', $ro->uri->method, (string) $ro->uri)); } + /** @psalm-suppress MixedAssignment */ - $getQuery[(string) $parameter->name] = $query[$parameter->name]; + $getQuery[$parameter->name] = $query[$parameter->name]; } return $getQuery; diff --git a/src/ResourceStorage.php b/src/ResourceStorage.php index 9f93cf9b..5ed91fcd 100644 --- a/src/ResourceStorage.php +++ b/src/ResourceStorage.php @@ -9,23 +9,27 @@ use BEAR\Resource\RequestInterface; use BEAR\Resource\ResourceObject; use Doctrine\Common\Cache\CacheProvider; + +use function assert; +use function explode; +use function is_array; use function is_string; +use function sprintf; +use function strtoupper; final class ResourceStorage implements ResourceStorageInterface { /** * Prefix for ETag URI */ - const ETAG_TABLE = 'etag-table-'; + public const ETAG_TABLE = 'etag-table-'; /** * Prefix of ETag value */ - const ETAG_VAL = 'etag-val-'; + public const ETAG_VAL = 'etag-val-'; - /** - * @var CacheProvider - */ + /** @var CacheProvider */ private $cache; /** @@ -39,7 +43,7 @@ public function __construct(CacheProvider $cache) /** * {@inheritdoc} */ - public function hasEtag(string $etag) : bool + public function hasEtag(string $etag): bool { return $this->cache->contains(self::ETAG_VAL . $etag); } @@ -84,16 +88,14 @@ public function deleteEtag(AbstractUri $uri) public function get(AbstractUri $uri) { $varyUri = $this->getVaryUri($uri); - /** @var array{0:string, 1: int, 2:array, 3: mixed, 4: string}|false $roProps */ - $roProps = $this->cache->fetch($varyUri); - return $roProps; + return $this->cache->fetch($varyUri); } /** * {@inheritdoc} */ - public function delete(AbstractUri $uri) : bool + public function delete(AbstractUri $uri): bool { $this->deleteEtag($uri); $varyUri = $this->getVaryUri($uri); @@ -106,14 +108,14 @@ public function delete(AbstractUri $uri) : bool * * @return bool */ - public function saveValue(ResourceObject $ro, int $lifeTime) + public function saveValue(ResourceObject $ro, int $ttl) { /** @psalm-suppress MixedAssignment $body */ $body = $this->evaluateBody($ro->body); $uri = $this->getVaryUri($ro->uri); $val = [$ro->uri, $ro->code, $ro->headers, $body, null]; - return $this->cache->save($uri, $val, $lifeTime); + return $this->cache->save($uri, $val, $ttl); } /** @@ -121,14 +123,14 @@ public function saveValue(ResourceObject $ro, int $lifeTime) * * @return bool */ - public function saveView(ResourceObject $ro, int $lifeTime) + public function saveView(ResourceObject $ro, int $ttl) { /** @psalm-suppress MixedAssignment $body */ $body = $this->evaluateBody($ro->body); $uri = $this->getVaryUri($ro->uri); $val = [$ro->uri, $ro->code, $ro->headers, $body, $ro->view]; - return $this->cache->save($uri, $val, $lifeTime); + return $this->cache->save($uri, $val, $ttl); } /** @@ -138,14 +140,16 @@ public function saveView(ResourceObject $ro, int $lifeTime) */ private function evaluateBody($body) { - if (! \is_array($body)) { + if (! is_array($body)) { return $body; } + /** @psalm-suppress MixedAssignment $item */ foreach ($body as &$item) { if ($item instanceof RequestInterface) { $item = ($item)(); } + if ($item instanceof ResourceObject) { $item->body = $this->evaluateBody($item->body); } @@ -154,17 +158,18 @@ private function evaluateBody($body) return $body; } - private function getVaryUri(AbstractUri $uri) : string + private function getVaryUri(AbstractUri $uri): string { if (! isset($_SERVER['X_VARY'])) { return (string) $uri; } - /** @var string $xvary */ + $xvary = $_SERVER['X_VARY']; - $varys = \explode(',', $xvary); + assert(is_string($xvary)); + $varys = explode(',', $xvary); $varyId = ''; foreach ($varys as $vary) { - $phpVaryKey = \sprintf('X_%s', \strtoupper($vary)); + $phpVaryKey = sprintf('X_%s', strtoupper($vary)); if (isset($_SERVER[$phpVaryKey]) && is_string($_SERVER[$phpVaryKey])) { $varyId .= $_SERVER[$phpVaryKey]; } diff --git a/src/ResourceStorageInterface.php b/src/ResourceStorageInterface.php index 4844a4a4..6bad81bb 100644 --- a/src/ResourceStorageInterface.php +++ b/src/ResourceStorageInterface.php @@ -12,7 +12,7 @@ interface ResourceStorageInterface /** * Is ETag registered ? */ - public function hasEtag(string $etag) : bool; + public function hasEtag(string $etag): bool; /** * Update or save new Etag @@ -33,7 +33,7 @@ public function deleteEtag(AbstractUri $uri); * * return [$uri, $code, $headers, $body, $view]] array. * - * @return array{0:string, 1: int, 2:array, 3: mixed, 4: string}|false + * @return array{0:AbstractUri, 1: int, 2:array, 3: mixed, 4: mixed}|false */ public function get(AbstractUri $uri); @@ -54,5 +54,5 @@ public function saveView(ResourceObject $ro, int $ttl); /** * Delete resource cache */ - public function delete(AbstractUri $uri) : bool; + public function delete(AbstractUri $uri): bool; } diff --git a/src/StorageExpiryModule.php b/src/StorageExpiryModule.php index 63b67dfb..066ae031 100644 --- a/src/StorageExpiryModule.php +++ b/src/StorageExpiryModule.php @@ -8,22 +8,16 @@ class StorageExpiryModule extends AbstractModule { - /** - * @var int - */ + /** @var int */ private $short; - /** - * @var int - */ + /** @var int */ private $medium; - /** - * @var int - */ + /** @var int */ private $long; - public function __construct(int $short, int $medium, int $long, AbstractModule $module = null) + public function __construct(int $short, int $medium, int $long, ?AbstractModule $module = null) { $this->short = $short; $this->medium = $medium; diff --git a/src/StorageMemcachedCacheProvider.php b/src/StorageMemcachedCacheProvider.php index e7a6a957..010e213c 100644 --- a/src/StorageMemcachedCacheProvider.php +++ b/src/StorageMemcachedCacheProvider.php @@ -6,6 +6,7 @@ use BEAR\RepositoryModule\Annotation\Memcache; use Doctrine\Common\Cache\MemcachedCache; +use Memcached; use Ray\Di\ProviderInterface; class StorageMemcachedCacheProvider implements ProviderInterface @@ -13,13 +14,14 @@ class StorageMemcachedCacheProvider implements ProviderInterface /** * memcached server list * - * @var array + * @var array */ private $servers; /** - * @Memcache("servers") + * @param array $servers * + * @Memcache("servers") * @see http://php.net/manual/en/memcached.addservers.php */ public function __construct(array $servers) @@ -32,8 +34,8 @@ public function __construct(array $servers) */ public function get() { - $memcachedCache = new MemcachedCache; - $memcache = new \Memcached; + $memcachedCache = new MemcachedCache(); + $memcache = new Memcached(); $memcache->addServers($this->servers); $memcachedCache->setMemcached($memcache); diff --git a/src/StorageMemcachedModule.php b/src/StorageMemcachedModule.php index d224cde0..ce209bae 100644 --- a/src/StorageMemcachedModule.php +++ b/src/StorageMemcachedModule.php @@ -11,21 +11,22 @@ use Ray\Di\AbstractModule; use Ray\Di\Scope; +use function array_map; +use function explode; + class StorageMemcachedModule extends AbstractModule { - /** - * @var array - */ + /** @var list> */ private $servers; /** * @param string $servers 'mem1.domain.com:11211:33,mem2.domain.com:11211:67' {host}:{port}:{weight} */ - public function __construct(string $servers, AbstractModule $module = null) + public function __construct(string $servers, ?AbstractModule $module = null) { - $this->servers = \array_map(function ($serverString) { - return \explode(':', $serverString); - }, \explode(',', $servers)); + $this->servers = array_map(static function ($serverString) { + return explode(':', $serverString); + }, explode(',', $servers)); parent::__construct($module); } diff --git a/src/StorageProvider.php b/src/StorageProvider.php index a4a9a59d..3b2d5246 100644 --- a/src/StorageProvider.php +++ b/src/StorageProvider.php @@ -12,19 +12,13 @@ class StorageProvider implements ProviderInterface { - /** - * @var CacheProvider - */ + /** @var CacheProvider */ private $cache; - /** - * @var string - */ + /** @var string */ private $appName; - /** - * @var string - */ + /** @var string */ private $version; /** diff --git a/src/StorageRedisCacheProvider.php b/src/StorageRedisCacheProvider.php index 09a2c90d..35556639 100644 --- a/src/StorageRedisCacheProvider.php +++ b/src/StorageRedisCacheProvider.php @@ -10,6 +10,8 @@ use Ray\Di\ProviderInterface; use RedisException; +use function sprintf; + class StorageRedisCacheProvider implements ProviderInterface { /** @var string */ @@ -19,9 +21,9 @@ class StorageRedisCacheProvider implements ProviderInterface private $port; /** - * @Redis("server") - * * @param array{0: string, 1: string} $server + * + * @Redis("server") */ public function __construct(array $server) { @@ -38,8 +40,10 @@ public function get() try { $redis->connect($this->host, $this->port); } catch (RedisException $e) { + /** @psalm-suppress UndefinedClass */ throw new RedisConnectionException(sprintf('%s/%s', $this->host, $this->port), 0, $e); } + $redisCache = new RedisCache(); $redisCache->setRedis($redis); diff --git a/src/StorageRedisModule.php b/src/StorageRedisModule.php index 36934759..efc324dd 100644 --- a/src/StorageRedisModule.php +++ b/src/StorageRedisModule.php @@ -11,19 +11,19 @@ use Ray\Di\AbstractModule; use Ray\Di\Scope; +use function explode; + class StorageRedisModule extends AbstractModule { - /** - * @var array - */ + /** @var array */ private $server; /** * @param string $server 'localhost:6379' {host}:{port} */ - public function __construct(string $server, AbstractModule $module = null) + public function __construct(string $server, ?AbstractModule $module = null) { - $this->server = \explode(':', $server); + $this->server = explode(':', $server); parent::__construct($module); } diff --git a/tests/BehaviorTest.php b/tests/BehaviorTest.php index cd902267..c7ce77c2 100644 --- a/tests/BehaviorTest.php +++ b/tests/BehaviorTest.php @@ -16,37 +16,29 @@ use PHPUnit\Framework\TestCase; use Ray\Di\Injector; +use function assert; + class BehaviorTest extends TestCase { - /** - * @var ResourceInterface - */ + /** @var ResourceInterface */ private $resource; - /** - * @var QueryRepository - */ - private $repository; - - /** - * @var HttpCacheInterface - */ + /** @var HttpCacheInterface */ private $httpCache; - protected function setUp() : void + protected function setUp(): void { $namespace = 'FakeVendor\HelloWorld'; $injector = new Injector(new QueryRepositoryModule(new ResourceModule($namespace)), $_ENV['TMP_DIR']); - $this->repository = $injector->getInstance(QueryRepositoryInterface::class); $this->resource = $injector->getInstance(ResourceInterface::class); $this->httpCache = $injector->getInstance(HttpCacheInterface::class); parent::setUp(); } - public function testPurgeSameResourceObjectByPatch() + public function testPurgeSameResourceObjectByPatch(): void { - /** @var ResourceObject $user */ $user = $this->resource->get('app://self/user', ['id' => 1]); + assert($user instanceof ResourceObject); $etag = $user->headers['ETag']; // reload (purge repository entry and re-generate by onGet) $this->resource->patch('app://self/user', ['id' => 1, 'name' => 'kuma']); @@ -62,14 +54,14 @@ public function testPurgeSameResourceObjectByPatch() $this->assertSame($lastModified, $user->headers['Last-Modified']); } - public function testPurgeSameResourceObjectByDelete() + public function testPurgeSameResourceObjectByDelete(): void { - /** @var ResourceObject $user */ $user = $this->resource->get('app://self/user', ['id' => 1]); + assert($user instanceof ResourceObject); $etag = $user->headers['ETag']; $server = [ 'REQUEST_METHOD' => 'GET', - 'HTTP_IF_NONE_MATCH' => $etag + 'HTTP_IF_NONE_MATCH' => $etag, ]; $isNotModified = $this->httpCache->isNotModified($server); $this->assertTrue($isNotModified); @@ -81,28 +73,28 @@ public function testPurgeSameResourceObjectByDelete() $this->assertFalse($isNotModified); } - public function testPurgeByAnnotation() + public function testPurgeByAnnotation(): void { $this->resource->put('app://self/user', ['id' => 1, 'age' => 10, 'name' => 'Sunday']); $this->assertTrue(Profile::$requested); } - public function testReturnValueIsNotResourceObjectException() + public function testReturnValueIsNotResourceObjectException(): void { $this->expectException(ReturnValueIsNotResourceObjectException::class); $this->resource->put('app://self/invalid', ['id' => 1, 'age' => 10, 'name' => 'Sunday']); } - public function testUnMatchQuery() + public function testUnMatchQuery(): void { $this->expectException(UnmatchedQuery::class); $this->resource->put('app://self/unmatch', ['id' => 1, 'age' => 10, 'name' => 'Sunday']); } - public function testCacheCode() + public function testCacheCode(): void { - /** @var Code $ro */ - $ro = $this->resource->get('app://self/code', []); // 1 + $ro = $this->resource->get('app://self/code', []); + assert($ro instanceof Code); // 1 $ro->code = 203; $ro->onGet(); // 2 non-caached $ro->code = 500; @@ -114,21 +106,21 @@ public function testCacheCode() $this->assertSame(4, Code::$i); } - public function testRefreshWithCacheableAnnotation() + public function testRefreshWithCacheableAnnotation(): void { RefreshDest::$id = 0; $this->resource->put('app://self/refresh-cache-src', ['id' => '1']); $this->assertSame('1', RefreshDest::$id); } - public function testRefreshWithoutCacheableAnnotation() + public function testRefreshWithoutCacheableAnnotation(): void { RefreshDest::$id = 0; $this->resource->put('app://self/refresh-src', ['id' => '1']); $this->assertSame('1', RefreshDest::$id); } - public function testRefreshByAbortedRequest() + public function testRefreshByAbortedRequest(): void { $profile = $this->resource->get('app://self/user/profile', ['user_id' => 1]); $lastModified = $profile->headers['Last-Modified']; @@ -138,7 +130,7 @@ public function testRefreshByAbortedRequest() $this->assertSame($lastModified, $profile->headers['Last-Modified']); } - public function testRefreshTypedParam() + public function testRefreshTypedParam(): void { $this->resource->put('app://self/typed-param', ['id' => '1']); $this->assertSame(1, TypedParam::$id); diff --git a/tests/CacheTypeTest.php b/tests/CacheTypeTest.php index b8d5cef2..787bf081 100644 --- a/tests/CacheTypeTest.php +++ b/tests/CacheTypeTest.php @@ -6,23 +6,22 @@ use BEAR\Resource\Module\ResourceModule; use BEAR\Resource\ResourceInterface; +use BEAR\Resource\ResourceObject; use PHPUnit\Framework\TestCase; use Ray\Di\Injector; class CacheTypeTest extends TestCase { - /** - * @var ResourceInterface - */ + /** @var ResourceInterface */ private $resource; - protected function setUp() : void + protected function setUp(): void { $this->resource = (new Injector(new QueryRepositoryModule(new ResourceModule('FakeVendor\HelloWorld')), $_ENV['TMP_DIR']))->getInstance(ResourceInterface::class); parent::setUp(); } - public function requestDobule($uri) + public function requestDobule(string $uri): ResourceObject { $ro = $this->resource->get($uri); // put @@ -38,20 +37,20 @@ public function requestDobule($uri) return $ro; } - public function testValue() + public function testValue(): void { $uri = 'app://self/value'; // put $ro = $this->resource->get($uri); - (string) $ro; + (string) $ro; /* @phpstan-ignore-line */ $time = $ro['time']; $this->assertSame('1' . $time, $ro->view); $ro = $this->resource->get($uri); - (string) $ro; + (string) $ro; /* @phpstan-ignore-line */ $this->assertSame('2' . $time, $ro->view); } - public function testView() + public function testView(): void { $uri = 'app://self/view'; // put diff --git a/tests/CacheVersionModuleTest.php b/tests/CacheVersionModuleTest.php index 4714ad40..a276c2bc 100644 --- a/tests/CacheVersionModuleTest.php +++ b/tests/CacheVersionModuleTest.php @@ -7,22 +7,23 @@ use BEAR\RepositoryModule\Annotation\Storage; use BEAR\Resource\Module\ResourceModule; use Doctrine\Common\Cache\Cache; +use Doctrine\Common\Cache\CacheProvider; use PHPUnit\Framework\TestCase; use Ray\Di\Injector; use Ray\Di\NullModule; class CacheVersionModuleTest extends TestCase { - public function testNew() + public function testNew(): void { $namespace = 'FakeVendor\HelloWorld'; $version = '1'; - $module = new NullModule; + $module = new NullModule(); $module->install(new CacheVersionModule($version)); $module->install(new QueryRepositoryModule(new ResourceModule($namespace))); $injector = new Injector($module, $_ENV['TMP_DIR']); $cache = $injector->getInstance(Cache::class, Storage::class); - /* @var $cache \Doctrine\Common\Cache\CacheProvider */ + /** @var CacheProvider $cache */ $ns = $cache->getNamespace(); $expected = $namespace . $version; $this->assertSame($expected, $ns); diff --git a/tests/EtagSetterTest.php b/tests/EtagSetterTest.php index 6b59dd7f..79174c35 100644 --- a/tests/EtagSetterTest.php +++ b/tests/EtagSetterTest.php @@ -11,21 +11,19 @@ class EtagSetterTest extends TestCase { - /** - * @var ResourceInterface - */ + /** @var ResourceInterface */ private $resource; - protected function setUp() : void + protected function setUp(): void { parent::setUp(); $this->resource = (new Injector(new QueryRepositoryModule(new ResourceModule('FakeVendor\HelloWorld')), $_ENV['TMP_DIR']))->getInstance(ResourceInterface::class); } - public function testInvoke() + public function testInvoke(): void { $ro = $this->resource->get('app://self/user', ['id' => 1]); - $setEtag = new EtagSetter; + $setEtag = new EtagSetter(); $time = 0; $setEtag($ro, $time); $expect = 'Thu, 01 Jan 1970 00:00:00 GMT'; diff --git a/tests/Fake/RedisException.php b/tests/Fake/RedisException.php new file mode 100644 index 00000000..96ccb31f --- /dev/null +++ b/tests/Fake/RedisException.php @@ -0,0 +1,7 @@ +resource = (new Injector(new QueryRepositoryModule(new ResourceModule('FakeVendor\HelloWorld')), $_ENV['TMP_DIR']))->getInstance(ResourceInterface::class); parent::setUp(); } - public function testLastModifiedHeader() + public function testLastModifiedHeader(): void { $user = $this->resource->get('app://self/user', ['id' => 1]); // put @@ -37,51 +35,51 @@ public function testLastModifiedHeader() $this->assertSame($expect, $user['time']); } - public function testCacheControlHeaderNone() + public function testCacheControlHeaderNone(): void { $user = $this->resource->get('app://self/control-none'); $this->assertArrayHasKey('Cache-Control', $user->headers); $this->assertSame('max-age=60', $user->headers['Cache-Control']); } - public function testCacheControlHeaderExpiry() + public function testCacheControlHeaderExpiry(): void { $user = $this->resource->get('app://self/control-expiry'); $this->assertArrayHasKey('Cache-Control', $user->headers); $this->assertStringContainsString('public, max-age=3', $user->headers['Cache-Control']); // 30 sec (but may 30+x sec for slow CI) } - public function testCacheControlHeaderExpiryError() + public function testCacheControlHeaderExpiryError(): void { $this->expectException(ExpireAtKeyNotExists::class); $this->resource->get('app://self/control-expiry-error'); } - public function testHttpCacheAnnotation() + public function testHttpCacheAnnotation(): void { $ro = $this->resource->get('app://self/http-cache-control'); $this->assertSame($ro->headers['Cache-Control'], 'private, no-cache, no-store, must-revalidate'); } - public function testNoHttpCacheAnnotation() + public function testNoHttpCacheAnnotation(): void { $ro = $this->resource->get('app://self/no-http-cache-control'); $this->assertSame($ro->headers['Cache-Control'], 'private, no-store, no-cache, must-revalidate'); } - public function testHttpCacheWithCacheble() + public function testHttpCacheWithCacheble(): void { $ro = $this->resource->get('app://self/http-cache-control-with-cacheable'); $this->assertSame($ro->headers['Cache-Control'], 'private, max-age=10'); } - public function testHttpCacheOverrideMaxAge() + public function testHttpCacheOverrideMaxAge(): void { $ro = $this->resource->get('app://self/http-cache-control-override-max-age'); $this->assertSame($ro->headers['Cache-Control'], 'max-age=5'); } - public function testHttpCacheEtag() + public function testHttpCacheEtag(): void { $ro1 = $this->resource->get('app://self/etag'); $ro2 = $this->resource->get('app://self/etag'); @@ -90,7 +88,7 @@ public function testHttpCacheEtag() $this->assertNotSame($ro1->headers['ETag'], $ro3->headers['ETag']); } - public function testHttpCacheVary() + public function testHttpCacheVary(): void { $ro1 = $this->resource->get('app://self/etag'); $ro2 = $this->resource->get('app://self/etag'); diff --git a/tests/HttpCacheTest.php b/tests/HttpCacheTest.php index 797eec15..cc2672a9 100644 --- a/tests/HttpCacheTest.php +++ b/tests/HttpCacheTest.php @@ -12,18 +12,18 @@ class HttpCacheTest extends TestCase { - public function testisNotModifiedFale() + public function testisNotModifiedFale(): void { - $httpCache = new CliHttpCache(new ResourceStorage(new ArrayCache)); + $httpCache = new CliHttpCache(new ResourceStorage(new ArrayCache())); $server = []; $this->assertFalse($httpCache->isNotModified($server)); } - public function testisNotModifiedTrue() + public function testisNotModifiedTrue(): CliHttpCache { $resource = (new Injector(new QueryRepositoryModule(new ResourceModule('FakeVendor\HelloWorld'))))->getInstance(ResourceInterface::class); $user = $resource->get('app://self/user', ['id' => 1]); - $storage = new ResourceStorage(new ArrayCache); + $storage = new ResourceStorage(new ArrayCache()); $storage->updateEtag($user, 10); $httpCache = new CliHttpCache($storage); $server = ['HTTP_IF_NONE_MATCH' => $user->headers['ETag']]; @@ -35,7 +35,7 @@ public function testisNotModifiedTrue() /** * @depends testisNotModifiedTrue */ - public function testTransfer(CliHttpCache $httpCache) + public function testTransfer(CliHttpCache $httpCache): void { $this->expectOutputRegex('/\A304 Not Modified/'); $httpCache->transfer(); diff --git a/tests/MobileEtagSetterTest.php b/tests/MobileEtagSetterTest.php index cad18d08..c22562aa 100644 --- a/tests/MobileEtagSetterTest.php +++ b/tests/MobileEtagSetterTest.php @@ -8,38 +8,40 @@ use FakeVendor\HelloWorld\Resource\App\User; use PHPUnit\Framework\TestCase; +use function time; + class MobileEtagSetterTest extends TestCase { - const IPHONE = 'Mozilla/5.0 (iPhone; CPU iPhone OS 6_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10A403 Safari/8536.25'; + public const IPHONE = 'Mozilla/5.0 (iPhone; CPU iPhone OS 6_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10A403 Safari/8536.25'; - const IPAD = 'Mozilla/5.0 (iPad; CPU OS 6_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10A403 Safari/8536.25'; + public const IPAD = 'Mozilla/5.0 (iPad; CPU OS 6_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10A403 Safari/8536.25'; - /** - * @var FakeMobileEtagSetter - */ + /** @var FakeMobileEtagSetter */ private $etagSetter; + /** @var int */ private $time; + /** @var User */ private $obj; - protected function setUp() : void + protected function setUp(): void { parent::setUp(); - $this->obj = new User; - $this->etagSetter = new FakeMobileEtagSetter(new MobileEtagSetter); - $this->time = \time(); + $this->obj = new User(); + $this->etagSetter = new FakeMobileEtagSetter(new MobileEtagSetter()); + $this->time = time(); } - public function testMobile() + public function testMobile(): void { $_SERVER['HTTP_USER_AGENT'] = self::IPHONE; - ($this->etagSetter)($this->obj, $this->time, new HttpCache); + ($this->etagSetter)($this->obj, $this->time, new HttpCache()); $expected = 'mobile'; $this->assertSame($expected, FakeMobileEtagSetter::$device); } - public function testTablet() + public function testTablet(): void { $_SERVER['HTTP_USER_AGENT'] = self::IPAD; ($this->etagSetter)($this->obj, $this->time); @@ -47,7 +49,7 @@ public function testTablet() $this->assertSame($expected, FakeMobileEtagSetter::$device); } - public function testPc() + public function testPc(): void { unset($_SERVER['HTTP_USER_AGENT']); ($this->etagSetter)($this->obj, $this->time); diff --git a/tests/NamespacedCacheProviderTest.php b/tests/NamespacedCacheProviderTest.php index da522261..8747fe63 100644 --- a/tests/NamespacedCacheProviderTest.php +++ b/tests/NamespacedCacheProviderTest.php @@ -9,9 +9,9 @@ class NamespacedCacheProviderTest extends TestCase { - public function testNew() + public function testNew(): void { - $provider = new NamespacedCacheProvider(new ArrayCache, 'app', '1'); + $provider = new NamespacedCacheProvider(new ArrayCache(), 'app', '1'); $cache = $provider->get(); $this->assertSame('app:1', $cache->getNamespace()); } diff --git a/tests/QueryRepositoryTest.php b/tests/QueryRepositoryTest.php index 19d4d4a0..d4300a27 100644 --- a/tests/QueryRepositoryTest.php +++ b/tests/QueryRepositoryTest.php @@ -12,29 +12,24 @@ use Doctrine\Common\Cache\CacheProvider; use FakeVendor\HelloWorld\Resource\App\User\Profile; use FakeVendor\HelloWorld\Resource\Page\None; -use PHPUnit\Framework\Error\Warning; use PHPUnit\Framework\TestCase; use Ray\Di\AbstractModule; use Ray\Di\Injector; +use function assert; + class QueryRepositoryTest extends TestCase { - /** - * @var ResourceInterface - */ + /** @var ResourceInterface */ private $resource; - /** - * @var QueryRepository - */ + /** @var QueryRepository */ private $repository; - /** - * @var HttpCacheInterface - */ + /** @var HttpCacheInterface */ private $httpCache; - protected function setUp() : void + protected function setUp(): void { $namespace = 'FakeVendor\HelloWorld'; $injector = new Injector(new QueryRepositoryModule(new MobileEtagModule(new ResourceModule($namespace))), $_ENV['TMP_DIR']); @@ -44,10 +39,10 @@ protected function setUp() : void parent::setUp(); } - public function testPurgeSameResourceObjectByPatch() + public function testPurgeSameResourceObjectByPatch(): void { - /** @var ResourceObject $user */ $user = $this->resource->get('app://self/user', ['id' => 1]); + assert($user instanceof ResourceObject); $etag = $user->headers['ETag']; // reload (purge repository entry and re-generate by onGet) $this->resource->patch('app://self/user', ['id' => 1, 'name' => 'kuma']); @@ -57,14 +52,14 @@ public function testPurgeSameResourceObjectByPatch() $this->assertFalse($etag === $newEtag); } - public function testPurgeSameResourceObjectByDelete() + public function testPurgeSameResourceObjectByDelete(): void { - /** @var ResourceObject $user */ $user = $this->resource->get('app://self/user', ['id' => 1]); + assert($user instanceof ResourceObject); $etag = $user->headers['ETag']; $server = [ 'REQUEST_METHOD' => 'GET', - 'HTTP_IF_NONE_MATCH' => $etag + 'HTTP_IF_NONE_MATCH' => $etag, ]; $isNotModified = $this->httpCache->isNotModified($server); $this->assertTrue($isNotModified); @@ -76,7 +71,7 @@ public function testPurgeSameResourceObjectByDelete() $this->assertFalse($isNotModified); } - public function testPurgeByAnnotation() + public function testPurgeByAnnotation(): void { $this->resource->put('app://self/user', ['id' => 1, 'age' => 10, 'name' => 'Sunday']); $this->assertTrue(Profile::$requested); @@ -85,15 +80,15 @@ public function testPurgeByAnnotation() /** * @covers \BEAR\QueryRepository\QueryRepository::getExpiryTime() */ - public function testNoAnnotationLifeTime() + public function testNoAnnotationLifeTime(): void { - $ro = new None; // no annotation + $ro = new None(); // no annotation $ro->uri = new Uri('page://self/none'); $result = $this->repository->put($ro); $this->assertTrue($result); } - public function testPutResquestEmbeddedResoureView() + public function testPutResquestEmbeddedResoureView(): void { $uri = 'page://self/emb-view'; $ro = $this->resource->get($uri); @@ -109,7 +104,7 @@ public function testPutResquestEmbeddedResoureView() ', $view); } - public function testPutResquestEmbeddedResoureValue() + public function testPutResquestEmbeddedResoureValue(): void { $uri = 'page://self/emb-val'; $ro = $this->resource->get($uri); @@ -119,7 +114,7 @@ public function testPutResquestEmbeddedResoureValue() $this->assertNull($view); } - public function testErrorInCacheRead() + public function testErrorInCacheRead(): void { $namespace = 'FakeVendor\HelloWorld'; $module = new QueryRepositoryModule(new MobileEtagModule(new ResourceModule($namespace))); @@ -138,7 +133,7 @@ protected function configure() $this->assertContains('Exception: DoctrineNamespaceCacheKey[]', $GLOBALS['BEAR\QueryRepository\syslog'][1]); } - public function testSameResponseButDifferentParameter() + public function testSameResponseButDifferentParameter(): void { $ro1 = $this->resource->get('app://self/sometimes-same-response', ['id' => 1]); $server1 = [ diff --git a/tests/ReloadAnnotatedCommandTest.php b/tests/ReloadAnnotatedCommandTest.php index 611d227d..29471d9b 100644 --- a/tests/ReloadAnnotatedCommandTest.php +++ b/tests/ReloadAnnotatedCommandTest.php @@ -11,18 +11,16 @@ class ReloadAnnotatedCommandTest extends TestCase { - /** - * @var ResourceInterface - */ + /** @var ResourceInterface */ private $resource; - protected function setUp() : void + protected function setUp(): void { $this->resource = (new Injector(new QueryRepositoryModule(new ResourceModule('FakeVendor\HelloWorld')), $_ENV['TMP_DIR']))->getInstance(ResourceInterface::class); parent::setUp(); } - public function testInvoke() + public function testInvoke(): void { $user = $this->resource->patch('app://self/user', ['id' => 1, 'name' => 'koriym']); // put diff --git a/tests/ResourceRepositoryTest.php b/tests/ResourceRepositoryTest.php index 856df351..7fc34323 100644 --- a/tests/ResourceRepositoryTest.php +++ b/tests/ResourceRepositoryTest.php @@ -12,52 +12,52 @@ use FakeVendor\HelloWorld\Resource\Page\Index; use PHPUnit\Framework\TestCase; +use function array_change_key_case; + +use const CASE_LOWER; + class ResourceRepositoryTest extends TestCase { - /** - * @var QueryRepository - */ + /** @var QueryRepository */ private $repository; - /** - * @var ResourceObject - */ + /** @var ResourceObject */ private $ro; - protected function setUp() : void + protected function setUp(): void { $this->repository = new Repository( - new EtagSetter, + new EtagSetter(), new ResourceStorage( new FilesystemCache($_ENV['TMP_DIR']) ), - new AnnotationReader, + new AnnotationReader(), new Expiry(0, 0, 0) ); - $this->ro = new Index; + $this->ro = new Index(); $this->ro->uri = new Uri('page://self/user'); } - public function testPutAndGet() + public function testPutAndGet(): void { // put $this->repository->put($this->ro); $uri = $this->ro->uri; // get - list($uri, $code, $headers, $body) = $this->repository->get($uri); + [$uri, $code, $headers, $body] = $this->repository->get($uri); $this->assertSame((string) $uri, (string) $this->ro->uri); $this->assertSame($code, $this->ro->code); $headers = array_change_key_case($headers, CASE_LOWER); - $Roheaders = array_change_key_case($this->ro->headers, CASE_LOWER); - $this->assertSame($headers['content-type'], $Roheaders['content-type']); - $this->assertSame($headers['etag'], $Roheaders['etag']); - $this->assertSame($headers['last-modified'], $Roheaders['last-modified']); + $roHeaders = array_change_key_case($this->ro->headers, CASE_LOWER); + $this->assertSame($headers['content-type'], $roHeaders['content-type']); + $this->assertSame($headers['etag'], $roHeaders['etag']); + $this->assertSame($headers['last-modified'], $roHeaders['last-modified']); $this->assertSame(0, $headers['age']); $this->assertArrayHasKey('age', $headers); $this->assertSame($body, $this->ro->body); } - public function testDelete() + public function testDelete(): void { $this->repository->put($this->ro); $uri = $this->ro->uri; diff --git a/tests/StorageApcModuleTest.php b/tests/StorageApcModuleTest.php index 12e9295b..ff7b167b 100644 --- a/tests/StorageApcModuleTest.php +++ b/tests/StorageApcModuleTest.php @@ -12,9 +12,9 @@ class StorageApcModuleTest extends TestCase { - public function testNew() + public function testNew(): void { - $cache = (new Injector(new StorageApcModule, $_ENV['TMP_DIR']))->getInstance(CacheProvider::class, Storage::class); + $cache = (new Injector(new StorageApcModule(), $_ENV['TMP_DIR']))->getInstance(CacheProvider::class, Storage::class); $this->assertInstanceOf(ApcuCache::class, $cache); } } diff --git a/tests/bootstrap.php b/tests/bootstrap.php index f91b8628..bd43cef4 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -3,7 +3,7 @@ declare(strict_types=1); $_ENV['TMP_DIR'] = __DIR__ . '/tmp'; -$unlink = function ($path) use (&$unlink) { +$unlink = static function ($path) use (&$unlink) { foreach ((array) glob(rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . '*') as $f) { $file = (string) $f; is_dir($file) ? $unlink($file) : unlink($file); diff --git a/tests/syslog.php b/tests/syslog.php index f562815f..df93a29a 100644 --- a/tests/syslog.php +++ b/tests/syslog.php @@ -4,7 +4,7 @@ namespace BEAR\QueryRepository; -function syslog(int $priority, string $message) +function syslog(int $priority, string $message): void { $GLOBALS['BEAR\QueryRepository\syslog'] = [$priority, $message]; } diff --git a/vendor-bin/tools/composer.json b/vendor-bin/tools/composer.json index 8c55a8a4..d7fbc7a0 100644 --- a/vendor-bin/tools/composer.json +++ b/vendor-bin/tools/composer.json @@ -1,6 +1,5 @@ { - "require-dev": { - "php": "^7.2 || ^8.0", + "require": { "doctrine/coding-standard": "^8.2", "phpmd/phpmd": "^2.9", "phpmetrics/phpmetrics": "^2.7",