<?php<liu21st@gmail.com>declare (strict_types = 1);
namespace think\cache;
use Closure;
use DateInterval;
use DateTime;
use DateTimeInterface;
use Exception;
use Psr\SimpleCache\CacheInterface;
use think\Container;
use think\contract\CacheHandlerInterface;
use think\exception\InvalidArgumentException;
use throwable;
abstract class Driver implements CacheInterface, CacheHandlerInterface
{
protected $handler = null;
protected $readTimes = 0;
protected $writeTimes = 0;
protected $options = [];
protected $tag = [];
protected function getExpireTime($expire): int
{
if ($expire instanceof DateTimeInterface) {
$expire = $expire->getTimestamp() - time();
} elseif ($expire instanceof DateInterval) {
$expire = DateTime::createFromFormat('U', (string) time())
->add($expire)
->format('U') - time();
}
return (int) $expire;
}
public function getCacheKey(string $name): string
{
return $this->options['prefix'] . $name;
}
public function pull(string $name)
{
$result = $this->get($name, false);
if ($result) {
$this->delete($name);
return $result;
}
}
public function push(string $name, $value): void
{
$item = $this->get($name, []);
if (!is_array($item)) {
throw new InvalidArgumentException('only array cache can be push');
}
$item[] = $value;
if (count($item) > 1000) {
array_shift($item);
}
$item = array_unique($item);
$this->set($name, $item);
}
public function remember(string $name, $value, $expire = null)
{
if ($this->has($name)) {
return $this->get($name);
}
$time = time();
while ($time + 5 > time() && $this->has($name . '_lock')) {
usleep(200000);
}
try {
$this->set($name . '_lock', true);
if ($value instanceof Closure) {
$value = Container::getInstance()->invokeFunction($value);
}
$this->set($name, $value, $expire);
$this->delete($name . '_lock');
} catch (Exception | throwable $e) {
$this->delete($name . '_lock');
throw $e;
}
return $value;
}
public function tag($name): TagSet
{
$name = (array) $name;
$key = implode('-', $name);
if (!isset($this->tag[$key])) {
$name = array_map(function ($val) {
return $this->getTagKey($val);
}, $name);
$this->tag[$key] = new TagSet($name, $this);
}
return $this->tag[$key];
}
public function getTagItems(string $tag): array
{
return $this->get($tag, []);
}
public function getTagKey(string $tag): string
{
return $this->options['tag_prefix'] . md5($tag);
}
protected function serialize($data): string
{
if (is_numeric($data)) {
return (string) $data;
}
$serialize = $this->options['serialize'][0] ?? "serialize";
return $serialize($data);
}
protected function unserialize(string $data)
{
if (is_numeric($data)) {
return $data;
}
$unserialize = $this->options['serialize'][1] ?? "unserialize";
return $unserialize($data);
}
public function handler()
{
return $this->handler;
}
public function getReadTimes(): int
{
return $this->readTimes;
}
public function getWriteTimes(): int
{
return $this->writeTimes;
}
public function getMultiple($keys, $default = null): iterable
{
$result = [];
foreach ($keys as $key) {
$result[$key] = $this->get($key, $default);
}
return $result;
}
public function setMultiple($values, $ttl = null): bool
{
foreach ($values as $key => $val) {
$result = $this->set($key, $val, $ttl);
if (false === $result) {
return false;
}
}
return true;
}
public function deleteMultiple($keys): bool
{
foreach ($keys as $key) {
$result = $this->delete($key);
if (false === $result) {
return false;
}
}
return true;
}
public function __call($method, $args)
{
return call_user_func_array([$this->handler, $method], $args);
}
}