From 8e5b5b459745fb8f2a685927a376ec06da67594c Mon Sep 17 00:00:00 2001 From: Chris Kankiewicz Date: Fri, 29 May 2020 15:59:48 -0700 Subject: [PATCH] Enabled caching for file hashes, zip files, translation definitions and markdown parsing --- .travis.yml | 8 + Dockerfile | 6 +- app/config/app.php | 8 - app/config/cache.php | 71 ++ app/config/container.php | 8 +- app/src/Controllers/FileInfoController.php | 26 +- app/src/Controllers/ZipController.php | 13 +- app/src/Factories/CacheFactory.php | 89 ++ app/src/Factories/TranslationFactory.php | 25 +- app/src/ViewFunctions/Markdown.php | 19 +- composer.json | 6 + composer.lock | 817 ++++++++++++++++-- docker-compose.yaml | 12 + index.php | 1 + psalm-baseline.xml | 8 +- tests/.env | 9 + tests/Controllers/FileInfoControllerTest.php | 18 +- ...pHandlerTest.php => ZipControllerTest.php} | 5 +- tests/Factories/CacheFactoryTest.php | 74 ++ tests/Factories/FinderFactoryTest.php | 10 +- tests/Factories/TranslationFactoryTest.php | 4 +- tests/TestCase.php | 8 + tests/ViewFunctions/MarkdownTest.php | 5 +- 23 files changed, 1120 insertions(+), 130 deletions(-) create mode 100644 app/config/cache.php create mode 100644 app/src/Factories/CacheFactory.php rename tests/Controllers/{ZipHandlerTest.php => ZipControllerTest.php} (95%) create mode 100644 tests/Factories/CacheFactoryTest.php diff --git a/.travis.yml b/.travis.yml index 195423e..e21cc70 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,12 +10,20 @@ jobs: allow_failures: - php: nightly +services: + - memcached + - redis + cache: directories: - $HOME/.composer/cache - $HOME/.npm - app/vendor +before_install: + - printf "\n" | pecl install -f apcu-5.1.18 memcached + - echo "extension = redis.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini + install: - composer install --no-suggest - npm ci diff --git a/Dockerfile b/Dockerfile index 9ec6399..19b51e6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,10 +4,12 @@ LABEL maintainer="Chris Kankiewicz " COPY .docker/apache/config/000-default.conf /etc/apache2/sites-available/000-default.conf COPY .docker/php/config/php.ini /usr/local/etc/php/php.ini -RUN apt-get update && apt-get install --assume-yes libzip-dev \ +RUN apt-get update && apt-get install --assume-yes libmemcached-dev libzip-dev \ && rm -rf /var/lib/apt/lists/* -RUN docker-php-ext-install zip && pecl install xdebug && docker-php-ext-enable xdebug +RUN docker-php-ext-install zip \ + && pecl install memcached redis xdebug \ + && docker-php-ext-enable memcached redis xdebug RUN a2enmod rewrite diff --git a/app/config/app.php b/app/config/app.php index 0b00c1c..d9a9dd3 100644 --- a/app/config/app.php +++ b/app/config/app.php @@ -147,14 +147,6 @@ return [ */ 'max_hash_size' => Helpers::env('MAX_HASH_SIZE', 1000000000), - /** - * Path to the view cache directory. - * Set to 'false' to disable view caching entirely. - * - * Default value: 'app/cache/views' - */ - 'view_cache' => Helpers::env('VIEW_CACHE', 'app/cache/views'), - /** * HTTP expires values. * diff --git a/app/config/cache.php b/app/config/cache.php new file mode 100644 index 0000000..6cc2f1b --- /dev/null +++ b/app/config/cache.php @@ -0,0 +1,71 @@ + Helpers::env('CACHE_DRIVER', 'file'), + + /** + * The app cache lifetime (in seconds). If set to 0, cache indefinitely. + * + * Default value: 0 (indefinitely) + */ + 'cache_lifetime' => Helpers::env('CACHE_LIFETIME', 0), + + /** + * Path to the view cache directory. Set to 'false' to disable + * view caching entirely. The view cache is separate from the application + * cache defined above. + * + * Default value: 'app/cache/views' + */ + 'view_cache' => Helpers::env('VIEW_CACHE', 'app/cache/views'), + + /** + * The Memcached configuration closure. This option is used when the + * 'cache_driver' configuration option is set to 'memcached'. The closure + * receives a Memcached object as it's only parameter. You can use this + * object to configure the Memcached connection. At a minimum you must + * connect to one or more Memcached servers via the 'addServer()' or + * 'addServers()' methods. + * + * Reference the PHP Memcached documentation for Memcached configuration + * options: https://secure.php.net/manual/en/book.memcached.php + * + * Default value: Adds a server at localhost:11211 + */ + 'memcached_config' => DI\value(function (Memcached $memcached): void { + $memcached->addServer( + Helpers::env('MEMCACHED_HOST', 'localhost'), + Helpers::env('MEMCACHED_PORT', 11211) + ); + }), + + /** + * The Redis configuration closure. This option is used when the + * 'cache_driver' configuration option is set to 'redis'. The closure + * receives a Redis object as it's only parameter. You can use this object + * to configure the Redis connection. At a minimum you must connect to one + * or more Redis servers via the 'connect()' or 'pconnect()' methods. + * + * Reference the phpredis documentation for Redis configuration options: + * https://github.com/phpredis/phpredis#readme + * + * Default value: Adds a server at localhost:6379 + */ + 'redis_config' => DI\value(function (Redis $redis): void { + $redis->pconnect( + Helpers::env('REDIS_HOST', 'localhost'), + Helpers::env('REDIS_PORT', 6379) + ); + }), +]; diff --git a/app/config/container.php b/app/config/container.php index 00bda2c..62351e9 100644 --- a/app/config/container.php +++ b/app/config/container.php @@ -14,6 +14,7 @@ return [ 'asset_path' => DI\string('{app_path}/assets'), 'cache_path' => DI\string('{app_path}/cache'), 'config_path' => DI\string('{app_path}/config'), + 'source_path' => DI\string('{app_path}/src'), 'translations_path' => DI\string('{app_path}/translations'), 'views_path' => DI\string('{app_path}/views'), 'icons_config' => DI\string('{config_path}/icons.php'), @@ -39,12 +40,6 @@ return [ 'type' => SortMethods\Type::class, ], - /** Array of available translation languages */ - 'translations' => [ - 'de', 'en', 'es', 'fr', 'id', 'it', 'kr', 'nl', - 'pl', 'pt-BR', 'ro', 'ru', 'zh-CN', 'zh-TW' - ], - /** Array of view functions */ 'view_functions' => [ ViewFunctions\Asset::class, @@ -62,6 +57,7 @@ return [ /** Container definitions */ Symfony\Component\Finder\Finder::class => DI\factory(Factories\FinderFactory::class), + Symfony\Contracts\Cache\CacheInterface::class => DI\Factory(Factories\CacheFactory::class), Symfony\Contracts\Translation\TranslatorInterface::class => DI\factory(Factories\TranslationFactory::class), Slim\Views\Twig::class => DI\factory(Factories\TwigFactory::class), Whoops\RunInterface::class => DI\create(Whoops\Run::class), diff --git a/app/src/Controllers/FileInfoController.php b/app/src/Controllers/FileInfoController.php index f4d4fe1..44a60f4 100644 --- a/app/src/Controllers/FileInfoController.php +++ b/app/src/Controllers/FileInfoController.php @@ -7,6 +7,7 @@ use Psr\Http\Message\ResponseInterface; use Slim\Psr7\Request; use Slim\Psr7\Response; use SplFileInfo; +use Symfony\Contracts\Cache\CacheInterface; use Symfony\Contracts\Translation\TranslatorInterface; class FileInfoController @@ -14,6 +15,9 @@ class FileInfoController /** @var Container The application container */ protected $container; + /** @var CacheInterface The application cache */ + protected $cache; + /** @var TranslatorInterface Translator component */ protected $translator; @@ -21,13 +25,16 @@ class FileInfoController * Create a new FileInfoHandler object. * * @param \DI\Container $container + * @param \Symfony\Contracts\Cache\CacheInterface $cache * @param \Symfony\Contracts\Translation\TranslatorInterface $translator */ public function __construct( Container $container, + CacheInterface $cache, TranslatorInterface $translator ) { $this->container = $container; + $this->cache = $cache; $this->translator = $translator; } @@ -55,13 +62,18 @@ class FileInfoController return $response->withStatus(500, $this->translator->trans('error.file_size_exceeded')); } - $response->getBody()->write(json_encode([ - 'hashes' => [ - 'md5' => hash('md5', file_get_contents($file->getPathname())), - 'sha1' => hash('sha1', file_get_contents($file->getPathname())), - 'sha256' => hash('sha256', file_get_contents($file->getPathname())), - ] - ])); + $response->getBody()->write($this->cache->get( + sprintf('file-info-%s', sha1($file->getRealPath())), + function () use ($file): string { + return json_encode([ + 'hashes' => [ + 'md5' => hash_file('md5', $file->getPathname()), + 'sha1' => hash_file('sha1', $file->getPathname()), + 'sha256' => hash_file('sha256', $file->getPathname()), + ] + ]); + } + )); return $response->withHeader('Content-Type', 'application/json'); } diff --git a/app/src/Controllers/ZipController.php b/app/src/Controllers/ZipController.php index d4e48ed..a69e5d4 100644 --- a/app/src/Controllers/ZipController.php +++ b/app/src/Controllers/ZipController.php @@ -10,6 +10,7 @@ use Slim\Psr7\Request; use Slim\Psr7\Response; use Symfony\Component\Finder\Finder; use Symfony\Component\Finder\SplFileInfo; +use Symfony\Contracts\Cache\CacheInterface; use Symfony\Contracts\Translation\TranslatorInterface; use ZipArchive; @@ -18,6 +19,9 @@ class ZipController /** @var Container The application container */ protected $container; + /** @var CacheInterface The application cache */ + protected $cache; + /** @var Finder The Finder Component */ protected $finder; @@ -28,15 +32,18 @@ class ZipController * Create a new ZipHandler object. * * @param \DI\Container $container + * @param \Symfony\Contracts\Cache\CacheInterface $cache * @param \PhpCsFixer\Finder $finder * @param \Symfony\Contracts\Translation\TranslatorInterface $translator */ public function __construct( Container $container, + CacheInterface $cache, Finder $finder, TranslatorInterface $translator ) { $this->container = $container; + $this->cache = $cache; $this->finder = $finder; $this->translator = $translator; } @@ -57,7 +64,11 @@ class ZipController return $response->withStatus(404, $this->translator->trans('error.file_not_found')); } - $response->getBody()->write($this->createZip($path)->getContents()); + $response->getBody()->write( + $this->cache->get(sprintf('zip-%s', sha1($path)), function () use ($path): string { + return $this->createZip($path)->getContents(); + }) + ); return $response->withHeader('Content-Type', 'application/zip') ->withHeader('Content-Disposition', sprintf( diff --git a/app/src/Factories/CacheFactory.php b/app/src/Factories/CacheFactory.php new file mode 100644 index 0000000..45143b3 --- /dev/null +++ b/app/src/Factories/CacheFactory.php @@ -0,0 +1,89 @@ +container = $container; + } + + /** + * Initialize and return a CacheInterface. + * + * @return \Symfony\Contracts\Cache\CacheInterface + */ + public function __invoke(): CacheInterface + { + switch ($this->container->get('cache_driver')) { + case 'apcu': + return new ApcuAdapter( + self::NAMESPACE_EXTERNAL, + $this->container->get('cache_lifetime') + ); + + case 'array': + return new ArrayAdapter($this->container->get('cache_lifetime')); + + case 'file': + return new FilesystemAdapter( + self::NAMESAPCE_INTERNAL, + $this->container->get('cache_lifetime'), + $this->container->get('cache_path') + ); + + case 'memcached': + $this->container->call('memcached_config', [$memcached = new \Memcached]); + + return new MemcachedAdapter( + $memcached, + self::NAMESPACE_EXTERNAL, + $this->container->get('cache_lifetime') + ); + + case 'php-file': + return new PhpFilesAdapter( + self::NAMESAPCE_INTERNAL, + $this->container->get('cache_lifetime'), + $this->container->get('cache_path') + ); + + case 'redis': + $this->container->call('redis_config', [$redis = new \Redis]); + + return new RedisAdapter( + $redis, + self::NAMESPACE_EXTERNAL, + $this->container->get('cache_lifetime') + ); + + default: + throw InvalidConfiguration::fromConfig('cache_driver', $this->container->get('cache_driver')); + } + } +} diff --git a/app/src/Factories/TranslationFactory.php b/app/src/Factories/TranslationFactory.php index 080da76..f6220a8 100644 --- a/app/src/Factories/TranslationFactory.php +++ b/app/src/Factories/TranslationFactory.php @@ -4,8 +4,11 @@ namespace App\Factories; use DI\Container; use RuntimeException; +use Symfony\Component\Finder\Finder; +use Symfony\Component\Finder\SplFileInfo; use Symfony\Component\Translation\Loader\YamlFileLoader; use Symfony\Component\Translation\Translator; +use Symfony\Contracts\Cache\CacheInterface; use Symfony\Contracts\Translation\TranslatorInterface; class TranslationFactory @@ -13,14 +16,18 @@ class TranslationFactory /** @var Container The applicaiton container */ protected $container; + /** @var CacheInterface The application cache */ + protected $cache; + /** * Create a new TranslationFactory object. * * @param \DI\Container $container */ - public function __construct(Container $container) + public function __construct(Container $container, CacheInterface $cache) { $this->container = $container; + $this->cache = $cache; } /** @@ -47,4 +54,20 @@ class TranslationFactory return $translator; } + + /** + * Get an array of available translation languages. + * + * @return array + */ + protected function translations(): array + { + return $this->cache->get('translations', function (): array { + return array_values(array_map(function (SplFileInfo $file): string { + return $file->getBasename('.yaml'); + }, iterator_to_array( + Finder::create()->in($this->container->get('translations_path'))->name('*.yaml') + ))); + }); + } } diff --git a/app/src/ViewFunctions/Markdown.php b/app/src/ViewFunctions/Markdown.php index 50ce376..72f7270 100644 --- a/app/src/ViewFunctions/Markdown.php +++ b/app/src/ViewFunctions/Markdown.php @@ -3,12 +3,25 @@ namespace App\ViewFunctions; use ParsedownExtra; +use Symfony\Contracts\Cache\CacheInterface; class Markdown extends ViewFunction { /** @var string The function name */ protected $name = 'markdown'; + /** @var ParsedownExtra The markdown parser */ + protected $parser; + + /** @var CacheInterface */ + protected $cache; + + public function __construct(ParsedownExtra $parser, CacheInterface $cache) + { + $this->parser = $parser; + $this->cache = $cache; + } + /** * Parses a string of markdown into HTML. * @@ -16,8 +29,10 @@ class Markdown extends ViewFunction * * @return string */ - public function __invoke(string $string) + public function __invoke(string $string): string { - return ParsedownExtra::instance()->parse($string); + return $this->cache->get(md5($string), function () use ($string): string { + return $this->parser->parse($string); + }); } } diff --git a/composer.json b/composer.json index 64f0809..e93b946 100644 --- a/composer.json +++ b/composer.json @@ -24,6 +24,7 @@ "slim/psr7": "^1.0", "slim/slim": "^4.3", "slim/twig-view": "^3.0", + "symfony/cache": "^5.0", "symfony/finder": "^5.0", "symfony/translation": "^5.0", "symfony/yaml": "^5.0", @@ -39,6 +40,11 @@ "symfony/var-dumper": "^5.0", "vimeo/psalm": "^3.6" }, + "suggest": { + "ext-apcu": "Required to use the APCu cache driver", + "ext-memcached": "Required to use the Memcached driver", + "ext-redis": "Required to use the Redis cache driver" + }, "autoload": { "psr-4": { "App\\": "app/src/" diff --git a/composer.lock b/composer.lock index 09a53cb..8e579f8 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "c7d03352cbe1a1776ad957094397d46f", + "content-hash": "e4419a29963c9f2076710469140600a5", "packages": [ { "name": "erusev/parsedown", @@ -566,6 +566,16 @@ } ], "description": "Glob-like pattern matching and utilities", + "funding": [ + { + "url": "https://github.com/sponsors/PHLAK", + "type": "github" + }, + { + "url": "https://paypal.me/ChrisKankiewicz", + "type": "paypal" + } + ], "time": "2020-05-15T17:01:07+00:00" }, { @@ -672,6 +682,16 @@ "ioc", "psr11" ], + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", + "type": "tidelift" + } + ], "time": "2020-04-06T09:54:49+00:00" }, { @@ -1419,6 +1439,171 @@ ], "time": "2020-03-13T18:08:36+00:00" }, + { + "name": "symfony/cache", + "version": "v5.0.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache.git", + "reference": "0c5f5b1882dc82b255a4bdead4ed3c7738cddc04" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache/zipball/0c5f5b1882dc82b255a4bdead4ed3c7738cddc04", + "reference": "0c5f5b1882dc82b255a4bdead4ed3c7738cddc04", + "shasum": "" + }, + "require": { + "php": "^7.2.5", + "psr/cache": "~1.0", + "psr/log": "~1.0", + "symfony/cache-contracts": "^1.1.7|^2", + "symfony/service-contracts": "^1.1|^2", + "symfony/var-exporter": "^4.4|^5.0" + }, + "conflict": { + "doctrine/dbal": "<2.5", + "symfony/dependency-injection": "<4.4", + "symfony/http-kernel": "<4.4", + "symfony/var-dumper": "<4.4" + }, + "provide": { + "psr/cache-implementation": "1.0", + "psr/simple-cache-implementation": "1.0", + "symfony/cache-implementation": "1.0" + }, + "require-dev": { + "cache/integration-tests": "dev-master", + "doctrine/cache": "~1.6", + "doctrine/dbal": "~2.5", + "predis/predis": "~1.1", + "psr/simple-cache": "^1.0", + "symfony/config": "^4.4|^5.0", + "symfony/dependency-injection": "^4.4|^5.0", + "symfony/var-dumper": "^4.4|^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Cache\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Cache component with PSR-6, PSR-16, and tags", + "homepage": "https://symfony.com", + "keywords": [ + "caching", + "psr6" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-04-28T17:58:55+00:00" + }, + { + "name": "symfony/cache-contracts", + "version": "v2.1.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache-contracts.git", + "reference": "87c92f62c494626598e9148208aaa6d1716b8e3c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/87c92f62c494626598e9148208aaa6d1716b8e3c", + "reference": "87c92f62c494626598e9148208aaa6d1716b8e3c", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/cache": "^1.0" + }, + "suggest": { + "symfony/cache-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Cache\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to caching", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-05-20T17:43:50+00:00" + }, { "name": "symfony/finder", "version": "v5.0.8", @@ -1466,6 +1651,20 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], "time": "2020-03-27T16:56:45+00:00" }, { @@ -1524,6 +1723,20 @@ "polyfill", "portable" ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], "time": "2020-05-12T16:14:59+00:00" }, { @@ -1583,6 +1796,20 @@ "portable", "shim" ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], "time": "2020-05-12T16:47:27+00:00" }, { @@ -1639,6 +1866,20 @@ "portable", "shim" ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], "time": "2020-05-12T16:47:27+00:00" }, { @@ -1691,8 +1932,94 @@ "polyfill", "shim" ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], "time": "2020-05-12T16:14:59+00:00" }, + { + "name": "symfony/service-contracts", + "version": "v2.1.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "66a8f0957a3ca54e4f724e49028ab19d75a8918b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/66a8f0957a3ca54e4f724e49028ab19d75a8918b", + "reference": "66a8f0957a3ca54e4f724e49028ab19d75a8918b", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/container": "^1.0" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-05-20T17:43:50+00:00" + }, { "name": "symfony/translation", "version": "v5.0.8", @@ -1768,24 +2095,38 @@ ], "description": "Symfony Translation Component", "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], "time": "2020-04-12T16:45:47+00:00" }, { "name": "symfony/translation-contracts", - "version": "v2.0.1", + "version": "v2.1.2", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "8cc682ac458d75557203b2f2f14b0b92e1c744ed" + "reference": "e5ca07c8f817f865f618aa072c2fe8e0e637340e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/8cc682ac458d75557203b2f2f14b0b92e1c744ed", - "reference": "8cc682ac458d75557203b2f2f14b0b92e1c744ed", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/e5ca07c8f817f865f618aa072c2fe8e0e637340e", + "reference": "e5ca07c8f817f865f618aa072c2fe8e0e637340e", "shasum": "" }, "require": { - "php": "^7.2.5" + "php": ">=7.2.5" }, "suggest": { "symfony/translation-implementation": "" @@ -1793,7 +2134,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-master": "2.1-dev" } }, "autoload": { @@ -1825,7 +2166,21 @@ "interoperability", "standards" ], - "time": "2019-11-18T17:27:11+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-05-20T17:43:50+00:00" }, { "name": "symfony/var-dumper", @@ -1900,8 +2255,96 @@ "debug", "dump" ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], "time": "2020-04-12T16:45:47+00:00" }, + { + "name": "symfony/var-exporter", + "version": "v5.0.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-exporter.git", + "reference": "5d18811da9e1ae2bb86b0a97fb2d784e27ffd59f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/5d18811da9e1ae2bb86b0a97fb2d784e27ffd59f", + "reference": "5d18811da9e1ae2bb86b0a97fb2d784e27ffd59f", + "shasum": "" + }, + "require": { + "php": "^7.2.5" + }, + "require-dev": { + "symfony/var-dumper": "^4.4|^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\VarExporter\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A blend of var_export() + serialize() to turn any serializable data structure to plain PHP code", + "homepage": "https://symfony.com", + "keywords": [ + "clone", + "construct", + "export", + "hydrate", + "instantiate", + "serialize" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-04-15T15:59:10+00:00" + }, { "name": "symfony/yaml", "version": "v5.0.8", @@ -1959,11 +2402,25 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], "time": "2020-04-28T17:58:55+00:00" }, { "name": "tightenco/collect", - "version": "v7.11.0", + "version": "v7.12.0", "source": { "type": "git", "url": "https://github.com/tightenco/collect.git", @@ -2135,6 +2592,16 @@ "env", "environment" ], + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv", + "type": "tidelift" + } + ], "time": "2020-05-23T09:43:32+00:00" } ], @@ -2385,6 +2852,12 @@ "Xdebug", "performance" ], + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + } + ], "time": "2020-03-01T12:26:26+00:00" }, { @@ -2422,22 +2895,22 @@ }, { "name": "doctrine/annotations", - "version": "1.10.2", + "version": "1.10.3", "source": { "type": "git", "url": "https://github.com/doctrine/annotations.git", - "reference": "b9d758e831c70751155c698c2f7df4665314a1cb" + "reference": "5db60a4969eba0e0c197a19c077780aadbc43c5d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/b9d758e831c70751155c698c2f7df4665314a1cb", - "reference": "b9d758e831c70751155c698c2f7df4665314a1cb", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/5db60a4969eba0e0c197a19c077780aadbc43c5d", + "reference": "5db60a4969eba0e0c197a19c077780aadbc43c5d", "shasum": "" }, "require": { "doctrine/lexer": "1.*", "ext-tokenizer": "*", - "php": "^7.1" + "php": "^7.1 || ^8.0" }, "require-dev": { "doctrine/cache": "1.*", @@ -2487,7 +2960,7 @@ "docblock", "parser" ], - "time": "2020-04-20T09:18:32+00:00" + "time": "2020-05-25T17:24:27+00:00" }, { "name": "doctrine/instantiator", @@ -2547,20 +3020,20 @@ }, { "name": "doctrine/lexer", - "version": "1.2.0", + "version": "1.2.1", "source": { "type": "git", "url": "https://github.com/doctrine/lexer.git", - "reference": "5242d66dbeb21a30dd8a3e66bf7a73b66e05e1f6" + "reference": "e864bbf5904cb8f5bb334f99209b48018522f042" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/5242d66dbeb21a30dd8a3e66bf7a73b66e05e1f6", - "reference": "5242d66dbeb21a30dd8a3e66bf7a73b66e05e1f6", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/e864bbf5904cb8f5bb334f99209b48018522f042", + "reference": "e864bbf5904cb8f5bb334f99209b48018522f042", "shasum": "" }, "require": { - "php": "^7.2" + "php": "^7.2 || ^8.0" }, "require-dev": { "doctrine/coding-standard": "^6.0", @@ -2605,7 +3078,21 @@ "parser", "php" ], - "time": "2019-10-30T14:39:59+00:00" + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", + "type": "tidelift" + } + ], + "time": "2020-05-25T17:44:05+00:00" }, { "name": "felixfbecker/advanced-json-rpc", @@ -2784,6 +3271,12 @@ } ], "description": "A tool to automatically fix PHP code style", + "funding": [ + { + "url": "https://github.com/keradus", + "type": "github" + } + ], "time": "2020-04-15T18:51:10+00:00" }, { @@ -2977,6 +3470,16 @@ } ], "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", + "funding": [ + { + "url": "https://github.com/Ocramius", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/ocramius/package-versions", + "type": "tidelift" + } + ], "time": "2020-04-06T17:43:35+00:00" }, { @@ -3499,6 +4002,12 @@ "testing", "xunit" ], + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], "time": "2020-05-23T08:02:54+00:00" }, { @@ -3549,6 +4058,12 @@ "filesystem", "iterator" ], + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], "time": "2020-04-18T05:02:12+00:00" }, { @@ -3697,6 +4212,12 @@ "keywords": [ "timer" ], + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], "time": "2020-04-20T06:00:37+00:00" }, { @@ -3746,6 +4267,12 @@ "keywords": [ "tokenizer" ], + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], "time": "2020-05-06T09:56:31+00:00" }, { @@ -3834,6 +4361,16 @@ "testing", "xunit" ], + "funding": [ + { + "url": "https://phpunit.de/donate.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], "time": "2020-05-22T13:54:05+00:00" }, { @@ -3998,6 +4535,12 @@ ], "description": "Collection of value objects that represent the PHP code units", "homepage": "https://github.com/sebastianbergmann/code-unit", + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], "time": "2020-04-30T05:58:10+00:00" }, { @@ -4163,6 +4706,12 @@ "unidiff", "unified diff" ], + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], "time": "2020-05-08T05:01:12+00:00" }, { @@ -4216,6 +4765,12 @@ "environment", "hhvm" ], + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], "time": "2020-04-14T13:36:52+00:00" }, { @@ -4692,6 +5247,20 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], "time": "2020-03-30T11:42:42+00:00" }, { @@ -4762,24 +5331,38 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], "time": "2020-03-27T16:56:45+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v2.0.1", + "version": "v2.1.2", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "af23c2584d4577d54661c434446fb8fbed6025dd" + "reference": "405952c4e90941a17e52ef7489a2bd94870bb290" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/af23c2584d4577d54661c434446fb8fbed6025dd", - "reference": "af23c2584d4577d54661c434446fb8fbed6025dd", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/405952c4e90941a17e52ef7489a2bd94870bb290", + "reference": "405952c4e90941a17e52ef7489a2bd94870bb290", "shasum": "" }, "require": { - "php": "^7.2.5", + "php": ">=7.2.5", "psr/event-dispatcher": "^1" }, "suggest": { @@ -4788,7 +5371,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-master": "2.1-dev" } }, "autoload": { @@ -4820,7 +5403,21 @@ "interoperability", "standards" ], - "time": "2019-11-18T17:27:11+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-05-20T17:43:50+00:00" }, { "name": "symfony/filesystem", @@ -4870,6 +5467,20 @@ ], "description": "Symfony Filesystem Component", "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], "time": "2020-04-12T14:40:17+00:00" }, { @@ -4924,6 +5535,20 @@ "configuration", "options" ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], "time": "2020-04-06T10:40:56+00:00" }, { @@ -4983,6 +5608,20 @@ "portable", "shim" ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], "time": "2020-05-12T16:47:27+00:00" }, { @@ -5038,6 +5677,20 @@ "portable", "shim" ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], "time": "2020-05-12T16:47:27+00:00" }, { @@ -5096,6 +5749,20 @@ "portable", "shim" ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], "time": "2020-05-12T16:47:27+00:00" }, { @@ -5145,65 +5812,21 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2020-04-15T15:59:10+00:00" - }, - { - "name": "symfony/service-contracts", - "version": "v2.0.1", - "source": { - "type": "git", - "url": "https://github.com/symfony/service-contracts.git", - "reference": "144c5e51266b281231e947b51223ba14acf1a749" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/144c5e51266b281231e947b51223ba14acf1a749", - "reference": "144c5e51266b281231e947b51223ba14acf1a749", - "shasum": "" - }, - "require": { - "php": "^7.2.5", - "psr/container": "^1.0" - }, - "suggest": { - "symfony/service-implementation": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\Service\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ + "funding": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "url": "https://symfony.com/sponsor", + "type": "custom" }, { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } ], - "description": "Generic abstractions related to writing services", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "time": "2019-11-18T17:27:11+00:00" + "time": "2020-04-15T15:59:10+00:00" }, { "name": "symfony/stopwatch", @@ -5253,6 +5876,20 @@ ], "description": "Symfony Stopwatch Component", "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], "time": "2020-03-27T16:56:45+00:00" }, { @@ -5297,16 +5934,16 @@ }, { "name": "vimeo/psalm", - "version": "3.11.4", + "version": "3.11.5", "source": { "type": "git", "url": "https://github.com/vimeo/psalm.git", - "reference": "58e1d8e68e5098bf4fbfdfb420c38d563f882549" + "reference": "3c60609c218d4d4b3b257728b8089094e5c6c6c2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vimeo/psalm/zipball/58e1d8e68e5098bf4fbfdfb420c38d563f882549", - "reference": "58e1d8e68e5098bf4fbfdfb420c38d563f882549", + "url": "https://api.github.com/repos/vimeo/psalm/zipball/3c60609c218d4d4b3b257728b8089094e5c6c6c2", + "reference": "3c60609c218d4d4b3b257728b8089094e5c6c6c2", "shasum": "" }, "require": { @@ -5368,8 +6005,7 @@ }, "autoload": { "psr-4": { - "Psalm\\Plugin\\": "src/Psalm/Plugin", - "Psalm\\": "src/Psalm" + "Psalm\\": "src/Psalm/" }, "files": [ "src/functions.php", @@ -5391,7 +6027,7 @@ "inspection", "php" ], - "time": "2020-05-11T13:39:25+00:00" + "time": "2020-05-27T15:12:09+00:00" }, { "name": "webmozart/assert", @@ -5548,5 +6184,6 @@ }, "platform-dev": { "php": ">=7.4" - } + }, + "plugin-api-version": "1.1.0" } diff --git a/docker-compose.yaml b/docker-compose.yaml index 84a1c42..065f279 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -12,6 +12,18 @@ services: extra_hosts: - host.docker.internal:${DOCKER_HOST_IP} + memcached: + container_name: directory-lister-memcached + ports: + - ${MEMCACHED_PORT:-11211}:11211 + image: memcached:1.6 + + redis: + container_name: directory-lister-redis + ports: + - ${REDIS_PORT:-6379}:6379 + image: redis:6.0 + networks: default: external: diff --git a/index.php b/index.php index 648c1bd..79d46a2 100644 --- a/index.php +++ b/index.php @@ -14,6 +14,7 @@ Dotenv::createImmutable(__DIR__)->safeLoad(); // Initialize the application $app = (new ContainerBuilder)->addDefinitions( + __DIR__ . '/app/config/cache.php', __DIR__ . '/app/config/app.php', __DIR__ . '/app/config/container.php' )->build()->call(AppManager::class); diff --git a/psalm-baseline.xml b/psalm-baseline.xml index f462a69..ba4a64c 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -1,10 +1,16 @@ - + current + + + 'memcached_config' + 'redis_config' + + name diff --git a/tests/.env b/tests/.env index 2a27301..7de2285 100644 --- a/tests/.env +++ b/tests/.env @@ -17,4 +17,13 @@ DATE_FORMAT="Y-m-d H:i:s" MAX_HASH_SIZE=1000000000 +CACHE_DRIVER=array +CACHE_LIFETIME=0 + +MEMCACHED_HOST=127.0.0.1 +MEMCACHED_PORT=11211 + +REDIS_HOST=127.0.0.1 +REDIS_PORT=6379 + VIEW_CACHE=false diff --git a/tests/Controllers/FileInfoControllerTest.php b/tests/Controllers/FileInfoControllerTest.php index a6f041e..e89ec42 100644 --- a/tests/Controllers/FileInfoControllerTest.php +++ b/tests/Controllers/FileInfoControllerTest.php @@ -13,7 +13,11 @@ class FileInfoControllerTest extends TestCase { public function test_it_can_return_a_successful_response(): void { - $handler = new FileInfoController($this->container, $this->container->get(TranslatorInterface::class)); + $handler = new FileInfoController( + $this->container, + $this->cache, + $this->container->get(TranslatorInterface::class) + ); $request = $this->createMock(Request::class); $request->method('getQueryParams')->willReturn(['info' => 'README.md']); @@ -33,7 +37,11 @@ class FileInfoControllerTest extends TestCase public function test_it_can_return_a_not_found_response(): void { - $handler = new FileInfoController($this->container, $this->container->get(TranslatorInterface::class)); + $handler = new FileInfoController( + $this->container, + $this->cache, + $this->container->get(TranslatorInterface::class) + ); $request = $this->createMock(Request::class); $request->method('getQueryParams')->willReturn(['info' => 'not_a_file.test']); @@ -47,7 +55,11 @@ class FileInfoControllerTest extends TestCase public function test_it_returns_an_error_when_file_size_is_too_large(): void { $this->container->set('max_hash_size', 10); - $handler = new FileInfoController($this->container, $this->container->get(TranslatorInterface::class)); + $handler = new FileInfoController( + $this->container, + $this->cache, + $this->container->get(TranslatorInterface::class) + ); $request = $this->createMock(Request::class); $request->method('getQueryParams')->willReturn(['info' => 'README.md']); diff --git a/tests/Controllers/ZipHandlerTest.php b/tests/Controllers/ZipControllerTest.php similarity index 95% rename from tests/Controllers/ZipHandlerTest.php rename to tests/Controllers/ZipControllerTest.php index b1b8b9c..1a20874 100644 --- a/tests/Controllers/ZipHandlerTest.php +++ b/tests/Controllers/ZipControllerTest.php @@ -10,12 +10,13 @@ use Symfony\Component\Finder\Finder; use Symfony\Contracts\Translation\TranslatorInterface; use Tests\TestCase; -class ZipHandlerTest extends TestCase +class ZipControllerTest extends TestCase { public function test_it_returns_a_successful_response_for_a_zip_request(): void { $handler = new ZipController( $this->container, + $this->cache, new Finder, $this->container->get(TranslatorInterface::class) ); @@ -37,6 +38,7 @@ class ZipHandlerTest extends TestCase { $handler = new ZipController( $this->container, + $this->cache, new Finder, $this->container->get(TranslatorInterface::class) ); @@ -56,6 +58,7 @@ class ZipHandlerTest extends TestCase $this->container->set('zip_downloads', false); $handler = new ZipController( $this->container, + $this->cache, new Finder, $this->container->get(TranslatorInterface::class) ); diff --git a/tests/Factories/CacheFactoryTest.php b/tests/Factories/CacheFactoryTest.php new file mode 100644 index 0000000..da5f0e7 --- /dev/null +++ b/tests/Factories/CacheFactoryTest.php @@ -0,0 +1,74 @@ +container->set('cache_driver', 'apcu'); + + $cache = (new CacheFactory($this->container))(); + + $this->assertInstanceOf(Adapter\ApcuAdapter::class, $cache); + } + + public function test_it_can_compose_an_array_adapter(): void + { + $this->container->set('cache_driver', 'array'); + + $cache = (new CacheFactory($this->container))(); + + $this->assertInstanceOf(Adapter\ArrayAdapter::class, $cache); + } + + public function test_it_can_compose_a_filesystem_adapter(): void + { + $this->container->set('cache_driver', 'file'); + + $cache = (new CacheFactory($this->container))(); + + $this->assertInstanceOf(Adapter\FilesystemAdapter::class, $cache); + } + + public function test_it_can_compose_a_memcached_adapter(): void + { + $this->container->set('cache_driver', 'memcached'); + + $cache = (new CacheFactory($this->container))(); + + $this->assertInstanceOf(Adapter\MemcachedAdapter::class, $cache); + } + + public function test_it_can_compose_a_php_files_adapter(): void + { + $this->container->set('cache_driver', 'php-file'); + + $cache = (new CacheFactory($this->container))(); + + $this->assertInstanceOf(Adapter\PhpFilesAdapter::class, $cache); + } + + public function test_it_can_compose_a_redis_adapter(): void + { + $this->container->set('cache_driver', 'redis'); + + $cache = (new CacheFactory($this->container))(); + + $this->assertInstanceOf(Adapter\RedisAdapter::class, $cache); + } + + public function test_it_throws_a_runtime_exception_with_an_invalid_sort_order(): void + { + $this->container->set('cache_driver', 'invalid'); + + $this->expectException(InvalidConfiguration::class); + + (new CacheFactory($this->container))(); + } +} diff --git a/tests/Factories/FinderFactoryTest.php b/tests/Factories/FinderFactoryTest.php index 0281ad1..e6d404e 100644 --- a/tests/Factories/FinderFactoryTest.php +++ b/tests/Factories/FinderFactoryTest.php @@ -12,7 +12,7 @@ class FinderFactoryTest extends TestCase { public function test_it_can_compose_the_finder_component(): void { - $finder = (new FinderFactory($this->container))(); + $finder = (new FinderFactory($this->container, $this->cache))(); $this->assertInstanceOf(Finder::class, $finder); @@ -35,7 +35,7 @@ class FinderFactoryTest extends TestCase } )); - $finder = (new FinderFactory($this->container))(); + $finder = (new FinderFactory($this->container, $this->cache))(); $finder->in($this->filePath('subdir'))->depth(0); $this->assertEquals([ @@ -51,7 +51,7 @@ class FinderFactoryTest extends TestCase { $this->container->set('reverse_sort', true); - $finder = (new FinderFactory($this->container))(); + $finder = (new FinderFactory($this->container, $this->cache))(); $finder->in($this->filePath('subdir'))->depth(0); $this->assertEquals([ @@ -69,7 +69,7 @@ class FinderFactoryTest extends TestCase 'subdir/alpha.scss', 'subdir/charlie.bash', '**/*.yaml' ]); - (new FinderFactory($this->container))(); + (new FinderFactory($this->container, $this->cache))(); $finder = $this->container->get(Finder::class); $finder->in($this->filePath('subdir'))->depth(0); @@ -87,7 +87,7 @@ class FinderFactoryTest extends TestCase $this->expectException(RuntimeException::class); - (new FinderFactory($this->container))(); + (new FinderFactory($this->container, $this->cache))(); } protected function getFilesArray(Finder $finder): array diff --git a/tests/Factories/TranslationFactoryTest.php b/tests/Factories/TranslationFactoryTest.php index 7ed74ba..fc43537 100644 --- a/tests/Factories/TranslationFactoryTest.php +++ b/tests/Factories/TranslationFactoryTest.php @@ -11,7 +11,7 @@ class TranslationFactoryTest extends TestCase { public function test_it_registers_the_translation_component(): void { - $translator = (new TranslationFactory($this->container))(); + $translator = (new TranslationFactory($this->container, $this->cache))(); $this->assertEquals('en', $translator->getLocale()); $this->assertInstanceOf(MessageCatalogue::class, $translator->getCatalogue('de')); @@ -32,6 +32,6 @@ class TranslationFactoryTest extends TestCase $this->expectException(RuntimeException::class); $this->container->set('language', 'xx'); - (new TranslationFactory($this->container))(); + (new TranslationFactory($this->container, $this->cache))(); } } diff --git a/tests/TestCase.php b/tests/TestCase.php index ad41ef6..bc50814 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -6,12 +6,17 @@ use DI\Container; use DI\ContainerBuilder; use Dotenv\Dotenv; use PHPUnit\Framework\TestCase as PHPUnitTestCase; +use Symfony\Component\Cache\Adapter\ArrayAdapter; +use Symfony\Contracts\Cache\CacheInterface; class TestCase extends PHPUnitTestCase { /** @var Container The test container */ protected $container; + /** @var CacheInterface The test cache */ + protected $cache; + /** @var string Path to test files directory */ protected $testFilesPath = __DIR__ . '/_files'; @@ -25,10 +30,13 @@ class TestCase extends PHPUnitTestCase Dotenv::createImmutable(__DIR__)->safeLoad(); $this->container = (new ContainerBuilder)->addDefinitions( + dirname(__DIR__) . '/app/config/cache.php', dirname(__DIR__) . '/app/config/app.php', dirname(__DIR__) . '/app/config/container.php' )->build(); + $this->cache = new ArrayAdapter($this->container->get('cache_lifetime')); + $this->container->set('base_path', $this->testFilesPath); $this->container->set('asset_path', $this->filePath('app/assets')); $this->container->set('cache_path', $this->filePath('app/cache')); diff --git a/tests/ViewFunctions/MarkdownTest.php b/tests/ViewFunctions/MarkdownTest.php index efd761e..16bd426 100644 --- a/tests/ViewFunctions/MarkdownTest.php +++ b/tests/ViewFunctions/MarkdownTest.php @@ -3,15 +3,18 @@ namespace Tests\ViewFunctions; use App\ViewFunctions\Markdown; +use ParsedownExtra; use Tests\TestCase; class MarkdownTest extends TestCase { public function test_it_can_parse_markdown_into_html(): void { + $markdown = new Markdown(new ParsedownExtra, $this->cache); + $this->assertEquals( '

Test markdown, please ignore

', - (new Markdown)('**Test** `markdown`, ~~please~~ _ignore_') + $markdown('**Test** `markdown`, ~~please~~ _ignore_') ); } }