Added Model Cacheable.

This commit is contained in:
李铭昕 2019-01-25 14:12:25 +08:00
parent 3caba80d1e
commit 194b66faa7
11 changed files with 493 additions and 1 deletions

View File

@ -16,6 +16,7 @@
"psr/http-message": "^1.0.1",
"psr/http-server-middleware": "^1.0",
"psr/event-dispatcher": "^0.7",
"psr/simple-cache": "^1.0",
"fig/http-message-util": "^1.1.2",
"nikic/fast-route": "^1.3",
"nikic/php-parser": "^4.1",

View File

@ -12,6 +12,7 @@
},
"require": {
"php": ">=7.2",
"psr/simple-cache": "^1.0",
"hyperf/database": "dev-master",
"hyperf/di": "dev-master",
"hyperf/event": "dev-master",

View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://hyperf.org
* @document https://wiki.hyperf.org
* @contact group@hyperf.org
* @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
*/
namespace Hyperf\DbConnection\Cache;
use Hyperf\Framework\ApplicationContext;
trait Cacheable
{
public static function findFromCache($id)
{
$container = ApplicationContext::getContainer();
$manager = $container->get(Manager::class);
return $manager->findFromCache($id, static::class);
}
public static function findManyFromCache($ids)
{
}
}

View File

@ -0,0 +1,102 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://hyperf.org
* @document https://wiki.hyperf.org
* @contact group@hyperf.org
* @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
*/
namespace Hyperf\DbConnection\Cache;
class Config
{
/**
* Model cache key.
*
* mc:$prefix:m:$model:$pk:$id
* You can rewrite it in Redis cluster, for examqple {mc:$prefix:m:$model}:$pk:$id
* @var string
*/
protected $cacheKey = 'mc:%s:m:%s:%s:%s';
/**
* @var string
*/
protected $prefix = 'hyperf';
/**
* @var int
*/
protected $ttl = 3600;
/**
* @var bool
*/
protected $loadScript = true;
public function __construct(array $values, string $name)
{
if (isset($values['cache_key'])) {
$this->cacheKey = $values['cache_key'];
}
if (isset($values['prefix'])) {
$this->prefix = $values['prefix'];
} else {
$this->prefix = $name;
}
if (isset($values['ttl'])) {
$this->ttl = $values['ttl'];
}
if (isset($values['load_script'])) {
$this->loadScript = $values['load_script'];
}
}
public function getCacheKey(): string
{
return $this->cacheKey;
}
public function setCacheKey(string $cacheKey): Config
{
$this->cacheKey = $cacheKey;
return $this;
}
public function getPrefix(): string
{
return $this->prefix;
}
public function setPrefix(string $prefix): Config
{
$this->prefix = $prefix;
return $this;
}
public function getTtl(): int
{
return $this->ttl;
}
public function setTtl(int $ttl): Config
{
$this->ttl = $ttl;
return $this;
}
public function isLoadScript(): bool
{
return $this->loadScript;
}
public function setLoadScript(bool $loadScript): Config
{
$this->loadScript = $loadScript;
return $this;
}
}

View File

@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://hyperf.org
* @document https://wiki.hyperf.org
* @contact group@hyperf.org
* @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
*/
namespace Hyperf\DbConnection\Cache\Exception;
class CacheException extends \RuntimeException
{
}

View File

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://hyperf.org
* @document https://wiki.hyperf.org
* @contact group@hyperf.org
* @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
*/
namespace Hyperf\DbConnection\Cache\Handler;
use Hyperf\DbConnection\Cache\Config;
use Psr\SimpleCache\CacheInterface;
interface HandlerInterface extends CacheInterface
{
public function getConfig(): Config;
}

View File

@ -0,0 +1,149 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://hyperf.org
* @document https://wiki.hyperf.org
* @contact group@hyperf.org
* @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
*/
namespace Hyperf\DbConnection\Cache\Handler;
use Hyperf\DbConnection\Cache\Config;
use Hyperf\DbConnection\Cache\Exception\CacheException;
use Hyperf\DbConnection\Cache\Redis\HashsGetMultiple;
use Hyperf\Utils\Contracts\Arrayable;
use Psr\Container\ContainerInterface;
use Redis;
class RedisHandler implements HandlerInterface
{
/**
* @var ContainerInterface
*/
protected $container;
/**
* @var Redis
*/
protected $redis;
/**
* @var Config
*/
protected $config;
/**
* @var HashsGetMultiple
*/
protected $multiple;
protected $luaSha = '';
protected $defaultHash = ['HF-DATA' => 'DEFAULT'];
public function __construct(ContainerInterface $container, Config $config)
{
$this->container = $container;
if (! $container->has(Redis::class)) {
throw new CacheException(sprintf('Entry[%s] of the container is not exist.', Redis::class));
}
$this->redis = $container->get(Redis::class);
$this->config = $config;
$this->multiple = new HashsGetMultiple();
}
public function get($key, $default = null)
{
$data = $this->redis->hGetAll($key);
if (! $data) {
return $default;
}
if ($data == $this->defaultHash) {
return $default;
}
return $data;
}
public function set($key, $value, $ttl = null)
{
if (is_array($value)) {
$data = $value;
} elseif ($value instanceof Arrayable) {
$data = $value->toArray();
} else {
throw new CacheException(sprintf('The value must is array.'));
}
$data = array_merge($data, $this->defaultHash);
$res = $this->redis->hMSet($key, $data);
if ($ttl && $ttl > 0) {
$this->redis->expire($key, $ttl);
}
return $res;
}
public function delete($key)
{
return $this->redis->delete($key);
}
public function clear()
{
throw new CacheException('Method clear is forbidden.');
}
public function getMultiple($keys, $default = null)
{
if ($this->config->isLoadScript()) {
$sha = $this->getLuaSha();
}
if (! empty($sha)) {
$list = $this->redis->evalSha($sha, $keys, count($keys));
} else {
$script = $this->multiple->getScript();
$list = $this->redis->eval($script, $keys, count($keys));
}
return $list;
}
public function setMultiple($values, $ttl = null)
{
// TODO: Implement setMultiple() method.
}
public function deleteMultiple($keys)
{
// TODO: Implement deleteMultiple() method.
}
public function has($key)
{
// TODO: Implement has() method.
}
public function getConfig(): Config
{
return $this->config;
}
protected function getLuaSha()
{
if (! empty($this->luaSha)) {
return $this->luaSha;
}
$sha = $this->redis->script('load', $this->multiple->getScript());
return $this->luaSha = $sha;
}
}

View File

@ -0,0 +1,102 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://hyperf.org
* @document https://wiki.hyperf.org
* @contact group@hyperf.org
* @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
*/
namespace Hyperf\DbConnection\Cache;
use Hyperf\Contract\ConfigInterface;
use Hyperf\DbConnection\Cache\Handler\HandlerInterface;
use Hyperf\DbConnection\Cache\Handler\RedisHandler;
use Hyperf\DbConnection\Model\Model;
use Hyperf\Framework\Contract\StdoutLoggerInterface;
use Psr\Container\ContainerInterface;
class Manager
{
/**
* @var ContainerInterface
*/
protected $container;
/**
* @var HandlerInterface[]
*/
protected $handlers = [];
/**
* @var StdoutLoggerInterface
*/
protected $logger;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
$this->logger = $container->get(StdoutLoggerInterface::class);
$config = $container->get(ConfigInterface::class);
if (! $config->has('databases')) {
throw new \InvalidArgumentException('config databases is not exist!');
}
foreach ($config->get('databases') as $key => $item) {
$handler = $item['handler'] ?? RedisHandler::class;
$config = new Config($item['cache'] ?? [], $key);
$this->handlers[$key] = new $handler($this->container, $config);
}
}
public function findFromCache($id, string $class)
{
/** @var Model $instance */
$instance = new $class();
$name = $instance->getConnectionName();
$primaryKey = $instance->getKeyName();
if ($handler = $this->handlers[$name] ?? null) {
$key = $this->getCacheKey($id, $instance, $handler->getConfig());
$data = $handler->get($key);
if ($data) {
return $instance->newFromBuilder($data);
}
if (is_null($data)) {
$model = $instance->newQuery()->where($primaryKey, '=', $id)->first();
if ($model) {
$handler->set($key, $model->toArray());
} else {
$handler->set($key, []);
}
return $model;
}
return null;
}
$this->logger->warning('Cache handler not exist, fetch data from database.');
return $instance->newQuery()->where($primaryKey, '=', $id)->first();
}
public function findManyFromCache(array $ids, string $class)
{
}
protected function getCacheKey($id, Model $model, Config $config)
{
// mc:$prefix:m:$model:$pk:$id
return sprintf(
$config->getCacheKey(),
$config->getPrefix(),
$model->getTable(),
$model->getKeyName(),
$id
);
}
}

View File

@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://hyperf.org
* @document https://wiki.hyperf.org
* @contact group@hyperf.org
* @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
*/
namespace Hyperf\DbConnection\Cache\Redis;
class HashsGetMultiple implements OperatorInterface
{
public function getScript(): string
{
$command = <<<LUA
local values = {};
for i,v in ipairs(KEYS) do
if(redis.call('type',v).ok == 'hash') then
values[#values+1] = redis.call('hgetall',v);
end
end
return values;
LUA;
return $command;
}
public function parseResponse($data)
{
$result = [];
foreach ($data ?? [] as $item) {
if (! empty($item) && is_array($item)) {
$temp = [];
$count = count($item);
for ($i = 0; $i < $count; ++$i) {
$temp[$item[$i]] = $item[++$i];
}
$result[] = $temp;
}
}
return $result;
}
}

View File

@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://hyperf.org
* @document https://wiki.hyperf.org
* @contact group@hyperf.org
* @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
*/
namespace Hyperf\DbConnection\Cache\Redis;
interface OperatorInterface
{
public function getScript(): string;
public function parseResponse($data);
}

View File

@ -43,7 +43,7 @@ class Model extends BaseModel
}
/**
* @throws RuntimeException When the model does not define the repository class.
* @throws RuntimeException when the model does not define the repository class
*/
public function getRepository()
{