243 lines
5.9 KiB
PHP
243 lines
5.9 KiB
PHP
<?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 $directorySeparator
|
|
*/
|
|
public function __construct(
|
|
string $directory,
|
|
SerializerInterface $serializer,
|
|
int $chmod = null,
|
|
int $ttl = null,
|
|
string $directorySeparator = null
|
|
)
|
|
{
|
|
$this->indexLocation = $directory . '/' . md5($directory) . 'index';
|
|
$this->ttlLocation = $directory . '/' . md5($directory) . 'ttl';
|
|
$this->directory = $directory;
|
|
$this->serializer = $serializer;
|
|
$this->directorySeparator = $directorySeparator;
|
|
$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): void
|
|
{
|
|
$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], $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): void
|
|
{
|
|
foreach ($this->index as $key => $value) {
|
|
if (fnmatch($pattern, $key)) {
|
|
$this->delete($key);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param $file
|
|
* @return bool
|
|
*/
|
|
private function exists(string $file): bool
|
|
{
|
|
return file_exists($file);
|
|
}
|
|
|
|
/**
|
|
* @param $key
|
|
* @return string
|
|
*/
|
|
private function getFilename(string $key): string
|
|
{
|
|
[$directory, $file] = $this->getDirectoryAndFile($key);
|
|
return $directory . '/' . md5($file);
|
|
}
|
|
|
|
/**
|
|
* @param string $key
|
|
* @return array
|
|
*/
|
|
private function getDirectoryAndFile(string $key): array
|
|
{
|
|
$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): void
|
|
{
|
|
if (file_exists($directory) === false) {
|
|
if (!mkdir($directory) && !is_dir($directory)) {
|
|
throw new \RuntimeException(sprintf('Directory "%s" was not created', $directory));
|
|
}
|
|
if ($this->chmod !== null) {
|
|
chmod($directory, $this->chmod);
|
|
}
|
|
}
|
|
}
|
|
|
|
private function saveIndex(): void
|
|
{
|
|
file_put_contents($this->indexLocation, json_encode($this->index));
|
|
file_put_contents($this->ttlLocation, json_encode($this->timeToLive));
|
|
}
|
|
} |