Add voku/simplecache as alternative to Cache/Lite and use it cache

Cache/Lite is abandoned
This commit is contained in:
onli 2019-02-12 18:52:08 +01:00
parent fc02b0306b
commit d52f0004a4
27 changed files with 3088 additions and 32 deletions

View File

@ -0,0 +1,48 @@
# Changelog 3.2.2 (2018-12-21)
- fix APC(u) detection for CLI usage
# Changelog 3.2.1 (2018-12-20)
- use phpcs fixer
# Changelog 3.2.0 (2018-12-03)
- "AdapterOpCache" -> use "opcache_compile_file()"
- add "AdapterFileSimple" + tests
- "Cache" -> add the possibility to disable the cache behavior via constructor() -> "disableCacheGetParameter, useCheckForAdminSession, useCheckForServerIpIsClientIp, useCheckForDev"
# Changelog 3.1.1 (2018-01-07)
- "AdapterApcu" -> fixed php-warning from "apcu_clear_cache()"
# Changelog 3.1.0 (2018-01-07)
- "AdapterOpCache" -> added
- "AdapterFile" -> remove duplicate file-get code
# Changelog 3.0.2 (2017-12-14)
- "AdapterFile" -> fix php warning
# Chabgelog 3.0.1 (2017-12-01)
- fix return from the "CacheChain"-class
- update phpunit-config
# Changelog 3.0.0 (2017-11-25)
- drop support for PHP < 7.0
- use "strict_types"
# Changelog 2.4.0 (2017-10-06)
- add support for PSR-16

View File

@ -0,0 +1,19 @@
Copyright (c) 2015 Lars Moelleken
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -0,0 +1,105 @@
[![Build Status](https://travis-ci.org/voku/simple-cache.svg?branch=master)](https://travis-ci.org/voku/simple-cache)
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fvoku%2Fsimple-cache.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fvoku%2Fsimple-cache?ref=badge_shield)
[![Coverage Status](https://coveralls.io/repos/github/voku/simple-cache/badge.svg?branch=master)](https://coveralls.io/github/voku/simple-cache?branch=master)
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/voku/simple-cache/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/voku/simple-cache/?branch=master)
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/5846d2a46599486486b3956c0ce11a18)](https://www.codacy.com/app/voku/simple-cache)
[![Latest Stable Version](https://poser.pugx.org/voku/simple-cache/v/stable)](https://packagist.org/packages/voku/simple-cache)
[![Total Downloads](https://poser.pugx.org/voku/simple-cache/downloads)](https://packagist.org/packages/voku/simple-cache)
[![License](https://poser.pugx.org/voku/simple-cache/license)](https://packagist.org/packages/voku/simple-cache)
:zap: Simple Cache Class
===================
This is a simple Cache Abstraction Layer for PHP >= 7.0 that provides a simple interaction
with your cache-server. You can define the Adapter / Serializer in the "constructor" or the class will auto-detect you server-cache in this order:
1. Memcached / Memcache
2. Redis
3. Xcache
4. APC / APCu
5. OpCache (via PHP-files)
6. Static-PHP-Cache
## Get "Simple Cache"
You can download it from here, or require it using [composer](https://packagist.org/packages/voku/simple-cache).
```json
{
"require": {
"voku/simple-cache": "3.*"
}
}
```
## Install via "composer require"
```shell
composer require voku/simple-cache
composer require predis/predis # if you will use redis as cache, then add predis
```
## Quick Start
```php
use voku\cache\Cache;
require_once 'composer/autoload.php';
$cache = new Cache();
$ttl = 3600; // 60s * 60 = 1h
$cache->setItem('foo', 'bar', $ttl);
$bar = $cache->getItem('foo');
```
## Usage
```php
use voku\cache\Cache;
$cache = new Cache();
if ($cache->getCacheIsReady() === true && $cache->existsItem('foo')) {
return $cache->getItem('foo');
} else {
$bar = someSpecialFunctionsWithAReturnValue();
$cache->setItem('foo', $bar);
return $bar;
}
```
If you have an heavy task e.g. a really-big-loop, then you can also use static-cache.
But keep in mind, that this will be stored into PHP (it needs more memory).
```php
use voku\cache\Cache;
$cache = new Cache();
if ($cache->getCacheIsReady() === true && $cache->existsItem('foo')) {
for ($i = 0; $i <= 100000; $i++) {
echo $this->cache->getItem('foo', 3); // use also static-php-cache, when we hit the cache 3-times
}
return $cache->getItem('foo');
} else {
$bar = someSpecialFunctionsWithAReturnValue();
$cache->setItem('foo', $bar);
return $bar;
}
```
PS: By default, the static cache is also used by >= 10 cache hits. But you can configure
this behavior via $cache->setStaticCacheHitCounter(INT).
## No-Cache for the admin or a specific ip-address
If you use the parameter "$checkForUser" (=== true) in the constructor, then the cache isn't used for the admin-session.
-> You can also overwrite the check for the user, if you add a global function named "checkForDev()".
## License
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fvoku%2Fsimple-cache.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fvoku%2Fsimple-cache?ref=badge_large)

View File

@ -0,0 +1,35 @@
{
"name": "voku/simple-cache",
"description": "Simple Cache library",
"keywords": [
"php",
"cache",
"simple cache",
"caching"
],
"type": "library",
"homepage": "https://github.com/voku/simple-cache",
"license": "MIT",
"authors": [
{
"name": "Lars Moelleken",
"homepage": "http://www.moelleken.org/",
"role": "Developer"
}
],
"provide": {
"psr/simple-cache-implementation": "1.0"
},
"require": {
"php": ">=7.0.0",
"psr/simple-cache": "~1.0"
},
"require-dev": {
"phpunit/phpunit": "~6.0"
},
"autoload": {
"psr-4": {
"voku\\cache\\": "src/voku/cache/"
}
}
}

View File

@ -0,0 +1,158 @@
<?php
declare(strict_types=1);
namespace voku\cache;
/**
* AdapterApc: a APC-Cache adapter
*
* @see http://php.net/manual/de/book.apc.php
*/
class AdapterApc implements iAdapter
{
/**
* @var bool
*/
public $installed = false;
/**
* @var bool
*/
public $debug = false;
/**
* __construct()
*/
public function __construct()
{
if (
\PHP_SAPI !== 'cli'
&&
\function_exists('apc_store') === true
&&
\ini_get('apc.enabled')
) {
$this->installed = true;
}
if (
\PHP_SAPI === 'cli'
&&
\function_exists('apc_store') === true
&&
\ini_get('apc.enable_cli')
) {
$this->installed = true;
}
}
/**
* Check if apc-cache exists.
*
* WARNING: use $this->exists($key) instead
*
* @param string $key
*
* @return bool
*
* @internal
*/
public function apc_cache_exists($key): bool
{
return (bool) \apc_fetch($key);
}
/**
* Clears the APC cache by type.
*
* @param string $type - If $type is "user", the user cache will be cleared; otherwise,
* the system cache (cached files) will be cleared
*
* @return bool
*
* @internal
*/
public function cacheClear(string $type): bool
{
return (bool) \apc_clear_cache($type);
}
/**
* Retrieves cached information from APC's data store
*
* @param string $type - If $type is "user", information about the user cache will be returned
* @param bool $limited - If $limited is TRUE, the return value will exclude the individual list of cache
* entries. This is useful when trying to optimize calls for statistics gathering
*
* @return array|bool <p>Array of cached data (and meta-data) or FALSE on failure.</p>
*/
public function cacheInfo(string $type = '', bool $limited = false): array
{
return \apc_cache_info($type, $limited);
}
/**
* {@inheritdoc}
*/
public function exists(string $key): bool
{
if (\function_exists('apc_exists')) {
return (bool) \apc_exists($key);
}
return $this->apc_cache_exists($key);
}
/**
* {@inheritdoc}
*/
public function get(string $key)
{
if ($this->exists($key)) {
return \apc_fetch($key);
}
return null;
}
/**
* {@inheritdoc}
*/
public function installed(): bool
{
return $this->installed;
}
/**
* {@inheritdoc}
*/
public function remove(string $key): bool
{
return (bool) \apc_delete($key);
}
/**
* {@inheritdoc}
*/
public function removeAll(): bool
{
return (bool) ($this->cacheClear('system') && $this->cacheClear('user'));
}
/**
* {@inheritdoc}
*/
public function set(string $key, $value): bool
{
return (bool) \apc_store($key, $value);
}
/**
* {@inheritdoc}
*/
public function setExpired(string $key, $data, int $ttl = 0): bool
{
return (bool) \apc_store($key, $data, $ttl);
}
}

View File

@ -0,0 +1,153 @@
<?php
declare(strict_types=1);
namespace voku\cache;
/**
* AdapterApcu: a APCu-Cache adapter
*
* @see http://php.net/manual/de/book.apcu.php
*/
class AdapterApcu implements iAdapter
{
/**
* @var bool
*/
public $installed = false;
/**
* @var bool
*/
public $debug = false;
/**
* __construct()
*/
public function __construct()
{
if (
\PHP_SAPI !== 'cli'
&&
\function_exists('apcu_store') === true
&&
\ini_get('apc.enabled')
) {
$this->installed = true;
}
if (
\PHP_SAPI === 'cli'
&&
\function_exists('apcu_store') === true
&&
\ini_get('apc.enable_cli')
) {
$this->installed = true;
}
}
/**
* Check if apcu-cache exists.
*
* WARNING: we only keep this method for compatibly-reasons
* -> use ->exists($key)
*
* @param string $key
*
* @return bool
*
* @deprecated
*/
public function apcu_cache_exists($key): bool
{
return $this->exists($key);
}
/**
* Clears the APCu cache by type.
*
* @param string $type <p>WARNING: is not used in APCu only valid for APC</p>
*
* @return bool
*
* @internal
*/
public function cacheClear(string $type): bool
{
return (bool) \apcu_clear_cache();
}
/**
* Retrieves cached information from APCu's data store
*
* @param bool $limited - If $limited is TRUE, the return value will exclude the individual list of cache
* entries. This is useful when trying to optimize calls for statistics gathering
*
* @return array|bool <p>Array of cached data (and meta-data) or FALSE on failure.</p>
*/
public function cacheInfo(bool $limited = false): array
{
return \apcu_cache_info($limited);
}
/**
* {@inheritdoc}
*/
public function exists(string $key): bool
{
return (bool) \apcu_exists($key);
}
/**
* {@inheritdoc}
*/
public function get(string $key)
{
if ($this->exists($key)) {
return \apcu_fetch($key);
}
return null;
}
/**
* {@inheritdoc}
*/
public function installed(): bool
{
return $this->installed;
}
/**
* {@inheritdoc}
*/
public function remove(string $key): bool
{
return (bool) \apcu_delete($key);
}
/**
* {@inheritdoc}
*/
public function removeAll(): bool
{
return (bool) ($this->cacheClear('system') && $this->cacheClear('user'));
}
/**
* {@inheritdoc}
*/
public function set(string $key, $value): bool
{
return (bool) \apcu_store($key, $value);
}
/**
* {@inheritdoc}
*/
public function setExpired(string $key, $data, int $ttl = 0): bool
{
return (bool) \apcu_store($key, $data, $ttl);
}
}

View File

@ -0,0 +1,128 @@
<?php
declare(strict_types=1);
namespace voku\cache;
/**
* AdapterArray: simple array-cache adapter
*/
class AdapterArray implements iAdapter
{
/**
* @var array
*/
private static $values = [];
/**
* @var array
*/
private static $expired = [];
/**
* {@inheritdoc}
*/
public function exists(string $key): bool
{
$this->removeExpired($key);
return \array_key_exists($key, self::$values);
}
/**
* {@inheritdoc}
*/
public function get(string $key)
{
return $this->exists($key) ? self::$values[$key] : null;
}
/**
* {@inheritdoc}
*/
public function installed(): bool
{
return true;
}
/**
* {@inheritdoc}
*/
public function remove(string $key): bool
{
$this->removeExpired($key);
if (\array_key_exists($key, self::$values) === true) {
unset(self::$values[$key]);
return true;
}
return false;
}
/**
* {@inheritdoc}
*/
public function removeAll(): bool
{
self::$values = [];
self::$expired = [];
return true;
}
/**
* {@inheritdoc}
*/
public function set(string $key, $value): bool
{
self::$values[$key] = $value;
return true;
}
/**
* {@inheritdoc}
*/
public function setExpired(string $key, $value, int $ttl = 0): bool
{
self::$values[$key] = $value;
if ($ttl !== null) {
self::$expired[$key] = [\time(), $ttl];
}
return true;
}
/**
* Remove expired cache.
*
* @param string $key
*
* @return bool
*/
private function removeExpired($key): bool
{
if (
\array_key_exists($key, self::$expired) === false
||
\array_key_exists($key, self::$values) === false
) {
return false;
}
list($time, $ttl) = self::$expired[$key];
if (\time() > ($time + $ttl)) {
unset(self::$values[$key]);
}
if (!isset(self::$values[$key])) {
unset(self::$expired[$key]);
}
return true;
}
}

View File

@ -0,0 +1,93 @@
<?php
declare(strict_types=1);
namespace voku\cache;
/**
* AdapterFile: File-adapter
*/
class AdapterFile extends AdapterFileAbstract
{
/**
* {@inheritdoc}
*/
public function get(string $key)
{
$path = $this->getFileName($key);
if (
\file_exists($path) === false
||
\filesize($path) === 0
) {
return null;
}
// init
$string = '';
/** @noinspection PhpUsageOfSilenceOperatorInspection */
$fp = @\fopen($path, 'rb');
if ($fp && \flock($fp, \LOCK_SH | \LOCK_NB)) {
while (!\feof($fp)) {
$line = \fgets($fp);
$string .= $line;
}
\flock($fp, \LOCK_UN);
}
if ($fp) {
\fclose($fp);
}
if (!$string) {
return null;
}
$data = $this->serializer->unserialize($string);
if (!$data || !$this->validateDataFromCache($data)) {
return null;
}
if ($this->ttlHasExpired($data['ttl']) === true) {
$this->remove($key);
return null;
}
return $data['value'];
}
/**
* {@inheritdoc}
*/
public function setExpired(string $key, $value, int $ttl = 0): bool
{
$item = $this->serializer->serialize(
[
'value' => $value,
'ttl' => $ttl ? $ttl + \time() : 0,
]
);
// init
$octetWritten = false;
$cacheFile = $this->getFileName($key);
// Open the file for writing only. If the file does not exist, it is created.
// If it exists, it is neither truncated, nor the call to this function fails.
/** @noinspection PhpUsageOfSilenceOperatorInspection */
$fp = @\fopen($cacheFile, 'cb');
if ($fp && \flock($fp, \LOCK_EX | \LOCK_NB)) {
\ftruncate($fp, 0);
$octetWritten = \fwrite($fp, $item);
\fflush($fp);
\flock($fp, \LOCK_UN);
}
\fclose($fp);
return $octetWritten !== false;
}
}

View File

@ -0,0 +1,242 @@
<?php
declare(strict_types=1);
namespace voku\cache;
/**
* AdapterFileSimple: File-adapter (simple)
*/
abstract class AdapterFileAbstract implements iAdapter
{
const CACHE_FILE_PREFIX = '__';
const CACHE_FILE_SUBFIX = '.php.cache';
/**
* @var bool
*/
public $installed = false;
/**
* @var string
*/
protected $cacheDir;
/**
* @var iSerializer
*/
protected $serializer;
/**
* @var string
*/
protected $fileMode = '0755';
/**
* @param string|null $cacheDir
*/
public function __construct($cacheDir = null)
{
$this->serializer = new SerializerIgbinary();
if (!$cacheDir) {
$cacheDir = \realpath(\sys_get_temp_dir()) . '/simple_php_cache';
}
$this->cacheDir = (string) $cacheDir;
if ($this->createCacheDirectory($cacheDir) === true) {
$this->installed = true;
}
}
/**
* Recursively creates & chmod directories.
*
* @param string $path
*
* @return bool
*/
protected function createCacheDirectory($path): bool
{
if (
!$path
||
$path === '/'
||
$path === '.'
||
$path === '\\'
) {
return false;
}
// if the directory already exists, just return true
if (\is_dir($path) && \is_writable($path)) {
return true;
}
// if more than one level, try parent first
if (\dirname($path) !== '.') {
$return = $this->createCacheDirectory(\dirname($path));
// if creating parent fails, we can abort immediately
if (!$return) {
return false;
}
}
$mode_dec = \intval($this->fileMode, 8);
$old_umask = \umask(0);
/** @noinspection PhpUsageOfSilenceOperatorInspection */
if (!@\mkdir($path, $mode_dec) && !\is_dir($path)) {
$return = false;
} else {
$return = true;
}
if (\is_dir($path) && !\is_writable($path)) {
$return = \chmod($path, $mode_dec);
}
\umask($old_umask);
return $return;
}
/**
* @param $cacheFile
*
* @return bool
*/
protected function deleteFile($cacheFile): bool
{
if (\is_file($cacheFile)) {
return \unlink($cacheFile);
}
return false;
}
/**
* {@inheritdoc}
*/
public function exists(string $key): bool
{
$value = $this->get($key);
return $value !== null;
}
/**
* {@inheritdoc}
*/
abstract public function get(string $key);
/**
* {@inheritdoc}
*/
public function installed(): bool
{
return $this->installed;
}
/**
* {@inheritdoc}
*/
public function remove(string $key): bool
{
$cacheFile = $this->getFileName($key);
return $this->deleteFile($cacheFile);
}
/**
* {@inheritdoc}
*/
public function removeAll(): bool
{
if (!$this->cacheDir) {
return false;
}
$return = [];
foreach (new \DirectoryIterator($this->cacheDir) as $fileInfo) {
if (!$fileInfo->isDot()) {
$return[] = \unlink($fileInfo->getPathname());
}
}
return \in_array(false, $return, true) === false;
}
/**
* {@inheritdoc}
*/
public function set(string $key, $value): bool
{
return $this->setExpired($key, $value);
}
/**
* {@inheritdoc}
*/
abstract public function setExpired(string $key, $value, int $ttl = 0): bool;
/**
* @param string $key
*
* @return string
*/
protected function getFileName(string $key): string
{
return $this->cacheDir . \DIRECTORY_SEPARATOR . self::CACHE_FILE_PREFIX . $key . self::CACHE_FILE_SUBFIX;
}
/**
* Set the file-mode for new cache-files.
*
* e.g. '0777', or '0755' ...
*
* @param $fileMode
*/
public function setFileMode($fileMode)
{
$this->fileMode = $fileMode;
}
/**
* @param $ttl
*
* @return bool
*/
protected function ttlHasExpired(int $ttl): bool
{
if ($ttl === 0) {
return false;
}
return \time() > $ttl;
}
/**
* @param mixed $data
*
* @return bool
*/
protected function validateDataFromCache($data): bool
{
if (!\is_array($data)) {
return false;
}
foreach (['value', 'ttl'] as $missing) {
if (!\array_key_exists($missing, $data)) {
return false;
}
}
return true;
}
}

View File

@ -0,0 +1,89 @@
<?php
declare(strict_types=1);
namespace voku\cache;
/**
* AdapterFileSimple: File-adapter (simple)
*/
class AdapterFileSimple extends AdapterFileAbstract
{
const CACHE_FILE_PREFIX = '__simple_';
protected function getContext()
{
static $CONTEXT_CACHE = null;
if ($CONTEXT_CACHE === null) {
$CONTEXT_CACHE = \stream_context_create(
[
'http' => [
'timeout' => 2,
],
]
);
}
return $CONTEXT_CACHE;
}
/**
* {@inheritdoc}
*/
public function get(string $key)
{
$path = $this->getFileName($key);
if (
\file_exists($path) === false
||
\filesize($path) === 0
) {
return null;
}
// init
$string = \file_get_contents(
$path,
false,
$this->getContext()
);
if (!$string) {
return null;
}
$data = $this->serializer->unserialize($string);
if (!$data || !$this->validateDataFromCache($data)) {
return null;
}
if ($this->ttlHasExpired($data['ttl']) === true) {
$this->remove($key);
return null;
}
return $data['value'];
}
/**
* {@inheritdoc}
*/
public function setExpired(string $key, $value, int $ttl = 0): bool
{
return (bool) \file_put_contents(
$this->getFileName($key),
$this->serializer->serialize(
[
'value' => $value,
'ttl' => $ttl ? $ttl + \time() : 0,
]
),
0,
$this->getContext()
);
}
}

View File

@ -0,0 +1,144 @@
<?php
declare(strict_types=1);
namespace voku\cache;
use voku\cache\Exception\InvalidArgumentException;
/**
* AdapterMemcache: Memcache-adapter
*/
class AdapterMemcache implements iAdapter
{
/**
* @var bool
*/
public $installed = false;
/**
* @var \Memcache
*/
private $memcache;
/**
* @var bool
*/
private $compressed = false;
/**
* __construct
*
* @param \Memcache|null $memcache
*/
public function __construct($memcache = null)
{
if ($memcache instanceof \Memcache) {
$this->setMemcache($memcache);
}
}
/**
* @param \Memcache $memcache
*/
public function setMemcache(\Memcache $memcache)
{
$this->memcache = $memcache;
$this->installed = true;
}
/**
* {@inheritdoc}
*/
public function exists(string $key): bool
{
return $this->get($key) !== false;
}
/**
* {@inheritdoc}
*/
public function get(string $key)
{
return $this->memcache->get($key);
}
/**
* {@inheritdoc}
*/
public function installed(): bool
{
return $this->installed;
}
/**
* {@inheritdoc}
*/
public function remove(string $key): bool
{
return $this->memcache->delete($key);
}
/**
* {@inheritdoc}
*/
public function removeAll(): bool
{
return $this->memcache->flush();
}
/**
* {@inheritdoc}
*/
public function set(string $key, $value): bool
{
// Make sure we are under the proper limit
if (\strlen($key) > 250) {
throw new InvalidArgumentException('The passed cache key is over 250 bytes:' . \print_r($key, true));
}
return $this->memcache->set($key, $value, $this->getCompressedFlag());
}
/**
* {@inheritdoc}
*/
public function setExpired(string $key, $value, int $ttl = 0): bool
{
if ($ttl > 2592000) {
$ttl = 2592000;
}
return $this->memcache->set($key, $value, $this->getCompressedFlag(), $ttl);
}
/**
* Get the compressed-flag from MemCache.
*
* @return int 2 || 0
*/
private function getCompressedFlag(): int
{
return $this->isCompressed() ? \MEMCACHE_COMPRESSED : 0;
}
/**
* Check if compression from MemCache is active.
*
* @return bool
*/
public function isCompressed(): bool
{
return $this->compressed;
}
/**
* Activate the compression from MemCache.
*
* @param mixed $value will be converted to bool
*/
public function setCompressed($value)
{
$this->compressed = (bool) $value;
}
}

View File

@ -0,0 +1,128 @@
<?php
declare(strict_types=1);
namespace voku\cache;
use voku\cache\Exception\InvalidArgumentException;
/**
* AdapterMemcached: Memcached-adapter
*/
class AdapterMemcached implements iAdapter
{
/**
* @var bool
*/
public $installed = false;
/**
* @var \Memcached
*/
private $memcached;
/**
* __construct
*
* @param \Memcached|null $memcached
*/
public function __construct($memcached = null)
{
if ($memcached instanceof \Memcached) {
$this->setMemcached($memcached);
}
}
/**
* @param \Memcached $memcached
*/
public function setMemcached(\Memcached $memcached)
{
$this->memcached = $memcached;
$this->installed = true;
$this->setSettings();
}
/**
* {@inheritdoc}
*/
public function exists(string $key): bool
{
return $this->get($key) !== false;
}
/**
* {@inheritdoc}
*/
public function get(string $key)
{
return $this->memcached->get($key);
}
/**
* {@inheritdoc}
*/
public function installed(): bool
{
return $this->installed;
}
/**
* {@inheritdoc}
*/
public function remove(string $key): bool
{
return $this->memcached->delete($key);
}
/**
* {@inheritdoc}
*/
public function removeAll(): bool
{
return $this->memcached->flush();
}
/**
* {@inheritdoc}
*/
public function set(string $key, $value): bool
{
// Make sure we are under the proper limit
if (\strlen($this->memcached->getOption(\Memcached::OPT_PREFIX_KEY) . $key) > 250) {
throw new InvalidArgumentException('The passed cache key is over 250 bytes:' . \print_r($key, true));
}
return $this->memcached->set($key, $value);
}
/**
* {@inheritdoc}
*/
public function setExpired(string $key, $value, int $ttl = 0): bool
{
if ($ttl > 2592000) {
$ttl = 2592000;
}
return $this->memcached->set($key, $value, $ttl);
}
/**
* Set the MemCached settings.
*/
private function setSettings()
{
// Use faster compression if available
if (\Memcached::HAVE_IGBINARY) {
$this->memcached->setOption(\Memcached::OPT_SERIALIZER, \Memcached::SERIALIZER_IGBINARY);
}
$this->memcached->setOption(\Memcached::OPT_DISTRIBUTION, \Memcached::DISTRIBUTION_CONSISTENT);
$this->memcached->setOption(\Memcached::OPT_LIBKETAMA_COMPATIBLE, true);
$this->memcached->setOption(\Memcached::OPT_NO_BLOCK, true);
$this->memcached->setOption(\Memcached::OPT_TCP_NODELAY, true);
$this->memcached->setOption(\Memcached::OPT_COMPRESSION, false);
$this->memcached->setOption(\Memcached::OPT_CONNECT_TIMEOUT, 2);
}
}

View File

@ -0,0 +1,115 @@
<?php
declare(strict_types=1);
namespace voku\cache;
/**
* AdapterOpCache: PHP-OPcache
*
* OPcache improves PHP performance by storing precompiled script bytecode
* in shared memory, thereby removing the need for PHP to load and
* parse scripts on each request.
*/
class AdapterOpCache extends AdapterFileSimple
{
/**
* @var bool
*/
private static $hasCompileFileFunction;
/**
* {@inheritdoc}
*/
public function __construct($cacheDir = null)
{
parent::__construct($cacheDir);
$this->serializer = new SerializerNo();
if (self::$hasCompileFileFunction === null) {
/** @noinspection PhpComposerExtensionStubsInspection */
self::$hasCompileFileFunction = \function_exists('opcache_compile_file') && !empty(\opcache_get_status());
}
}
/**
* {@inheritdoc}
*/
public function get(string $key)
{
$path = $this->getFileName($key);
if (
\file_exists($path) === false
||
\filesize($path) === 0
) {
return null;
}
/** @noinspection PhpIncludeInspection */
$data = include $path;
if (!$data || !$this->validateDataFromCache($data)) {
return null;
}
if ($this->ttlHasExpired($data['ttl']) === true) {
$this->remove($key);
return null;
}
return $data['value'];
}
/**
* {@inheritdoc}
*/
protected function getFileName(string $key): string
{
return $this->cacheDir . \DIRECTORY_SEPARATOR . self::CACHE_FILE_PREFIX . $key . '.php';
}
/**
* {@inheritdoc}
*/
public function setExpired(string $key, $value, int $ttl = 0): bool
{
$item = [
'value' => $value,
'ttl' => $ttl ? $ttl + \time() : 0,
];
$content = \var_export($item, true);
$content = '<?php return ' . $content . ';';
$cacheFile = $this->getFileName($key);
$result = (bool) \file_put_contents(
$cacheFile,
$content,
0,
$this->getContext()
);
if (
$result === true
&&
self::$hasCompileFileFunction === true
) {
// opcache will only compile and cache files older than the script execution start.
// set a date before the script execution date, then opcache will compile and cache the generated file.
/** @noinspection SummerTimeUnsafeTimeManipulationInspection */
\touch($cacheFile, \time() - 86400);
/** @noinspection PhpComposerExtensionStubsInspection */
\opcache_invalidate($cacheFile);
/** @noinspection PhpComposerExtensionStubsInspection */
\opcache_compile_file($cacheFile);
}
return $result;
}
}

View File

@ -0,0 +1,98 @@
<?php
declare(strict_types=1);
namespace voku\cache;
use Predis\Client;
/**
* AdapterPredis: Memcached-adapter
*/
class AdapterPredis implements iAdapter
{
/**
* @var bool
*/
public $installed = false;
/**
* @var Client
*/
private $client;
/**
* @param Client|null $client
*/
public function __construct($client = null)
{
if ($client instanceof Client) {
$this->setPredisClient($client);
}
}
/**
* @param Client $client
*/
public function setPredisClient(Client $client)
{
$this->installed = true;
$this->client = $client;
}
/**
* {@inheritdoc}
*/
public function exists(string $key): bool
{
return $this->client->exists($key);
}
/**
* {@inheritdoc}
*/
public function get(string $key)
{
return $this->client->get($key);
}
/**
* {@inheritdoc}
*/
public function installed(): bool
{
return $this->installed;
}
/**
* {@inheritdoc}
*/
public function remove(string $key): bool
{
return $this->client->del($key);
}
/**
* {@inheritdoc}
*/
public function removeAll(): bool
{
return $this->client->flushall();
}
/**
* {@inheritdoc}
*/
public function set(string $key, $value): bool
{
return $this->client->set($key, $value);
}
/**
* {@inheritdoc}
*/
public function setExpired(string $key, $value, int $ttl = 0): bool
{
return $this->client->setex($key, $ttl, $value);
}
}

View File

@ -0,0 +1,88 @@
<?php
declare(strict_types=1);
namespace voku\cache;
/**
* AdapterXcache: Xcache-adapter
*/
class AdapterXcache implements iAdapter
{
public $installed = false;
/**
* __construct
*/
public function __construct()
{
if (\extension_loaded('xcache') === true) {
$this->installed = true;
}
}
/**
* {@inheritdoc}
*/
public function exists(string $key): bool
{
return \xcache_isset($key);
}
/**
* {@inheritdoc}
*/
public function get(string $key)
{
return \xcache_get($key);
}
/**
* {@inheritdoc}
*/
public function installed(): bool
{
return $this->installed;
}
/**
* {@inheritdoc}
*/
public function remove(string $key): bool
{
return \xcache_unset($key);
}
/**
* {@inheritdoc}
*/
public function removeAll(): bool
{
if (\defined('XC_TYPE_VAR')) {
$xCacheCount = xcache_count(XC_TYPE_VAR);
for ($i = 0; $i < $xCacheCount; $i++) {
\xcache_clear_cache(XC_TYPE_VAR, $i);
}
return true;
}
return false;
}
/**
* {@inheritdoc}
*/
public function set(string $key, $value): bool
{
return \xcache_set($key, $value);
}
/**
* {@inheritdoc}
*/
public function setExpired(string $key, $value, int $ttl = 0): bool
{
return \xcache_set($key, $value, $ttl);
}
}

View File

@ -0,0 +1,798 @@
<?php
declare(strict_types=1);
namespace voku\cache;
use voku\cache\Exception\InvalidArgumentException;
/**
* Cache: global-cache class
*
* can use different cache-adapter:
* - Redis
* - Memcache / Memcached
* - APC / APCu
* - Xcache
* - Array
* - File / OpCache
*/
class Cache implements iCache
{
/**
* @var iAdapter
*/
protected $adapter;
/**
* @var iSerializer
*/
protected $serializer;
/**
* @var string
*/
protected $prefix = '';
/**
* @var bool
*/
protected $isReady = false;
/**
* @var bool
*/
protected $isActive = true;
/**
* @var bool
*/
protected $useCheckForDev;
/**
* @var bool
*/
protected $useCheckForAdminSession;
/**
* @var bool
*/
protected $useCheckForServerIpIsClientIp;
/**
* @var string
*/
protected $disableCacheGetParameter;
/**
* @var bool
*/
protected $isAdminSession;
/**
* @var array
*/
protected static $STATIC_CACHE = [];
/**
* @var array
*/
protected static $STATIC_CACHE_EXPIRE = [];
/**
* @var array
*/
protected static $STATIC_CACHE_COUNTER = [];
/**
* @var int
*/
protected $staticCacheHitCounter = 10;
/**
* __construct
*
* @param iAdapter|null $adapter
* @param iSerializer|null $serializer
* @param bool $checkForUsage <p>admin-session || server-ip == client-ip || check for
* dev</p>
* @param bool $cacheEnabled <p>false will disable the cache (use it e.g. for global
* settings)</p>
* @param bool|string $isAdminSession <p>set a admin-id, if the user is a admin (so we can
* disable cache for this user)
* @param bool $useCheckForAdminSession <p>use $isAdminSession flag or not</p>
* @param bool $useCheckForDev <p>use checkForDev() or not</p>
* @param bool $useCheckForServerIpIsClientIp <p>use check for server-ip == client-ip or not</p>
* @param string $disableCacheGetParameter <p>set the _GET parameter for disabling the cache,
* disable this check via empty string</p>
*/
public function __construct(
iAdapter $adapter = null,
iSerializer $serializer = null,
bool $checkForUsage = true,
bool $cacheEnabled = true,
bool $isAdminSession = false,
bool $useCheckForDev = true,
bool $useCheckForAdminSession = true,
bool $useCheckForServerIpIsClientIp = true,
string $disableCacheGetParameter = 'testWithoutCache'
) {
$this->isAdminSession = $isAdminSession;
$this->useCheckForDev = $useCheckForDev;
$this->useCheckForAdminSession = $useCheckForAdminSession;
$this->useCheckForServerIpIsClientIp = $useCheckForServerIpIsClientIp;
$this->disableCacheGetParameter = $disableCacheGetParameter;
// First check if the cache is active at all.
$this->isActive = $cacheEnabled;
if (
$this->isActive === true
&&
$checkForUsage === true
) {
$this->setActive($this->isCacheActiveForTheCurrentUser());
}
// If the cache is active, then try to auto-connect to the best possible cache-system.
if ($this->isActive === true) {
$this->setPrefix($this->getTheDefaultPrefix());
if ($adapter === null) {
$adapter = $this->autoConnectToAvailableCacheSystem();
}
// INFO: Memcache(d) has his own "serializer", so don't use it twice
if (!\is_object($serializer) && $serializer === null) {
if (
$adapter instanceof AdapterMemcached
||
$adapter instanceof AdapterMemcache
) {
$serializer = new SerializerNo();
} else {
// set default serializer
$serializer = new SerializerIgbinary();
}
}
}
// Final checks ...
if (
$serializer !== null
&&
$adapter !== null
) {
$this->setCacheIsReady(true);
$this->adapter = $adapter;
$this->serializer = $serializer;
}
}
/**
* enable / disable the cache
*
* @param bool $isActive
*/
public function setActive(bool $isActive)
{
$this->isActive = $isActive;
}
/**
* check if the current use is a admin || dev || server == client
*
* @return bool
*/
public function isCacheActiveForTheCurrentUser(): bool
{
$active = true;
// test the cache, with this GET-parameter
if ($this->disableCacheGetParameter) {
$testCache = isset($_GET[$this->disableCacheGetParameter]) ? (int) $_GET[$this->disableCacheGetParameter] : 0;
} else {
$testCache = 0;
}
if ($testCache !== 1) {
if (
// admin session is active
(
$this->useCheckForAdminSession
&&
$this->isAdminSession
)
||
// server == client
(
$this->useCheckForServerIpIsClientIp === true
&&
isset($_SERVER['SERVER_ADDR'])
&&
$_SERVER['SERVER_ADDR'] === $this->getClientIp()
)
||
// user is a dev
(
$this->useCheckForDev === true
&&
$this->checkForDev() === true
)
) {
$active = false;
}
}
return $active;
}
/**
* returns the IP address of the client
*
* @param bool $trust_proxy_headers <p>
* Whether or not to trust the
* proxy headers HTTP_CLIENT_IP
* and HTTP_X_FORWARDED_FOR. ONLY
* use if your $_SERVER is behind a
* proxy that sets these values
* </p>
*
* @return string
*/
protected function getClientIp(bool $trust_proxy_headers = false): string
{
$remoteAddr = $_SERVER['REMOTE_ADDR'] ?? 'NO_REMOTE_ADDR';
if ($trust_proxy_headers) {
return $remoteAddr;
}
if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
$ip = $_SERVER['HTTP_CLIENT_IP'];
} elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
} else {
$ip = $remoteAddr;
}
return $ip;
}
/**
* Check for local developer.
*
* @return bool
*/
protected function checkForDev(): bool
{
$return = false;
if (\function_exists('checkForDev')) {
$return = checkForDev();
} else {
// for testing with dev-address
$noDev = isset($_GET['noDev']) ? (int) $_GET['noDev'] : 0;
$remoteAddr = $_SERVER['REMOTE_ADDR'] ?? 'NO_REMOTE_ADDR';
if (
$noDev !== 1
&&
(
$remoteAddr === '127.0.0.1'
||
$remoteAddr === '::1'
||
\PHP_SAPI === 'cli'
)
) {
$return = true;
}
}
return $return;
}
/**
* Set the default-prefix via "SERVER"-var + "SESSION"-language.
*/
protected function getTheDefaultPrefix(): string
{
return ($_SERVER['SERVER_NAME'] ?? '') . '_' .
($_SERVER['THEME'] ?? '') . '_' .
($_SERVER['STAGE'] ?? '') . '_' .
($_SESSION['language'] ?? '') . '_' .
($_SESSION['language_extra'] ?? '');
}
/**
* Auto-connect to the available cache-system on the server.
*
* @return iAdapter
*/
protected function autoConnectToAvailableCacheSystem(): iAdapter
{
static $adapterCache;
if (\is_object($adapterCache) && $adapterCache instanceof iAdapter) {
return $adapterCache;
}
$memcached = null;
$isMemcachedAvailable = false;
if (\extension_loaded('memcached')) {
/** @noinspection PhpComposerExtensionStubsInspection */
$memcached = new \Memcached();
/** @noinspection PhpUsageOfSilenceOperatorInspection */
$isMemcachedAvailable = @$memcached->addServer('127.0.0.1', 11211);
}
if ($isMemcachedAvailable === false) {
$memcached = null;
}
$adapterMemcached = new AdapterMemcached($memcached);
if ($adapterMemcached->installed() === true) {
// -------------------------------------------------------------
// "Memcached"
// -------------------------------------------------------------
$adapter = $adapterMemcached;
} else {
$memcache = null;
$isMemcacheAvailable = false;
/** @noinspection ClassConstantCanBeUsedInspection */
if (\class_exists('\Memcache')) {
/** @noinspection PhpComposerExtensionStubsInspection */
$memcache = new \Memcache();
/** @noinspection PhpUsageOfSilenceOperatorInspection */
$isMemcacheAvailable = @$memcache->connect('127.0.0.1', 11211);
}
if ($isMemcacheAvailable === false) {
$memcache = null;
}
$adapterMemcache = new AdapterMemcache($memcache);
if ($adapterMemcache->installed() === true) {
// -------------------------------------------------------------
// "Memcache"
// -------------------------------------------------------------
$adapter = $adapterMemcache;
} else {
$redis = null;
$isRedisAvailable = false;
if (
\extension_loaded('redis')
&&
\class_exists('\Predis\Client')
) {
/** @noinspection PhpUndefinedNamespaceInspection */
/** @noinspection PhpUndefinedClassInspection */
$redis = new \Predis\Client(
[
'scheme' => 'tcp',
'host' => '127.0.0.1',
'port' => 6379,
'timeout' => '2.0',
]
);
try {
/** @noinspection PhpUndefinedMethodInspection */
$redis->connect();
/** @noinspection PhpUndefinedMethodInspection */
$isRedisAvailable = $redis->getConnection()->isConnected();
} catch (\Exception $e) {
// nothing
}
}
if ($isRedisAvailable === false) {
$redis = null;
}
$adapterRedis = new AdapterPredis($redis);
if ($adapterRedis->installed() === true) {
// -------------------------------------------------------------
// Redis
// -------------------------------------------------------------
$adapter = $adapterRedis;
} else {
$adapterXcache = new AdapterXcache();
if ($adapterXcache->installed() === true) {
// -------------------------------------------------------------
// "Xcache"
// -------------------------------------------------------------
$adapter = $adapterXcache;
} else {
$adapterApcu = new AdapterApcu();
if ($adapterApcu->installed() === true) {
// -------------------------------------------------------------
// "APCu"
// -------------------------------------------------------------
$adapter = $adapterApcu;
} else {
$adapterApc = new AdapterApc();
if ($adapterApc->installed() === true) {
// -------------------------------------------------------------
// "APC"
// -------------------------------------------------------------
$adapter = $adapterApc;
} else {
$adapterObCache = new AdapterOpCache();
if ($adapterObCache->installed() === true) {
// -------------------------------------------------------------
// OpCache (via PHP-files)
// -------------------------------------------------------------
$adapter = $adapterObCache;
} else {
// -------------------------------------------------------------
// Static-PHP-Cache
// -------------------------------------------------------------
$adapter = new AdapterArray();
}
}
}
}
}
}
}
// save to static cache
$adapterCache = $adapter;
return $adapter;
}
/**
* Set "isReady" state.
*
* @param bool $isReady
*/
protected function setCacheIsReady(bool $isReady)
{
$this->isReady = $isReady;
}
/**
* Get the "isReady" state.
*
* @return bool
*/
public function getCacheIsReady(): bool
{
return $this->isReady;
}
/**
* Get cached-item by key.
*
* @param string $key
* @param int $forceStaticCacheHitCounter
*
* @return mixed
*/
public function getItem(string $key, int $forceStaticCacheHitCounter = 0)
{
if (!$this->adapter instanceof iAdapter) {
return null;
}
$storeKey = $this->calculateStoreKey($key);
// check if we already using static-cache
$useStaticCache = true;
if ($this->adapter instanceof AdapterArray) {
$useStaticCache = false;
}
if (!isset(self::$STATIC_CACHE_COUNTER[$storeKey])) {
self::$STATIC_CACHE_COUNTER[$storeKey] = 0;
}
// get from static-cache
if (
$useStaticCache === true
&&
$this->checkForStaticCache($storeKey) === true
) {
return self::$STATIC_CACHE[$storeKey];
}
$serialized = $this->adapter->get($storeKey);
$value = $serialized ? $this->serializer->unserialize($serialized) : null;
self::$STATIC_CACHE_COUNTER[$storeKey]++;
// save into static-cache if needed
if (
$useStaticCache === true
&&
(
(
$forceStaticCacheHitCounter !== 0
&&
self::$STATIC_CACHE_COUNTER[$storeKey] >= $forceStaticCacheHitCounter
)
||
(
$this->staticCacheHitCounter !== 0
&&
self::$STATIC_CACHE_COUNTER[$storeKey] >= $this->staticCacheHitCounter
)
)
) {
self::$STATIC_CACHE[$storeKey] = $value;
}
return $value;
}
/**
* Calculate store-key (prefix + $rawKey).
*
* @param string $rawKey
*
* @return string
*/
protected function calculateStoreKey(string $rawKey): string
{
$str = $this->getPrefix() . $rawKey;
if ($this->adapter instanceof AdapterFileAbstract) {
$str = $this->cleanStoreKey($str);
}
return $str;
}
/**
* Clean store-key (required e.g. for the "File"-Adapter).
*
* @param string $str
*
* @return string
*/
protected function cleanStoreKey(string $str): string
{
return \md5($str);
}
/**
* Get the prefix.
*
* @return string
*/
public function getPrefix(): string
{
return $this->prefix;
}
/**
* !!! Set the prefix. !!!
*
* WARNING: Do not use if you don't know what you do. Because this will overwrite the default prefix.
*
* @param string $prefix
*/
public function setPrefix(string $prefix)
{
$this->prefix = $prefix;
}
/**
* Get the current value, when the static cache is used.
*
* @return int
*/
public function getStaticCacheHitCounter(): int
{
return $this->staticCacheHitCounter;
}
/**
* Set the static-hit-counter: Who often do we hit the cache, before we use static cache?
*
* @param int $staticCacheHitCounter
*/
public function setStaticCacheHitCounter(int $staticCacheHitCounter)
{
$this->staticCacheHitCounter = $staticCacheHitCounter;
}
/**
* Set cache-item by key => value + date.
*
* @param string $key
* @param mixed $value
* @param \DateTime $date <p>If the date is in the past, we will remove the existing cache-item.</p>
*
* @throws InvalidArgumentException <p>If the $date is in the past.</p>
*
* @return bool
*/
public function setItemToDate(string $key, $value, \DateTime $date): bool
{
$ttl = $date->getTimestamp() - \time();
if ($ttl <= 0) {
throw new InvalidArgumentException('Date in the past.');
}
return $this->setItem($key, $value, $ttl);
}
/**
* Set cache-item by key => value + ttl.
*
* @param string $key
* @param mixed $value
* @param \DateInterval|int|null $ttl
*
* @throws InvalidArgumentException
*
* @return bool
*/
public function setItem(string $key, $value, $ttl = 0): bool
{
if (
!$this->adapter instanceof iAdapter
||
!$this->serializer instanceof iSerializer
) {
return false;
}
$storeKey = $this->calculateStoreKey($key);
$serialized = $this->serializer->serialize($value);
// update static-cache, if it's exists
if (\array_key_exists($storeKey, self::$STATIC_CACHE) === true) {
self::$STATIC_CACHE[$storeKey] = $value;
}
if ($ttl) {
if ($ttl instanceof \DateInterval) {
// Converting to a TTL in seconds
$dateTimeNow = new \DateTime('now');
$ttl = $dateTimeNow->add($ttl)->getTimestamp() - \time();
}
// always cache the TTL time, maybe we need this later ...
self::$STATIC_CACHE_EXPIRE[$storeKey] = ($ttl ? (int) $ttl + \time() : 0);
return $this->adapter->setExpired($storeKey, $serialized, $ttl);
}
return $this->adapter->set($storeKey, $serialized);
}
/**
* Remove a cached-item.
*
* @param string $key
*
* @return bool
*/
public function removeItem(string $key): bool
{
if (!$this->adapter instanceof iAdapter) {
return false;
}
$storeKey = $this->calculateStoreKey($key);
// remove static-cache
if (
!empty(self::$STATIC_CACHE)
&&
\array_key_exists($storeKey, self::$STATIC_CACHE) === true
) {
unset(self::$STATIC_CACHE[$storeKey], self::$STATIC_CACHE_COUNTER[$storeKey], self::$STATIC_CACHE_EXPIRE[$storeKey]
);
}
return $this->adapter->remove($storeKey);
}
/**
* Remove all cached-items.
*
* @return bool
*/
public function removeAll(): bool
{
if (!$this->adapter instanceof iAdapter) {
return false;
}
// remove static-cache
if (!empty(self::$STATIC_CACHE)) {
self::$STATIC_CACHE = [];
self::$STATIC_CACHE_COUNTER = [];
self::$STATIC_CACHE_EXPIRE = [];
}
return $this->adapter->removeAll();
}
/**
* Check if cached-item exists.
*
* @param string $key
*
* @return bool
*/
public function existsItem(string $key): bool
{
if (!$this->adapter instanceof iAdapter) {
return false;
}
$storeKey = $this->calculateStoreKey($key);
// check static-cache
if ($this->checkForStaticCache($storeKey) === true) {
return true;
}
return $this->adapter->exists($storeKey);
}
/**
* @param string $storeKey
*
* @return bool
*/
protected function checkForStaticCache(string $storeKey): bool
{
return !empty(self::$STATIC_CACHE)
&&
\array_key_exists($storeKey, self::$STATIC_CACHE) === true
&&
\array_key_exists($storeKey, self::$STATIC_CACHE_EXPIRE) === true
&&
\time() <= self::$STATIC_CACHE_EXPIRE[$storeKey];
}
/**
* Get the current adapter class-name.
*
* @return string
*/
public function getUsedAdapterClassName(): string
{
if ($this->adapter) {
/** @noinspection GetClassUsageInspection */
return \get_class($this->adapter);
}
return '';
}
/**
* Get the current serializer class-name.
*
* @return string
*/
public function getUsedSerializerClassName(): string
{
if ($this->serializer) {
/** @noinspection GetClassUsageInspection */
return \get_class($this->serializer);
}
return '';
}
}

View File

@ -0,0 +1,152 @@
<?php
declare(strict_types=1);
namespace voku\cache;
/**
* CacheChain: global-cache-chain class
*/
class CacheChain implements iCache
{
/**
* @var array|iCache[]
*/
private $caches = [];
/**
* __construct
*
* @param array $caches
*/
public function __construct(array $caches = [])
{
\array_map(
[
$this,
'addCache',
],
$caches
);
}
/**
* get caches
*
* @return array
*/
public function getCaches(): array
{
return $this->caches;
}
/**
* add cache
*
* @param iCache $cache
* @param bool $prepend
*
* @throws \InvalidArgumentException
*/
public function addCache(iCache $cache, $prepend = true)
{
if ($this === $cache) {
throw new \InvalidArgumentException('loop-error, put into other cache');
}
if ($prepend) {
\array_unshift($this->caches, $cache);
} else {
$this->caches[] = $cache;
}
}
/**
* {@inheritdoc}
*/
public function getItem(string $key)
{
foreach ($this->caches as $cache) {
if ($cache->existsItem($key)) {
return $cache->getItem($key);
}
}
return null;
}
/**
* {@inheritdoc}
*/
public function setItem(string $key, $value, $ttl = null): bool
{
// init
$results = [];
foreach ($this->caches as $cache) {
$results[] = $cache->setItem($key, $value, $ttl);
}
return \in_array(false, $results, true) === false;
}
/**
* {@inheritdoc}
*/
public function setItemToDate(string $key, $value, \DateTime $date): bool
{
// init
$results = [];
/* @var $cache iCache */
foreach ($this->caches as $cache) {
$results[] = $cache->setItemToDate($key, $value, $date);
}
return \in_array(false, $results, true) === false;
}
/**
* {@inheritdoc}
*/
public function removeItem(string $key): bool
{
// init
$results = [];
foreach ($this->caches as $cache) {
$results[] = $cache->removeItem($key);
}
return \in_array(false, $results, true) === false;
}
/**
* {@inheritdoc}
*/
public function existsItem(string $key): bool
{
foreach ($this->caches as $cache) {
if ($cache->existsItem($key)) {
return true;
}
}
return false;
}
/**
* {@inheritdoc}
*/
public function removeAll(): bool
{
// init
$results = [];
foreach ($this->caches as $cache) {
$results[] = $cache->removeAll();
}
return \in_array(false, $results, true) === false;
}
}

View File

@ -0,0 +1,177 @@
<?php
declare(strict_types=1);
namespace voku\cache;
use Psr\SimpleCache\CacheInterface;
use voku\cache\Exception\InvalidArgumentException;
class CachePsr16 extends Cache implements CacheInterface
{
/**
* Wipes clean the entire cache's keys.
*
* @return bool true on success and false on failure
*/
public function clear(): bool
{
return $this->removeAll();
}
/**
* Delete an item from the cache by its unique key.
*
* @param string $key the unique cache key of the item to delete
*
* @throws InvalidArgumentException
*
* @return bool True if the item was successfully removed. False if there was an error.
*/
public function delete($key): bool
{
if (!\is_string($key)) {
throw new InvalidArgumentException('$key is not a string:' . \print_r($key, true));
}
return $this->removeItem($key);
}
/**
* Deletes multiple cache items in a single operation.
*
* @param \iterable $keys a list of string-based keys to be deleted
*
* @throws InvalidArgumentException
*
* @return bool True if the items were successfully removed. False if there was an error.
*/
public function deleteMultiple($keys): bool
{
if (!\is_array($keys) && !($keys instanceof \Traversable)) {
throw new InvalidArgumentException('$keys is not iterable:' . \print_r($keys, true));
}
$results = [];
foreach ((array) $keys as $key) {
$results = $this->delete($key);
}
return \in_array(false, $results, true) === false;
}
/**
* Fetches a value from the cache.
*
* @param string $key the unique key of this item in the cache
* @param mixed $default default value to return if the key does not exist
*
* @throws InvalidArgumentException
*
* @return mixed the value of the item from the cache, or $default in case of cache miss
*/
public function get($key, $default = null)
{
if ($this->has($key) === true) {
return $this->getItem($key);
}
return $default;
}
/**
* Obtains multiple cache items by their unique keys.
*
* @param \iterable $keys a list of keys that can obtained in a single operation
* @param mixed $default default value to return for keys that do not exist
*
* @throws InvalidArgumentException
*
* @return \iterable A list of key => value pairs. Cache keys that do not exist or are stale will have $default as
* value.
*/
public function getMultiple($keys, $default = null)
{
if (!\is_array($keys) && !($keys instanceof \Traversable)) {
throw new InvalidArgumentException('$keys is not iterable:' . \print_r($keys, true));
}
$result = [];
foreach ((array) $keys as $key) {
$result[$key] = $this->has($key) ? $this->get($key) : $default;
}
return $result;
}
/**
* Determines whether an item is present in the cache.
*
* NOTE: It is recommended that has() is only to be used for cache warming type purposes
* and not to be used within your live applications operations for get/set, as this method
* is subject to a race condition where your has() will return true and immediately after,
* another script can remove it making the state of your app out of date.
*
* @param string $key the cache item key
*
* @throws InvalidArgumentException
*
* @return bool
*/
public function has($key): bool
{
if (!\is_string($key)) {
throw new InvalidArgumentException('$key is not a string:' . \print_r($key, true));
}
return $this->existsItem($key);
}
/**
* Persists data in the cache, uniquely referenced by a key with an optional expiration TTL time.
*
* @param string $key the key of the item to store
* @param mixed $value the value of the item to store, must be serializable
* @param \DateInterval|int|null $ttl Optional. The TTL value of this item. If no value is sent and
* the driver supports TTL then the library may set a default value
* for it or let the driver take care of that.
*
* @throws InvalidArgumentException
*
* @return bool true on success and false on failure
*/
public function set($key, $value, $ttl = null): bool
{
if (!\is_string($key)) {
throw new InvalidArgumentException('$key is not a string:' . \print_r($key, true));
}
return $this->setItem($key, $value, $ttl);
}
/**
* Persists a set of key => value pairs in the cache, with an optional TTL.
*
* @param \iterable $values a list of key => value pairs for a multiple-set operation
* @param \DateInterval|int|null $ttl Optional. The TTL value of this item. If no value is sent and
* the driver supports TTL then the library may set a default value
* for it or let the driver take care of that.
*
* @throws InvalidArgumentException
*
* @return bool true on success and false on failure
*/
public function setMultiple($values, $ttl = null): bool
{
if (!\is_array($values) && !($values instanceof \Traversable)) {
throw new InvalidArgumentException('$values is not iterable:' . \print_r($values, true));
}
$results = [];
foreach ((array) $values as $key => $value) {
$results = $this->set($key, $value, $ttl);
}
return \in_array(false, $results, true) === false;
}
}

View File

@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace voku\cache\Exception;
class InvalidArgumentException extends \Exception implements \Psr\SimpleCache\InvalidArgumentException
{
}

View File

@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace voku\cache;
/**
* SerializerDefault: simple serialize / unserialize
*/
class SerializerDefault implements iSerializer
{
/**
* {@inheritdoc}
*/
public function serialize($value)
{
return \serialize($value);
}
/**
* {@inheritdoc}
*/
public function unserialize($value)
{
return \unserialize($value);
}
}

View File

@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
namespace voku\cache;
/**
* SerializerIgbinary: serialize / unserialize
*/
class SerializerIgbinary implements iSerializer
{
/**
* @var bool
*/
public static $_exists_igbinary;
/**
* SerializerIgbinary constructor.
*/
public function __construct()
{
self::$_exists_igbinary = (
\function_exists('igbinary_serialize')
&&
\function_exists('igbinary_unserialize')
);
}
/**
* {@inheritdoc}
*/
public function serialize($value)
{
if (self::$_exists_igbinary === true) {
/** @noinspection PhpUndefinedFunctionInspection */
return \igbinary_serialize($value);
}
// fallback
return \serialize($value);
}
/**
* {@inheritdoc}
*/
public function unserialize($value)
{
if (self::$_exists_igbinary === true) {
/** @noinspection PhpUndefinedFunctionInspection */
return \igbinary_unserialize($value);
}
// fallback
return \unserialize($value);
}
}

View File

@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace voku\cache;
/**
* SerializerNo: no serialize / unserialize !!!
*/
class SerializerNo implements iSerializer
{
/**
* {@inheritdoc}
*/
public function serialize($value)
{
return $value;
}
/**
* {@inheritdoc}
*/
public function unserialize($value)
{
return $value;
}
}

View File

@ -0,0 +1,73 @@
<?php
declare(strict_types=1);
namespace voku\cache;
/**
* iAdapter: cache-adapter interface
*/
interface iAdapter
{
/**
* Get cached-item by key.
*
* @param string $key
*
* @return mixed|null <p>will return NULL if the key not exists</p>
*/
public function get(string $key);
/**
* Set cache-item by key => value.
*
* @param string $key
* @param mixed $value
*
* @return bool
*/
public function set(string $key, $value): bool;
/**
* Set cache-item by key => value + ttl.
*
* @param string $key
* @param mixed $value
* @param int $ttl
*
* @return bool
*/
public function setExpired(string $key, $value, int $ttl): bool;
/**
* Remove cached-item by key.
*
* @param string $key
*
* @return bool
*/
public function remove(string $key): bool;
/**
* Remove all cached items.
*
* @return bool
*/
public function removeAll(): bool;
/**
* Check if cache-key exists.
*
* @param string $key
*
* @return bool
*/
public function exists(string $key): bool;
/**
* Check if cache is installed.
*
* @return bool
*/
public function installed(): bool;
}

View File

@ -0,0 +1,67 @@
<?php
declare(strict_types=1);
namespace voku\cache;
/**
* iCache: cache-global interface
*/
interface iCache
{
/**
* get item
*
* @param string $key
*
* @return mixed
*/
public function getItem(string $key);
/**
* set item
*
* @param string $key
* @param mixed $value
* @param int|null $ttl
*
* @return bool
*/
public function setItem(string $key, $value, $ttl = 0): bool;
/**
* set item a special expire-date
*
* @param string $key
* @param mixed $value
* @param \DateTime $date
*
* @return bool
*/
public function setItemToDate(string $key, $value, \DateTime $date): bool;
/**
* remove item
*
* @param string $key
*
* @return bool
*/
public function removeItem(string $key): bool;
/**
* remove all items
*
* @return bool
*/
public function removeAll(): bool;
/**
* check if item exists
*
* @param string $key
*
* @return bool
*/
public function existsItem(string $key): bool;
}

View File

@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace voku\cache;
/**
* iSerializer: cache-serializer interface
*/
interface iSerializer
{
/**
* serialize
*
* @param $value
*
* @return mixed
*/
public function serialize($value);
/**
* unserialize
*
* @param $value
*
* @return mixed
*/
public function unserialize($value);
}

View File

@ -8,7 +8,8 @@
"require": {
"php": ">=5.3.3",
"zendframework/zend-db": "2.*",
"katzgrau/klogger": "1.0.*"
"katzgrau/klogger": "1.0.*",
"voku/simple-cache": "^3.2"
},
"config": {
"vendor-dir": "bundled-libs"

View File

@ -213,9 +213,9 @@ function &serendipity_fetchEntries($range = null, $full = true, $limit = '', $fe
$cache = serendipity_setupCache();
$key = md5(serialize($initial_args) . $serendipity['short_archives'] . '||' . $serendipity['range'] . '||' . $serendipity['GET']['category'] . '||' . $serendipity['GET']['hide_category'] . '||' . $serendipity['GET']['viewAuthor'] . '||' . $serendipity['GET']['page'] . '||' . $serendipity['fetchLimit'] . '||' . $serendipity['max_fetch_limit'] . '||' . $serendipity['GET']['adminModule'] . '||' . serendipity_checkPermission('adminEntriesMaintainOthers') . '||' .$serendipity['showFutureEntries'] . '||' . $serendipity['archiveSortStable'] . '||' . $serendipity['plugindata']['smartyvars']['uriargs'] );
$entries = $cache->get($key, "fetchEntries");
if ($entries !== false) {
$serendipity['fullCountQuery'] = $cache->get($key . '_fullCountQuery', "fetchEntries");
$entries = $cache->getItem($key);
if ($entries && $entries !== false) {
$serendipity['fullCountQuery'] = $cache->getItem($key . '_fullCountQuery');
return unserialize($entries);
}
}
@ -463,9 +463,9 @@ function &serendipity_fetchEntries($range = null, $full = true, $limit = '', $fe
if ($serendipity['useInternalCache']) {
$key = md5(serialize($initial_args) . $serendipity['short_archives'] . '||' . $serendipity['range'] . '||' . $serendipity['GET']['category'] . '||' . $serendipity['GET']['hide_category'] . '||' . $serendipity['GET']['viewAuthor'] . '||' . $serendipity['GET']['page'] . '||' . $serendipity['fetchLimit'] . '||' . $serendipity['max_fetch_limit'] . '||' . $serendipity['GET']['adminModule'] . '||' . serendipity_checkPermission('adminEntriesMaintainOthers') . '||' .$serendipity['showFutureEntries'] . '||' . $serendipity['archiveSortStable'] . '||' . $serendipity['plugindata']['smartyvars']['uriargs']);
$cache->save(serialize($ret), $key, "fetchEntries");
$cache->save($serendipity['fullCountQuery'], $key . '_fullCountQuery', "fetchEntries");
$cache->setItem($key, serialize($ret));
$cache->setItem($key . '_fullCountQuery', $serendipity['fullCountQuery']);
}
return $ret;
@ -1292,35 +1292,32 @@ function serendipity_printEntries($entries, $extended = 0, $preview = false, $sm
} // end function serendipity_printEntries
function serendipity_cleanCache() {
include_once 'Cache/Lite.php';
if (!class_exists('Cache_Lite')) {
return false;
}
$options = array(
'cacheDir' => $serendipity['serendipityPath'] . 'templates_c/',
'lifeTime' => 3600,
'hashedDirectoryLevel' => 2
);
$cache = new Cache_Lite($options);
return $cache->clean("fetchEntries");
$cache = serendipity_setupCache();
return $cache->removeAll();
}
//function serendipity_setupCache() {
//include_once 'Cache/Lite.php';
//if (!class_exists('Cache_Lite')) {
//return false;
//}
//$options = array(
//'cacheDir' => $serendipity['serendipityPath'] . 'templates_c/',
//'lifeTime' => 3600,
//'hashedDirectoryLevel' => 2
//);
//return new Cache_Lite($options);
//}
use voku\cache\Cache;
function serendipity_setupCache() {
include_once 'Cache/Lite.php';
if (!class_exists('Cache_Lite')) {
return false;
}
$options = array(
'cacheDir' => $serendipity['serendipityPath'] . 'templates_c/',
'lifeTime' => 3600,
'hashedDirectoryLevel' => 2
);
return new Cache_Lite($options);
$cache = new Cache();
$ttl = 3600; // 60s * 60 = 1h
return $cache;
}