mirror of
https://github.com/shlinkio/shlink.git
synced 2025-02-17 15:59:56 +03:00
Enhanced CacheFactory to support redis and allow optional APCu
This commit is contained in:
parent
c9be89647c
commit
16653d60ed
3 changed files with 119 additions and 41 deletions
40
module/Common/README.md
Normal file
40
module/Common/README.md
Normal file
|
@ -0,0 +1,40 @@
|
|||
# Shlink Common
|
||||
|
||||
This library provides some utils and conventions for web apps. It's main purpose is to be used on [Shlink](https://github.com/shlinkio/shlink) project, but any PHP project can take advantage.
|
||||
|
||||
Most of the elements it provides require a [PSR-11] container, and it's easy to integrate on [expressive] applications thanks to the `ConfigProvider` it includes.
|
||||
|
||||
## Cache
|
||||
|
||||
A [doctrine cache] adapter is registered, which returns different instances depending on your configuration:
|
||||
|
||||
* An `ArrayCache` instance when the `debug` config is set to true.
|
||||
* A `PredisCache` instance when the `cache.redis` config is defined.
|
||||
* An `ArrayCache` instance when no `cache.redis` is defined and the APCu extension is not installed.
|
||||
* An `ApcuCache`instance when no `cache.redis` is defined and the APCu extension is installed.
|
||||
|
||||
Any of the adapters will use the namespace defined in `cache.namespace` config entry.
|
||||
|
||||
```php
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
|
||||
'debug' => false,
|
||||
|
||||
'cache' => [
|
||||
'namespace' => 'my_namespace',
|
||||
'redis' => [
|
||||
'servers' => [
|
||||
'tcp://1.1.1.1:6379',
|
||||
'tcp://2.2.2.2:6379',
|
||||
'tcp://3.3.3.3:6379',
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
```
|
||||
|
||||
When the `cache.redis` config is provided, a set of servers is expected. If only one server is provided, this library will treat it as a regular server, but if several servers are defined, it will treat them as a redis cluster and expect the servers to be configured as such.
|
|
@ -4,22 +4,48 @@ declare(strict_types=1);
|
|||
namespace Shlinkio\Shlink\Common\Cache;
|
||||
|
||||
use Doctrine\Common\Cache;
|
||||
use Interop\Container\ContainerInterface;
|
||||
use Shlinkio\Shlink\Core\Options\AppOptions;
|
||||
use Zend\ServiceManager\Factory\FactoryInterface;
|
||||
use Predis\Client as PredisClient;
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
||||
use function Shlinkio\Shlink\Common\env;
|
||||
use function extension_loaded;
|
||||
|
||||
class CacheFactory implements FactoryInterface
|
||||
class CacheFactory
|
||||
{
|
||||
public function __invoke(ContainerInterface $container, $requestedName, ?array $options = null): Cache\Cache
|
||||
{
|
||||
// TODO Make use of the redis cache via RedisFactory when possible
|
||||
/** @var callable|null */
|
||||
private $apcuEnabled;
|
||||
|
||||
$appOptions = $container->get(AppOptions::class);
|
||||
$adapter = env('APP_ENV', 'pro') === 'pro' ? new Cache\ApcuCache() : new Cache\ArrayCache();
|
||||
$adapter->setNamespace((string) $appOptions);
|
||||
public function __construct(?callable $apcuEnabled = null)
|
||||
{
|
||||
$this->apcuEnabled = $apcuEnabled ?? function () {
|
||||
return extension_loaded('apcu');
|
||||
};
|
||||
}
|
||||
|
||||
public function __invoke(ContainerInterface $container): Cache\CacheProvider
|
||||
{
|
||||
$config = $container->get('config');
|
||||
$adapter = $this->buildAdapter($config, $container);
|
||||
$adapter->setNamespace($config['cache']['namespace'] ?? '');
|
||||
|
||||
return $adapter;
|
||||
}
|
||||
|
||||
private function buildAdapter(array $config, ContainerInterface $container): Cache\CacheProvider
|
||||
{
|
||||
$isDebug = (bool) ($config['debug'] ?? false);
|
||||
$redisConfig = $config['cache']['redis'] ?? null;
|
||||
$apcuEnabled = ($this->apcuEnabled)();
|
||||
|
||||
if ($isDebug || (! $apcuEnabled && $redisConfig === null)) {
|
||||
return new Cache\ArrayCache();
|
||||
}
|
||||
|
||||
if ($redisConfig === null) {
|
||||
return new Cache\ApcuCache();
|
||||
}
|
||||
|
||||
/** @var PredisClient $predis */
|
||||
$predis = $container->get(RedisFactory::SERVICE_NAME);
|
||||
return new Cache\PredisCache($predis);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,48 +3,60 @@ declare(strict_types=1);
|
|||
|
||||
namespace ShlinkioTest\Shlink\Common\Cache;
|
||||
|
||||
use Doctrine\Common\Cache\ApcuCache;
|
||||
use Doctrine\Common\Cache\ArrayCache;
|
||||
use Doctrine\Common\Cache;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Predis\ClientInterface;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Shlinkio\Shlink\Common\Cache\CacheFactory;
|
||||
use Shlinkio\Shlink\Core\Options\AppOptions;
|
||||
use Zend\ServiceManager\ServiceManager;
|
||||
|
||||
use function putenv;
|
||||
use Shlinkio\Shlink\Common\Cache\RedisFactory;
|
||||
|
||||
class CacheFactoryTest extends TestCase
|
||||
{
|
||||
/** @var CacheFactory */
|
||||
private $factory;
|
||||
/** @var ServiceManager */
|
||||
private $sm;
|
||||
/** @var ObjectProphecy */
|
||||
private $container;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->factory = new CacheFactory();
|
||||
$this->sm = new ServiceManager(['services' => [
|
||||
AppOptions::class => new AppOptions(),
|
||||
]]);
|
||||
$this->container = $this->prophesize(ContainerInterface::class);
|
||||
}
|
||||
|
||||
public static function tearDownAfterClass(): void
|
||||
{
|
||||
putenv('APP_ENV');
|
||||
/**
|
||||
* @test
|
||||
* @dataProvider provideCacheConfig
|
||||
*/
|
||||
public function expectedCacheAdapterIsReturned(
|
||||
array $config,
|
||||
string $expectedAdapterClass,
|
||||
string $expectedNamespace,
|
||||
?callable $apcuEnabled = null
|
||||
): void {
|
||||
$factory = new CacheFactory($apcuEnabled);
|
||||
|
||||
$getConfig = $this->container->get('config')->willReturn($config);
|
||||
$getRedis = $this->container->get(RedisFactory::SERVICE_NAME)->willReturn(
|
||||
$this->prophesize(ClientInterface::class)->reveal()
|
||||
);
|
||||
|
||||
$cache = $factory($this->container->reveal());
|
||||
|
||||
$this->assertInstanceOf($expectedAdapterClass, $cache);
|
||||
$this->assertEquals($expectedNamespace, $cache->getNamespace());
|
||||
$getConfig->shouldHaveBeenCalledOnce();
|
||||
$getRedis->shouldHaveBeenCalledTimes($expectedAdapterClass === Cache\PredisCache::class ? 1 :0);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function productionReturnsApcAdapter(): void
|
||||
public function provideCacheConfig(): iterable
|
||||
{
|
||||
putenv('APP_ENV=pro');
|
||||
$instance = ($this->factory)($this->sm, '');
|
||||
$this->assertInstanceOf(ApcuCache::class, $instance);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function developmentReturnsArrayAdapter(): void
|
||||
{
|
||||
putenv('APP_ENV=dev');
|
||||
$instance = ($this->factory)($this->sm, '');
|
||||
$this->assertInstanceOf(ArrayCache::class, $instance);
|
||||
yield 'debug true' => [['debug' => true], Cache\ArrayCache::class, ''];
|
||||
yield 'debug false' => [['debug' => false], Cache\ApcuCache::class, ''];
|
||||
yield 'no debug' => [[], Cache\ApcuCache::class, ''];
|
||||
yield 'with redis' => [['cache' => [
|
||||
'namespace' => $namespace = 'some_namespace',
|
||||
'redis' => [],
|
||||
]], Cache\PredisCache::class, $namespace];
|
||||
yield 'debug false and no apcu' => [['debug' => false], Cache\ArrayCache::class, '', function () {
|
||||
return false;
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue