Initial commit cache
This commit is contained in:
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
.DS_Store
|
||||||
|
.idea/
|
||||||
|
composer.lock
|
||||||
|
vendor/
|
||||||
|
tmp/
|
||||||
|
.vs/
|
||||||
|
.vscode/
|
||||||
27
composer.json
Normal file
27
composer.json
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"name": "iqparts/cache",
|
||||||
|
"type": "library",
|
||||||
|
"require": {
|
||||||
|
"predis/predis": "^1.1"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "^6.3",
|
||||||
|
"phpstan/phpstan": "^0.9.1"
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"IQParts\\Cache\\": ["src"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload-dev": {
|
||||||
|
"psr-4": {
|
||||||
|
"IQParts\\CacheTest\\": ["test"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Mèir Noordermeer",
|
||||||
|
"email": "meirnoordermeer@me.com"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
18
phpunit.xml
Normal file
18
phpunit.xml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
|
||||||
|
colors="true"
|
||||||
|
verbose="true"
|
||||||
|
backupGlobals="false"
|
||||||
|
beStrictAboutOutputDuringTests="true"
|
||||||
|
beStrictAboutTestsThatDoNotTestAnything="true"
|
||||||
|
>
|
||||||
|
<testsuite name="IQParts Cache tests">
|
||||||
|
<directory>./test/Unit</directory>
|
||||||
|
</testsuite>
|
||||||
|
<filter>
|
||||||
|
<whitelist addUncoveredFilesFromWhitelist="true">
|
||||||
|
<directory suffix=".php">./src</directory>
|
||||||
|
</whitelist>
|
||||||
|
</filter>
|
||||||
|
</phpunit>
|
||||||
39
src/Adapter/CacheAdapterInterface.php
Normal file
39
src/Adapter/CacheAdapterInterface.php
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace IQParts\Cache\Adapter;
|
||||||
|
|
||||||
|
interface CacheAdapterInterface
|
||||||
|
{
|
||||||
|
public const NO_TTL = -1;
|
||||||
|
/**
|
||||||
|
* @param string $key
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function get(string $key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $key
|
||||||
|
* @param $value
|
||||||
|
* @param int|null $ttl
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function set(string $key, $value, int $ttl = null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $key
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function delete(string $key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $key
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function keys($key = '*');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $key
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function ttl($key): int;
|
||||||
|
}
|
||||||
242
src/Adapter/FilesystemAdapter.php
Normal file
242
src/Adapter/FilesystemAdapter.php
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace IQParts\Cache\Adapter;
|
||||||
|
|
||||||
|
use IQParts\Cache\Serializer\SerializerInterface;
|
||||||
|
|
||||||
|
class FilesystemAdapter implements CacheAdapterInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $directory;
|
||||||
|
/**
|
||||||
|
* @var null|int
|
||||||
|
*/
|
||||||
|
private $chmod;
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $directorySeparator;
|
||||||
|
/**
|
||||||
|
* @var int|null
|
||||||
|
*/
|
||||||
|
private $ttl;
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $timeToLive;
|
||||||
|
/**
|
||||||
|
* @var SerializerInterface
|
||||||
|
*/
|
||||||
|
private $serializer;
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $index;
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $indexLocation;
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $ttlLocation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FilesystemAdapter constructor.
|
||||||
|
* @param string $directory
|
||||||
|
* @param SerializerInterface $serializer
|
||||||
|
* @param int|null $chmod
|
||||||
|
* @param int|null $ttl
|
||||||
|
* @param string|null $directorySeperator
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
string $directory,
|
||||||
|
SerializerInterface $serializer,
|
||||||
|
int $chmod = null,
|
||||||
|
int $ttl = null,
|
||||||
|
string $directorySeperator = null
|
||||||
|
)
|
||||||
|
{
|
||||||
|
$this->indexLocation = $directory . '/' . md5($directory) . 'index';
|
||||||
|
$this->ttlLocation = $directory . '/' . md5($directory) . 'ttl';
|
||||||
|
$this->directory = $directory;
|
||||||
|
$this->serializer = $serializer;
|
||||||
|
$this->directorySeparator = $directorySeperator;
|
||||||
|
$this->chmod = $chmod;
|
||||||
|
$this->ttl = $ttl;
|
||||||
|
|
||||||
|
if (file_exists($this->indexLocation)) {
|
||||||
|
$this->index = json_decode(file_get_contents($this->indexLocation), true);
|
||||||
|
} else {
|
||||||
|
$this->index = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file_exists($this->ttlLocation)) {
|
||||||
|
$this->timeToLive = json_decode(file_get_contents($this->ttlLocation), true);
|
||||||
|
} else {
|
||||||
|
$this->timeToLive = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $key
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function get(string $key)
|
||||||
|
{
|
||||||
|
$file = $this->getFilename($key);
|
||||||
|
if ($this->exists($file)) {
|
||||||
|
if (isset($this->timeToLive[$key])) {
|
||||||
|
if ($this->ttl($key) > 0) {
|
||||||
|
return $this->serializer->deserialize(file_get_contents($file));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return $this->serializer->deserialize(file_get_contents($file));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $key
|
||||||
|
* @param $value
|
||||||
|
* @param int|null $ttl
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function set(string $key, $value, int $ttl = null)
|
||||||
|
{
|
||||||
|
$file = $this->getFilename($key);
|
||||||
|
file_put_contents($file, $this->serializer->serialize($value));
|
||||||
|
if ($this->chmod !== null) {
|
||||||
|
chmod($file, $this->chmod);
|
||||||
|
}
|
||||||
|
if ($ttl !== null) {
|
||||||
|
$this->timeToLive[$key] = $ttl;
|
||||||
|
} else if ($this->ttl !== null) {
|
||||||
|
$this->timeToLive[$key] = $this->ttl;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->index[$key] = $file;
|
||||||
|
$this->saveIndex();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $key
|
||||||
|
*/
|
||||||
|
public function delete(string $key)
|
||||||
|
{
|
||||||
|
if (strpos($key, '*') !== false) {
|
||||||
|
$this->deleteGlob($key);
|
||||||
|
$this->saveIndex();
|
||||||
|
} else {
|
||||||
|
$file = $this->getFilename($key);
|
||||||
|
if ($this->exists($file)) {
|
||||||
|
unlink($file);
|
||||||
|
}
|
||||||
|
unset($this->index[$key]);
|
||||||
|
unset($this->timeToLive[$key]);
|
||||||
|
$this->saveIndex();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $key
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function keys($key = '*')
|
||||||
|
{
|
||||||
|
$matches = [];
|
||||||
|
foreach ($this->index as $name => $index) {
|
||||||
|
if (fnmatch($key, $name)) {
|
||||||
|
$matches[] = $name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $matches;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $key
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function ttl($key): int
|
||||||
|
{
|
||||||
|
$file = $this->getFilename($key);
|
||||||
|
|
||||||
|
if (isset($this->timeToLive[$key])) {
|
||||||
|
return max(filemtime($file) + $this->timeToLive[$key] - time(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::NO_TTL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $pattern
|
||||||
|
*/
|
||||||
|
private function deleteGlob($pattern)
|
||||||
|
{
|
||||||
|
foreach ($this->index as $key => $value) {
|
||||||
|
if (fnmatch($pattern, $key)) {
|
||||||
|
$this->delete($key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $file
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
private function exists(string $file)
|
||||||
|
{
|
||||||
|
return file_exists($file);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $key
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function getFilename(string $key)
|
||||||
|
{
|
||||||
|
list($directory, $file) = $this->getDirectoryAndFile($key);
|
||||||
|
return $directory . '/' . md5($file);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $key
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function getDirectoryAndFile(string $key)
|
||||||
|
{
|
||||||
|
$directory = $this->directory;
|
||||||
|
if ($this->directorySeparator !== null) {
|
||||||
|
while (($position = strpos($key, $this->directorySeparator)) !== false) {
|
||||||
|
$dirName = md5(substr($key, 0, $position));
|
||||||
|
$directory = $directory . '/' . $dirName;
|
||||||
|
$this->createSubDirectoryIfNotExists($directory);
|
||||||
|
$key = substr($key, $position + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [$directory, $key];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $directory
|
||||||
|
*/
|
||||||
|
private function createSubDirectoryIfNotExists($directory)
|
||||||
|
{
|
||||||
|
if (file_exists($directory) === false) {
|
||||||
|
mkdir($directory);
|
||||||
|
if ($this->chmod !== null) {
|
||||||
|
chmod($directory, $this->chmod);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function saveIndex()
|
||||||
|
{
|
||||||
|
file_put_contents($this->indexLocation, json_encode($this->index));
|
||||||
|
file_put_contents($this->ttlLocation, json_encode($this->timeToLive));
|
||||||
|
}
|
||||||
|
}
|
||||||
91
src/Adapter/MemoryAdapter.php
Normal file
91
src/Adapter/MemoryAdapter.php
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace IQParts\Cache\Adapter;
|
||||||
|
|
||||||
|
final class MemoryAdapter implements CacheAdapterInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $data = [];
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $timeToLive = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $key
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function get(string $key)
|
||||||
|
{
|
||||||
|
if (!isset($this->data[$key])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($this->timeToLive[$key])) {
|
||||||
|
return $this->data[$key];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->ttl($key) > 0) {
|
||||||
|
return $this->data[$key];
|
||||||
|
}
|
||||||
|
|
||||||
|
unset($this->timeToLive[$key]);
|
||||||
|
unset($this->data[$key]);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $key
|
||||||
|
* @param $value
|
||||||
|
* @param int|null $ttl
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function set(string $key, $value, int $ttl = null)
|
||||||
|
{
|
||||||
|
$this->data[$key] = $value;
|
||||||
|
if ($ttl !== null) {
|
||||||
|
$this->timeToLive[$key] = time() + $ttl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $key
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function delete(string $key)
|
||||||
|
{
|
||||||
|
unset($this->data[$key]);
|
||||||
|
unset($this->timeToLive[$key]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $key
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function keys($key = '*')
|
||||||
|
{
|
||||||
|
$matches = [];
|
||||||
|
foreach ($this->data as $name => $value) {
|
||||||
|
if (fnmatch($key, $name)) {
|
||||||
|
$matches[] = $name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $matches;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $key
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function ttl($key): int
|
||||||
|
{
|
||||||
|
if (isset($this->timeToLive[$key])) {
|
||||||
|
return max($this->timeToLive[$key] - time(), 0);
|
||||||
|
}
|
||||||
|
return self::NO_TTL;
|
||||||
|
}
|
||||||
|
}
|
||||||
87
src/Adapter/NamespaceAdapter.php
Normal file
87
src/Adapter/NamespaceAdapter.php
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace IQParts\Cache\Adapter;
|
||||||
|
|
||||||
|
final class NamespaceAdapter implements CacheAdapterInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $namespace;
|
||||||
|
/**
|
||||||
|
* @var CacheAdapterInterface
|
||||||
|
*/
|
||||||
|
private $cacheAdapter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NamespaceAdapter constructor.
|
||||||
|
* @param string $namespace
|
||||||
|
* @param CacheAdapterInterface $cacheAdapter
|
||||||
|
*/
|
||||||
|
public function __construct(string $namespace, CacheAdapterInterface $cacheAdapter)
|
||||||
|
{
|
||||||
|
$this->namespace = $namespace . ':';
|
||||||
|
$this->cacheAdapter = $cacheAdapter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $key
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function get(string $key)
|
||||||
|
{
|
||||||
|
return $this->cacheAdapter->get($this->namespace . $key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $key
|
||||||
|
* @param $value
|
||||||
|
* @param int|null $ttl
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function set(string $key, $value, int $ttl = null): void
|
||||||
|
{
|
||||||
|
$this->cacheAdapter->set($this->namespace . $key, $value, $ttl);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $key
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function delete(string $key)
|
||||||
|
{
|
||||||
|
$this->cacheAdapter->delete($this->namespace . $key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $key
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function keys($key = '*')
|
||||||
|
{
|
||||||
|
if (substr($key, -1) !== '*') {
|
||||||
|
$key = $key . '*';
|
||||||
|
}
|
||||||
|
|
||||||
|
$keys = $this->cacheAdapter->keys($this->namespace . $key);
|
||||||
|
$allowed = [];
|
||||||
|
$len = strlen($this->namespace);
|
||||||
|
|
||||||
|
foreach ($keys as $key) {
|
||||||
|
if (substr($key, 0, $len) === $this->namespace) {
|
||||||
|
$allowed[] = substr($key, $len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $key
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function ttl($key): int
|
||||||
|
{
|
||||||
|
return $this->cacheAdapter->ttl($this->namespace . $key);
|
||||||
|
}
|
||||||
|
}
|
||||||
52
src/Adapter/NullAdapter.php
Normal file
52
src/Adapter/NullAdapter.php
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace IQParts\Cache\Adapter;
|
||||||
|
|
||||||
|
final class NullAdapter implements CacheAdapterInterface
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $key
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function get(string $key)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $key
|
||||||
|
* @param $value
|
||||||
|
* @param int|null $ttl
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function set(string $key, $value, int $ttl = null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $key
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function delete(string $key)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $key
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function keys($key = '')
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $key
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function ttl($key): int
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
86
src/Adapter/PredisAdapter.php
Normal file
86
src/Adapter/PredisAdapter.php
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace IQParts\Cache\Adapter;
|
||||||
|
|
||||||
|
use IQParts\Cache\Serializer\SerializerInterface;
|
||||||
|
use Predis\Client;
|
||||||
|
use Predis\ClientInterface;
|
||||||
|
|
||||||
|
final class PredisAdapter implements CacheAdapterInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var Client
|
||||||
|
*/
|
||||||
|
private $client;
|
||||||
|
/**
|
||||||
|
* @var SerializerInterface
|
||||||
|
*/
|
||||||
|
private $serializer;
|
||||||
|
/**
|
||||||
|
* @var null
|
||||||
|
*/
|
||||||
|
private $defaultTtl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PredisAdapter constructor.
|
||||||
|
* @param ClientInterface $client
|
||||||
|
* @param SerializerInterface $serializer
|
||||||
|
* @param int $defaultTtl
|
||||||
|
*/
|
||||||
|
public function __construct(ClientInterface $client, SerializerInterface $serializer, $defaultTtl = null)
|
||||||
|
{
|
||||||
|
$this->client = $client;
|
||||||
|
$this->serializer = $serializer;
|
||||||
|
$this->defaultTtl = $defaultTtl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $key
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function get(string $key)
|
||||||
|
{
|
||||||
|
return $this->serializer->deserialize($this->client->get($key));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $key
|
||||||
|
* @param $value
|
||||||
|
* @param int|null $ttl TTL in seconds
|
||||||
|
*/
|
||||||
|
public function set(string $key, $value, int $ttl = null)
|
||||||
|
{
|
||||||
|
if ($ttl !== null && $ttl !== 0) {
|
||||||
|
$this->client->setex($key, $ttl ?? $this->defaultTtl, $this->serializer->serialize($value));
|
||||||
|
} else {
|
||||||
|
$this->client->set($key, $this->serializer->serialize($value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $key
|
||||||
|
* @return integer
|
||||||
|
*/
|
||||||
|
public function delete(string $key): int
|
||||||
|
{
|
||||||
|
return $this->client->del([$key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $key
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function keys($key = '*')
|
||||||
|
{
|
||||||
|
return $this->client->keys($key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $key
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function ttl($key): int
|
||||||
|
{
|
||||||
|
return $this->client->ttl($key);
|
||||||
|
}
|
||||||
|
}
|
||||||
20
src/Serializer/JsonSerializer.php
Normal file
20
src/Serializer/JsonSerializer.php
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace IQParts\Cache\Serializer;
|
||||||
|
|
||||||
|
final class JsonSerializer implements SerializerInterface
|
||||||
|
{
|
||||||
|
public function serialize($input)
|
||||||
|
{
|
||||||
|
if (is_array($input)) {
|
||||||
|
return json_encode($input);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $input;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deserialize($string)
|
||||||
|
{
|
||||||
|
return json_decode($string, true) ?? $string;
|
||||||
|
}
|
||||||
|
}
|
||||||
17
src/Serializer/NoSerializer.php
Normal file
17
src/Serializer/NoSerializer.php
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace IQParts\Cache\Serializer;
|
||||||
|
|
||||||
|
final class NoSerializer implements SerializerInterface
|
||||||
|
{
|
||||||
|
public function serialize($input)
|
||||||
|
{
|
||||||
|
return $input;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deserialize($string)
|
||||||
|
{
|
||||||
|
return $string;
|
||||||
|
}
|
||||||
|
}
|
||||||
10
src/Serializer/SerializerInterface.php
Normal file
10
src/Serializer/SerializerInterface.php
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace IQParts\Cache\Serializer;
|
||||||
|
|
||||||
|
interface SerializerInterface
|
||||||
|
{
|
||||||
|
public function serialize($input);
|
||||||
|
|
||||||
|
public function deserialize($string);
|
||||||
|
}
|
||||||
20
test/AbstractTestCase.php
Normal file
20
test/AbstractTestCase.php
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace IQParts\CacheTest;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
abstract class AbstractTestCase extends TestCase
|
||||||
|
{
|
||||||
|
public function getTmpDirectory()
|
||||||
|
{
|
||||||
|
$location = __DIR__ . '/../tmp';
|
||||||
|
if (!file_exists($location)) {
|
||||||
|
if (!mkdir($location)) {
|
||||||
|
throw new \RuntimeException('Could not create directory: ' . $location);
|
||||||
|
}
|
||||||
|
chmod($location, 0777);
|
||||||
|
}
|
||||||
|
return $location;
|
||||||
|
}
|
||||||
|
}
|
||||||
121
test/Unit/Adapter/FilesystemAdapterTest.php
Normal file
121
test/Unit/Adapter/FilesystemAdapterTest.php
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace IQParts\CacheTest\Unit\Adapter;
|
||||||
|
|
||||||
|
use IQParts\Cache\Adapter\FilesystemAdapter;
|
||||||
|
use IQParts\Cache\Serializer\JsonSerializer;
|
||||||
|
use IQParts\CacheTest\AbstractTestCase;
|
||||||
|
|
||||||
|
final class FilesystemAdapterTest extends AbstractTestCase
|
||||||
|
{
|
||||||
|
public function testGetSet()
|
||||||
|
{
|
||||||
|
$location = $this->getTmpDirectory();
|
||||||
|
$adapter = new FilesystemAdapter(
|
||||||
|
$location,
|
||||||
|
new JsonSerializer(),
|
||||||
|
0777
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
$adapter->set('a', 'b');
|
||||||
|
$this->assertEquals('b', $adapter->get('a'));
|
||||||
|
|
||||||
|
$adapter->delete('a');
|
||||||
|
$this->assertEquals(null, $adapter->get('a'));
|
||||||
|
|
||||||
|
$adapter->set('ttl', 'b', 200);
|
||||||
|
$this->assertTrue($adapter->ttl('ttl') > 0);
|
||||||
|
$this->assertTrue($adapter->ttl('b') === $adapter::NO_TTL);
|
||||||
|
|
||||||
|
$adapter->set('a-keys', 'a');
|
||||||
|
$adapter->set('b-keys', 'a');
|
||||||
|
$this->assertEquals(['a-keys'], $adapter->keys('a*'));
|
||||||
|
$adapter->delete('b');
|
||||||
|
$adapter->delete('a-keys');
|
||||||
|
$adapter->delete('b-keys');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testTtl()
|
||||||
|
{
|
||||||
|
$location = $this->getTmpDirectory();
|
||||||
|
$adapter = new FilesystemAdapter(
|
||||||
|
$location,
|
||||||
|
new JsonSerializer(),
|
||||||
|
0777
|
||||||
|
);
|
||||||
|
|
||||||
|
$adapter->set('a', 'b', 10);
|
||||||
|
|
||||||
|
$this->assertEquals('b', $adapter->get('a'));
|
||||||
|
$this->assertTrue($adapter->ttl('a') > 0);
|
||||||
|
|
||||||
|
$adapter->set('b', 'a', -10);
|
||||||
|
$this->assertNull($adapter->get('b'));
|
||||||
|
|
||||||
|
$adapter->delete('b');
|
||||||
|
$adapter->delete('a');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDefaultTtl()
|
||||||
|
{
|
||||||
|
$location = $this->getTmpDirectory();
|
||||||
|
$adapter = new FilesystemAdapter(
|
||||||
|
$location,
|
||||||
|
new JsonSerializer(),
|
||||||
|
0777,
|
||||||
|
10
|
||||||
|
);
|
||||||
|
|
||||||
|
$adapter->set('a', 'b');
|
||||||
|
$this->assertTrue($adapter->ttl('a') > 0);
|
||||||
|
$adapter->delete('a');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDelete()
|
||||||
|
{
|
||||||
|
$location = $this->getTmpDirectory();
|
||||||
|
$adapter = new FilesystemAdapter(
|
||||||
|
$location,
|
||||||
|
new JsonSerializer(),
|
||||||
|
0777
|
||||||
|
);
|
||||||
|
|
||||||
|
$adapter->set('del-a', 'a');
|
||||||
|
$adapter->set('del-b', 'b');
|
||||||
|
$adapter->delete('del*');
|
||||||
|
$this->assertNull($adapter->get('del-a'));
|
||||||
|
$this->assertNull($adapter->get('del-b'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSubFolder()
|
||||||
|
{
|
||||||
|
$location = $this->getTmpDirectory();
|
||||||
|
$adapter = new FilesystemAdapter(
|
||||||
|
$location,
|
||||||
|
new JsonSerializer(),
|
||||||
|
0777
|
||||||
|
);
|
||||||
|
|
||||||
|
$adapter->set('a/b', 'a');
|
||||||
|
$this->assertEquals('a', $adapter->get('a/b'));
|
||||||
|
$adapter->delete('a/b');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSubFolderWithSeperator()
|
||||||
|
{
|
||||||
|
$location = $this->getTmpDirectory();
|
||||||
|
$adapter = new FilesystemAdapter(
|
||||||
|
$location,
|
||||||
|
new JsonSerializer(),
|
||||||
|
0777,
|
||||||
|
null,
|
||||||
|
'/'
|
||||||
|
);
|
||||||
|
|
||||||
|
$adapter->set('a/b', 'a');
|
||||||
|
$this->assertEquals('a', $adapter->get('a/b'));
|
||||||
|
$adapter->delete('a/b');
|
||||||
|
}
|
||||||
|
}
|
||||||
42
test/Unit/Adapter/MemoryAdapterTest.php
Normal file
42
test/Unit/Adapter/MemoryAdapterTest.php
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace IQParts\CacheTest\Unit\Adapter;
|
||||||
|
|
||||||
|
use IQParts\Cache\Adapter\MemoryAdapter;
|
||||||
|
use IQParts\CacheTest\AbstractTestCase;
|
||||||
|
|
||||||
|
final class MemoryAdapterTest extends AbstractTestCase
|
||||||
|
{
|
||||||
|
public function testAdapter()
|
||||||
|
{
|
||||||
|
$adapter = new MemoryAdapter();
|
||||||
|
$this->assertEquals(null, $adapter->get('a'));
|
||||||
|
|
||||||
|
$adapter->set('a', 'b');
|
||||||
|
$this->assertEquals('b', $adapter->get('a'));
|
||||||
|
|
||||||
|
$adapter->delete('a');
|
||||||
|
$this->assertEquals(null, $adapter->get('a'));
|
||||||
|
|
||||||
|
$adapter->set('a-keys', 'a');
|
||||||
|
$adapter->set('b-keys', 'a');
|
||||||
|
$this->assertEquals(['a-keys'], $adapter->keys('a*'));
|
||||||
|
|
||||||
|
$adapter->set('a', 'b', 200);
|
||||||
|
$this->assertTrue($adapter->ttl('a') > 0);
|
||||||
|
$this->assertTrue($adapter->ttl('c') === MemoryAdapter::NO_TTL);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testTtl()
|
||||||
|
{
|
||||||
|
$adapter = new MemoryAdapter();
|
||||||
|
$adapter->set('a', 'b', 10);
|
||||||
|
|
||||||
|
$this->assertEquals('b', $adapter->get('a'));
|
||||||
|
$this->assertTrue($adapter->ttl('a') > 0);
|
||||||
|
|
||||||
|
$adapter->set('b', 'a', -10);
|
||||||
|
$this->assertNull($adapter->get('b'));
|
||||||
|
}
|
||||||
|
}
|
||||||
61
test/Unit/Adapter/NamespaceAdapterTest.php
Normal file
61
test/Unit/Adapter/NamespaceAdapterTest.php
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace IQParts\CacheTest\Unit\Adapter;
|
||||||
|
|
||||||
|
use IQParts\Cache\Adapter\MemoryAdapter;
|
||||||
|
use IQParts\CacheTest\AbstractTestCase;
|
||||||
|
use IQParts\Cache\Adapter\CacheAdapterInterface;
|
||||||
|
use IQParts\Cache\Adapter\NamespaceAdapter;
|
||||||
|
use PhpParser\Node\Name;
|
||||||
|
|
||||||
|
final class NamespaceAdapterTest extends AbstractTestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var CacheAdapterInterface
|
||||||
|
*/
|
||||||
|
private $adapter;
|
||||||
|
|
||||||
|
public function setUp()
|
||||||
|
{
|
||||||
|
$this->adapter = new MemoryAdapter();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGet()
|
||||||
|
{
|
||||||
|
$adapter = new NamespaceAdapter('test', $this->adapter);
|
||||||
|
$adapter->set('mykey', 'a');
|
||||||
|
$this->assertEquals('test:mykey', $this->adapter->keys()[0]);
|
||||||
|
$this->assertEquals('a', $adapter->get('mykey'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSet()
|
||||||
|
{
|
||||||
|
$adapter = new NamespaceAdapter('test', $this->adapter);
|
||||||
|
$adapter->set('mykey', null);
|
||||||
|
$this->assertEquals('test:mykey', $this->adapter->keys()[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDelete()
|
||||||
|
{
|
||||||
|
$adapter = new NamespaceAdapter('test', $this->adapter);
|
||||||
|
$adapter->delete('mykey');
|
||||||
|
$this->assertEquals(null, $this->adapter->get('mykey'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testKeys()
|
||||||
|
{
|
||||||
|
$adapter = new NamespaceAdapter('test', $this->adapter);
|
||||||
|
$adapter->set('a-keys', 'a');
|
||||||
|
$adapter->set('b-keys', 'b');
|
||||||
|
$this->assertEquals(['a-keys'], $adapter->keys('a-*'));
|
||||||
|
$this->assertEquals(['a-keys'], $adapter->keys('a-'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testTtl()
|
||||||
|
{
|
||||||
|
$adapter = new NamespaceAdapter('test', $this->adapter);
|
||||||
|
$adapter->set('a', 'b', 10);
|
||||||
|
$this->assertTrue($adapter->ttl('a') > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
19
test/Unit/Adapter/NullAdapterTest.php
Normal file
19
test/Unit/Adapter/NullAdapterTest.php
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace IQParts\CacheTest\Unit\Adapter;
|
||||||
|
|
||||||
|
use IQParts\CacheTest\AbstractTestCase;
|
||||||
|
use IQParts\Cache\Adapter\NullAdapter;
|
||||||
|
|
||||||
|
final class NullAdapterTest extends AbstractTestCase
|
||||||
|
{
|
||||||
|
public function testNullAdapter()
|
||||||
|
{
|
||||||
|
$adapter = new NullAdapter();
|
||||||
|
$adapter->set('a', 'b');
|
||||||
|
$this->assertEquals(null, $adapter->get('a'));
|
||||||
|
$adapter->delete('a');
|
||||||
|
$this->assertEquals([], $adapter->keys());
|
||||||
|
$this->assertEquals(0, $adapter->ttl('a'));
|
||||||
|
}
|
||||||
|
}
|
||||||
114
test/Unit/Adapter/PredisAdapterTest.php
Normal file
114
test/Unit/Adapter/PredisAdapterTest.php
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace IQParts\CacheTest\Unit\Adapter;
|
||||||
|
|
||||||
|
use IQParts\Cache\Adapter\PredisAdapter;
|
||||||
|
use IQParts\Cache\Serializer\JsonSerializer;
|
||||||
|
use IQParts\CacheTest\AbstractTestCase;
|
||||||
|
use Predis\ClientInterface;
|
||||||
|
|
||||||
|
final class PredisAdapterTest extends AbstractTestCase
|
||||||
|
{
|
||||||
|
public function testGet()
|
||||||
|
{
|
||||||
|
$client = $this->createMock(ClientInterface::class);
|
||||||
|
$client
|
||||||
|
->expects($this->at(0))
|
||||||
|
->method('__call')
|
||||||
|
->with('get', ['a'])
|
||||||
|
->willReturn(null);
|
||||||
|
|
||||||
|
/** @var ClientInterface $client */
|
||||||
|
$cache = new PredisAdapter($client, new JsonSerializer());
|
||||||
|
$this->assertNull($cache->get('a'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSet()
|
||||||
|
{
|
||||||
|
$client = $this->createMock(ClientInterface::class);
|
||||||
|
$client
|
||||||
|
->expects($this->at(0))
|
||||||
|
->method('__call')
|
||||||
|
->with('set', ['a', 'b']);
|
||||||
|
$client
|
||||||
|
->expects($this->at(1))
|
||||||
|
->method('__call')
|
||||||
|
->with('get', ['a'])
|
||||||
|
->willReturn('b');
|
||||||
|
|
||||||
|
/** @var ClientInterface $client */
|
||||||
|
$cache = new PredisAdapter($client, new JsonSerializer());
|
||||||
|
$cache->set('a', 'b');
|
||||||
|
$this->assertEquals('b', $cache->get('a'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDelete()
|
||||||
|
{
|
||||||
|
$client = $this->createMock(ClientInterface::class);
|
||||||
|
$client
|
||||||
|
->expects($this->at(0))
|
||||||
|
->method('__call')
|
||||||
|
->with('set', ['a', 'b']);
|
||||||
|
$client
|
||||||
|
->expects($this->at(1))
|
||||||
|
->method('__call')
|
||||||
|
->with('del', [['a']])
|
||||||
|
->willReturn(1);
|
||||||
|
$client
|
||||||
|
->expects($this->at(2))
|
||||||
|
->method('__call')
|
||||||
|
->with('get', ['a'])
|
||||||
|
->willReturn(null);
|
||||||
|
|
||||||
|
/** @var ClientInterface $client */
|
||||||
|
$cache = new PredisAdapter($client, new JsonSerializer());
|
||||||
|
$cache->set('a', 'b');
|
||||||
|
$cache->delete('a');
|
||||||
|
$this->assertNull($cache->get('a'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testTtl()
|
||||||
|
{
|
||||||
|
$client = $this->createMock(ClientInterface::class);
|
||||||
|
$client
|
||||||
|
->expects($this->at(0))
|
||||||
|
->method('__call')
|
||||||
|
->with('setex', ['a', 200, 'b']);
|
||||||
|
$client
|
||||||
|
->expects($this->at(1))
|
||||||
|
->method('__call')
|
||||||
|
->with('ttl', ['a'])
|
||||||
|
->willReturn(200);
|
||||||
|
|
||||||
|
/** @var ClientInterface $client */
|
||||||
|
$cache = new PredisAdapter($client, new JsonSerializer());
|
||||||
|
$cache->set('a', 'b', 200);
|
||||||
|
$this->assertEquals(200, $cache->ttl('a'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testKeys()
|
||||||
|
{
|
||||||
|
$client = $this->createMock(ClientInterface::class);
|
||||||
|
$client
|
||||||
|
->expects($this->at(0))
|
||||||
|
->method('__call')
|
||||||
|
->with('set', ['a-keys', '1']);
|
||||||
|
$client
|
||||||
|
->expects($this->at(1))
|
||||||
|
->method('__call')
|
||||||
|
->with('set', ['b-keys', '1']);
|
||||||
|
$client
|
||||||
|
->expects($this->at(2))
|
||||||
|
->method('__call')
|
||||||
|
->with('keys', ['a*'])
|
||||||
|
->willReturn(['a-keys']);
|
||||||
|
|
||||||
|
/** @var ClientInterface $client */
|
||||||
|
$cache = new PredisAdapter($client, new JsonSerializer());
|
||||||
|
$cache->set('a-keys', '1');
|
||||||
|
$cache->set('b-keys', '1');
|
||||||
|
$this->assertEquals(['a-keys'], $cache->keys('a*'));
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
22
test/Unit/Serializer/JsonSerializerTest.php
Normal file
22
test/Unit/Serializer/JsonSerializerTest.php
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace IQParts\CacheTest\Unit\Serializer;
|
||||||
|
|
||||||
|
use IQParts\CacheTest\AbstractTestCase;
|
||||||
|
use IQParts\Cache\Serializer\JsonSerializer;
|
||||||
|
|
||||||
|
final class JsonSerializerTest extends AbstractTestCase
|
||||||
|
{
|
||||||
|
public function testSerialize()
|
||||||
|
{
|
||||||
|
$serializer = new JsonSerializer();
|
||||||
|
$string = "myString";
|
||||||
|
$array = [0 => '1', 1 => '2', 2 => '3'];
|
||||||
|
|
||||||
|
$this->assertTrue(is_string($serializer->serialize($array)));
|
||||||
|
$this->assertEquals($string, $serializer->serialize($string));
|
||||||
|
|
||||||
|
$this->assertEquals($array, $serializer->deserialize($serializer->serialize($array)));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
22
test/Unit/Serializer/NoSerializerTest.php
Normal file
22
test/Unit/Serializer/NoSerializerTest.php
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace IQParts\CacheTest\Unit\Serializer;
|
||||||
|
|
||||||
|
use IQParts\Cache\Serializer\NoSerializer;
|
||||||
|
use IQParts\CacheTest\AbstractTestCase;
|
||||||
|
|
||||||
|
final class NoSerializerTest extends AbstractTestCase
|
||||||
|
{
|
||||||
|
public function testSerialize()
|
||||||
|
{
|
||||||
|
$serializer = new NoSerializer();
|
||||||
|
$this->assertEquals('a', $serializer->serialize('a'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDeserialize()
|
||||||
|
{
|
||||||
|
$serializer = new NoSerializer();
|
||||||
|
$this->assertEquals('a', $serializer->deserialize('a'));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user