Add FileCache driver

This commit is contained in:
Frederic Guillot 2016-08-21 18:46:34 -04:00
parent 836e935463
commit 8e83e404fb
No known key found for this signature in database
GPG Key ID: 92D77191BA7FBC99
15 changed files with 442 additions and 118 deletions

View File

@ -18,6 +18,7 @@ use Pimple\Container;
* @property \Kanboard\Core\Action\ActionManager $actionManager
* @property \Kanboard\Core\ExternalLink\ExternalLinkManager $externalLinkManager
* @property \Kanboard\Core\Cache\MemoryCache $memoryCache
* @property \Kanboard\Core\Cache\BaseCache $cacheDriver
* @property \Kanboard\Core\Event\EventManager $eventManager
* @property \Kanboard\Core\Group\GroupManager $groupManager
* @property \Kanboard\Core\Http\Client $httpClient

View File

@ -1,38 +0,0 @@
<?php
namespace Kanboard\Core\Cache;
/**
* Base class for cache drivers
*
* @package cache
* @author Frederic Guillot
*/
abstract class Base
{
/**
* Proxy cache
*
* Note: Arguments must be scalar types
*
* @access public
* @param string $class Class instance
* @param string $method Container method
* @return mixed
*/
public function proxy($class, $method)
{
$args = func_get_args();
array_shift($args);
$key = 'proxy:'.get_class($class).':'.implode(':', $args);
$result = $this->get($key);
if ($result === null) {
$result = call_user_func_array(array($class, $method), array_splice($args, 1));
$this->set($key, $result);
}
return $result;
}
}

View File

@ -0,0 +1,71 @@
<?php
namespace Kanboard\Core\Cache;
/**
* Base Class for Cache Drivers
*
* @package Kanboard\Core\Cache
* @author Frederic Guillot
*/
abstract class BaseCache
{
/**
* Store an item in the cache
*
* @access public
* @param string $key
* @param string $value
*/
abstract public function set($key, $value);
/**
* Retrieve an item from the cache by key
*
* @access public
* @param string $key
* @return mixed Null when not found, cached value otherwise
*/
abstract public function get($key);
/**
* Remove all items from the cache
*
* @access public
*/
abstract public function flush();
/**
* Remove an item from the cache
*
* @access public
* @param string $key
*/
abstract public function remove($key);
/**
* Proxy cache
*
* Note: Arguments must be scalar types
*
* @access public
* @param string $class Class instance
* @param string $method Container method
* @return mixed
*/
public function proxy($class, $method)
{
$args = func_get_args();
array_shift($args);
$key = 'proxy:'.get_class($class).':'.implode(':', $args);
$result = $this->get($key);
if ($result === null) {
$result = call_user_func_array(array($class, $method), array_splice($args, 1));
$this->set($key, $result);
}
return $result;
}
}

View File

@ -1,45 +0,0 @@
<?php
namespace Kanboard\Core\Cache;
/**
* Cache Interface
*
* @package cache
* @author Frederic Guillot
*/
interface CacheInterface
{
/**
* Save a new value in the cache
*
* @access public
* @param string $key
* @param string $value
*/
public function set($key, $value);
/**
* Fetch value from cache
*
* @access public
* @param string $key
* @return mixed Null when not found, cached value otherwise
*/
public function get($key);
/**
* Clear all cache
*
* @access public
*/
public function flush();
/**
* Remove cached value
*
* @access public
* @param string $key
*/
public function remove($key);
}

View File

@ -0,0 +1,98 @@
<?php
namespace Kanboard\Core\Cache;
use Kanboard\Core\Tool;
use LogicException;
/**
* Class FileCache
*
* @package Kanboard\Core\Cache
*/
class FileCache extends BaseCache
{
/**
* Store an item in the cache
*
* @access public
* @param string $key
* @param string $value
*/
public function set($key, $value)
{
$this->createCacheFolder();
file_put_contents($this->getFilenameFromKey($key), serialize($value));
}
/**
* Retrieve an item from the cache by key
*
* @access public
* @param string $key
* @return mixed Null when not found, cached value otherwise
*/
public function get($key)
{
$filename = $this->getFilenameFromKey($key);
if (file_exists($filename)) {
return unserialize(file_get_contents($filename));
}
return null;
}
/**
* Remove all items from the cache
*
* @access public
*/
public function flush()
{
$this->createCacheFolder();
Tool::removeAllFiles(CACHE_DIR, false);
}
/**
* Remove an item from the cache
*
* @access public
* @param string $key
*/
public function remove($key)
{
$filename = $this->getFilenameFromKey($key);
if (file_exists($filename)) {
unlink($filename);
}
}
/**
* Get absolute filename from the key
*
* @access protected
* @param string $key
* @return string
*/
protected function getFilenameFromKey($key)
{
return CACHE_DIR.DIRECTORY_SEPARATOR.$key;
}
/**
* Create cache folder if missing
*
* @access protected
* @throws LogicException
*/
protected function createCacheFolder()
{
if (! is_dir(CACHE_DIR)) {
if (! mkdir(CACHE_DIR, 0755)) {
throw new LogicException('Unable to create cache directory: '.CACHE_DIR);
}
}
}
}

View File

@ -3,12 +3,12 @@
namespace Kanboard\Core\Cache;
/**
* Memory Cache
* Memory Cache Driver
*
* @package cache
* @package Kanboard\Core\Cache
* @author Frederic Guillot
*/
class MemoryCache extends Base implements CacheInterface
class MemoryCache extends BaseCache
{
/**
* Container
@ -19,7 +19,7 @@ class MemoryCache extends Base implements CacheInterface
private $storage = array();
/**
* Save a new value in the cache
* Store an item in the cache
*
* @access public
* @param string $key
@ -31,7 +31,7 @@ class MemoryCache extends Base implements CacheInterface
}
/**
* Fetch value from cache
* Retrieve an item from the cache by key
*
* @access public
* @param string $key

View File

@ -2,9 +2,8 @@
namespace Kanboard\Core\Plugin;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use ZipArchive;
use Kanboard\Core\Tool;
/**
* Class Installer
@ -64,7 +63,7 @@ class Installer extends \Kanboard\Core\Base
throw new PluginInstallerException(e('You don\'t have the permission to remove this plugin.'));
}
$this->removeAllDirectories($pluginFolder);
Tool::removeAllFiles($pluginFolder);
}
/**
@ -137,26 +136,4 @@ class Installer extends \Kanboard\Core\Base
unlink($zip->filename);
$zip->close();
}
/**
* Remove recursively a directory
*
* @access protected
* @param string $directory
*/
protected function removeAllDirectories($directory)
{
$it = new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS);
$files = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST);
foreach ($files as $file) {
if ($file->isDir()) {
rmdir($file->getRealPath());
} else {
unlink($file->getRealPath());
}
}
rmdir($directory);
}
}

View File

@ -3,6 +3,8 @@
namespace Kanboard\Core;
use Pimple\Container;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
/**
* Tool class
@ -12,6 +14,32 @@ use Pimple\Container;
*/
class Tool
{
/**
* Remove recursively a directory
*
* @static
* @access public
* @param string $directory
* @param bool $removeDirectory
*/
public static function removeAllFiles($directory, $removeDirectory = true)
{
$it = new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS);
$files = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST);
foreach ($files as $file) {
if ($file->isDir()) {
rmdir($file->getRealPath());
} else {
unlink($file->getRealPath());
}
}
if ($removeDirectory) {
rmdir($directory);
}
}
/**
* Build dependency injection container from an array
*

View File

@ -0,0 +1,41 @@
<?php
namespace Kanboard\ServiceProvider;
use Kanboard\Core\Cache\FileCache;
use Kanboard\Core\Cache\MemoryCache;
use Pimple\Container;
use Pimple\ServiceProviderInterface;
/**
* Cache Provider
*
* @package Kanboard\ServiceProvider
* @author Frederic Guillot
*/
class CacheProvider implements ServiceProviderInterface
{
/**
* Register providers
*
* @access public
* @param \Pimple\Container $container
* @return \Pimple\Container
*/
public function register(Container $container)
{
$container['memoryCache'] = function() {
return new MemoryCache();
};
if (CACHE_DRIVER === 'file') {
$container['cacheDriver'] = function() {
return new FileCache();
};
} else {
$container['cacheDriver'] = $container['memoryCache'];
}
return $container;
}
}

View File

@ -140,9 +140,6 @@ class ClassProvider implements ServiceProviderInterface
'Response',
'RememberMeCookie',
),
'Core\Cache' => array(
'MemoryCache',
),
'Core\Plugin' => array(
'Hook',
),

View File

@ -35,6 +35,7 @@ $container->register(new Kanboard\ServiceProvider\MailProvider());
$container->register(new Kanboard\ServiceProvider\HelperProvider());
$container->register(new Kanboard\ServiceProvider\SessionProvider());
$container->register(new Kanboard\ServiceProvider\LoggingProvider());
$container->register(new Kanboard\ServiceProvider\CacheProvider());
$container->register(new Kanboard\ServiceProvider\DatabaseProvider());
$container->register(new Kanboard\ServiceProvider\AuthenticationProvider());
$container->register(new Kanboard\ServiceProvider\NotificationProvider());

View File

@ -12,6 +12,12 @@ defined('DATA_DIR') or define('DATA_DIR', ROOT_DIR.DIRECTORY_SEPARATOR.'data');
// Files directory (attachments)
defined('FILES_DIR') or define('FILES_DIR', DATA_DIR.DIRECTORY_SEPARATOR.'files');
// Available cache drivers are "file" and "memory"
defined('CACHE_DRIVER') or define('CACHE_DRIVER', 'memory');
// Cache folder (file driver)
defined('CACHE_DIR') or define('CACHE_DIR', DATA_DIR.DIRECTORY_SEPARATOR.'cache');
// Plugins settings
defined('PLUGINS_DIR') or define('PLUGINS_DIR', ROOT_DIR.DIRECTORY_SEPARATOR.'plugins');
defined('PLUGIN_API_URL') or define('PLUGIN_API_URL', 'https://kanboard.net/plugins.json');

View File

@ -38,6 +38,7 @@ abstract class Base extends PHPUnit_Framework_TestCase
}
$this->container = new Pimple\Container;
$this->container->register(new Kanboard\ServiceProvider\CacheProvider());
$this->container->register(new Kanboard\ServiceProvider\HelperProvider());
$this->container->register(new Kanboard\ServiceProvider\AuthenticationProvider());
$this->container->register(new Kanboard\ServiceProvider\DatabaseProvider());

View File

@ -0,0 +1,186 @@
<?php
namespace Kanboard\Core\Cache;
require_once __DIR__.'/../../Base.php';
function file_put_contents($filename, $data)
{
return FileCacheTest::$functions->file_put_contents($filename, $data);
}
function file_get_contents($filename)
{
return FileCacheTest::$functions->file_get_contents($filename);
}
function mkdir($filename, $mode = 0777, $recursif = false)
{
return FileCacheTest::$functions->mkdir($filename, $mode, $recursif);
}
function is_dir($filename)
{
return FileCacheTest::$functions->is_dir($filename);
}
function file_exists($filename)
{
return FileCacheTest::$functions->file_exists($filename);
}
function unlink($filename)
{
return FileCacheTest::$functions->unlink($filename);
}
class FileCacheTest extends \Base
{
/**
* @var \PHPUnit_Framework_MockObject_MockObject
*/
public static $functions;
public function setUp()
{
parent::setup();
self::$functions = $this
->getMockBuilder('stdClass')
->setMethods(array(
'file_put_contents',
'file_get_contents',
'file_exists',
'mkdir',
'is_dir',
'unlink',
))
->getMock();
}
public function tearDown()
{
parent::tearDown();
self::$functions = null;
}
public function testSet()
{
$key = 'mykey';
$data = 'data';
$cache = new FileCache();
self::$functions
->expects($this->at(0))
->method('is_dir')
->with(
$this->equalTo(CACHE_DIR)
)
->will($this->returnValue(false));
self::$functions
->expects($this->at(1))
->method('mkdir')
->with(
$this->equalTo(CACHE_DIR),
0755
)
->will($this->returnValue(true));
self::$functions
->expects($this->at(2))
->method('file_put_contents')
->with(
$this->equalTo(CACHE_DIR.DIRECTORY_SEPARATOR.$key),
$this->equalTo(serialize($data))
)
->will($this->returnValue(true));
$cache->set($key, $data);
}
public function testGet()
{
$key = 'mykey';
$data = 'data';
$cache = new FileCache();
self::$functions
->expects($this->at(0))
->method('file_exists')
->with(
$this->equalTo(CACHE_DIR.DIRECTORY_SEPARATOR.$key)
)
->will($this->returnValue(true));
self::$functions
->expects($this->at(1))
->method('file_get_contents')
->with(
$this->equalTo(CACHE_DIR.DIRECTORY_SEPARATOR.$key)
)
->will($this->returnValue(serialize($data)));
$this->assertSame($data, $cache->get($key));
}
public function testGetWithKeyNotFound()
{
$key = 'mykey';
$cache = new FileCache();
self::$functions
->expects($this->at(0))
->method('file_exists')
->with(
$this->equalTo(CACHE_DIR.DIRECTORY_SEPARATOR.$key)
)
->will($this->returnValue(false));
$this->assertNull($cache->get($key));
}
public function testRemoveWithKeyNotFound()
{
$key = 'mykey';
$cache = new FileCache();
self::$functions
->expects($this->at(0))
->method('file_exists')
->with(
$this->equalTo(CACHE_DIR.DIRECTORY_SEPARATOR.$key)
)
->will($this->returnValue(false));
self::$functions
->expects($this->never())
->method('unlink');
$cache->remove($key);
}
public function testRemove()
{
$key = 'mykey';
$cache = new FileCache();
self::$functions
->expects($this->at(0))
->method('file_exists')
->with(
$this->equalTo(CACHE_DIR.DIRECTORY_SEPARATOR.$key)
)
->will($this->returnValue(true));
self::$functions
->expects($this->at(1))
->method('unlink')
->with(
$this->equalTo(CACHE_DIR.DIRECTORY_SEPARATOR.$key)
)
->will($this->returnValue(true));
$cache->remove($key);
}
}

View File

@ -2,7 +2,7 @@
namespace Kanboard\Core\ObjectStorage;
require_once __DIR__.'/../Base.php';
require_once __DIR__.'/../../Base.php';
function file_put_contents($filename, $data)
{
@ -105,7 +105,7 @@ class FileStorageTest extends \Base
->method('file_put_contents')
->with(
$this->equalTo('somewhere'.DIRECTORY_SEPARATOR.'mykey'),
$this->equalTo('data')
$this->equalTo($data)
)
->will($this->returnValue(true));