文档地址:Hyperf
Aspect基于概念AOP(面向切面编程),aop:
Hyperf。
用通俗的话来讲,就是在 Hyperf 里可以通过
切面(Aspect)
介入到任意类的任意方法的执行流程中去,从而改变或加强原方法的功能,这就是 AOP。
我理解就像tp中的log类,引入之后可以在任何位置保存日志,或者tp全局自定义函数,比如核心文件中的help.php或者function.php中的函数可以随时随地使用。
psr-16规范:PSR-16: Common Interface for Caching Libraries - PHP-FIG
配置文件
#config/cache.php
return [
'default' => [
'driver' => Hyperf\Cache\Driver\RedisDriver::class,//缓存驱动,默认为 Redis
'packer' => Hyperf\Utils\Packer\PhpSerializerPacker::class,//打包器
'prefix' => 'c:',//缓存前缀
],
];
模块hyperf/cache,使用psr/simple-cache,composer地址:psr/simple-cache - Packagist,仅定义缓存实现接口。
系统内部实现为Hyperf\Cache\Cache类,该类继承Psr\SimpleCache\CacheInterface。
#Hyperf\Cache\ConfigProvider
public function __invoke(): array
{
return [
'dependencies' => [
CacheInterface::class => Cache::class,
],
……
];
}
#Hyperf\Cache\Cache
use Psr\SimpleCache\CacheInterface;
class Cache implements CacheInterface
{……}
可以通过备注方法使用,下文介绍。
默认驱动为Hyperf\Cache\Driver\RedisDriver,继承Hyperf\Cache\Driver\Driver,通过容器获取redis。自定义驱动可以参照该类。
#/config/authload/cache.php
return [
'default' => [
'driver' => Hyperf\Cache\Driver\RedisDriver::class,
'packer' => Hyperf\Utils\Packer\PhpSerializerPacker::class,
'prefix' => 'c:',
],
];
#Hyperf\Cache\Driver\RedisDriver
class RedisDriver extends Driver implements KeyCollectorInterface
{
/**
* @var \Redis
*/
protected $redis;
public function __construct(ContainerInterface $container, array $config)
{
parent::__construct($container, $config);
$this->redis = $container->get(\Redis::class);
}
……
}
以使用框架自带另一个驱动举例。Hyperf\Cache\Driver\CoroutineMemoryDriver 携程内存驱动,重点在于程序的并发执行,例子中调用getCache1()(get()->sleep(2))两次,应该总间隔4秒左右,结果只执行2秒左右。
#/config/autoload/cache.php
return [
'default' => [
'driver' => Hyperf\Cache\Driver\RedisDriver::class,
'packer' => Hyperf\Utils\Packer\PhpSerializerPacker::class,
'prefix' => 'c:',
],
'test1' => [
'driver' => Hyperf\Cache\Driver\CoroutineMemoryDriver::class,
'packer' => Hyperf\Utils\Packer\PhpSerializerPacker::class,
'prefix' => 'cm:',
],
];
# App\Controller\TestController 自定义控制器
/**
* @Inject
* @var Cache1Service
*/
/**
携程缓存
*/
public function index3()
{
$time1 = microtime(true);
$user = $this->service->getCache1();
$user2 = $this->service->getCache1();
$user3 = $this->service->getCache1();
$time2 = microtime(true);
$time = $time2 - $time1;
$data = [
'userlist' => [
$user,
$user2,
$user3,
],
$time,
];
return $this->response->json($data);
}
#执行结果
{"userlist":["test_1689584463","test_1689584463","test_1689584463"],"0":2.076417922973633}]
#App\Service\Cache1Service
/**
* @Cacheable(prefix="cache", group="test1")
*/
public function getCache1($user = "test")
{
return $this->get($user);
}
默认配置,在.env中可以设置默认的redis连接属性,在配置文件中可以设置多个redis链接。
自定义在驱动中处理,所以要加配置需手动重写驱动和缓存配置,主要点在设置redis实例时不适用容器获取,而是使用redis代理创建。
#/config/autoload/redis.php
return [
'default' => [
'host' => env('REDIS_HOST', 'localhost'),
'auth' => env('REDIS_AUTH', null),
'port' => (int) env('REDIS_PORT', 6379),
'db' => (int) env('REDIS_DB', 0),
'pool' => [
'min_connections' => 1,
'max_connections' => 10,
'connect_timeout' => 10.0,
'wait_timeout' => 3.0,
'heartbeat' => -1,
'max_idle_time' => (float) env('REDIS_MAX_IDLE_TIME', 60),
],
],
];
#/config/autoload/cache.php
……
'test2' => [
'driver' => App\Service\RedisDriver::class,
'packer' => Hyperf\Utils\Packer\PhpSerializerPacker::class,
'prefix' => 'cm:',
],
];
……
# App\Service\RedisDriver
class RedisDriver extends hRedisDriver
{
/**
* @var \Redis
*/
protected $redis;
public function __construct(ContainerInterface $container, array $config)
{
parent::__construct($container, $config);
$this->redis = make(RedisProxy::class, ['pool' => 'test1']);
}
}
#App\Service\Cache1Service
/**
* @Cacheable(prefix="cache", group="test2")
*/
public function getCache2($user = "test")
{
return $this->get($user);
}
#请求
curl 127.0.0.1:9501/test/index4?user=qwe1
{"user":"qwe1_1689586593"}
#redis
select 1
keys *
1) "cm:cache:qwe1"
get cm:cache:qwe1
"s:15:\"qwe1_1689586593\";"
通过容器获取缓存实体类,对应操作redis的方法在对应驱动中。
$container = Hyperf\Utils\ApplicationContext::getContainer();
//获取缓存 Hyperf\Cache\Cache
$cache = $container->get(\Psr\SimpleCache\CacheInterface::class);
//调用driver方法
# Hyperf\Cache\Cache
public function __call($name, $arguments)
{
return $this->driver->{$name}(...$arguments);
}
public function get($key, $default = null)
{
return $this->__call(__FUNCTION__, func_get_args());
}
public function set($key, $value, $ttl = null)
{
return $this->__call(__FUNCTION__, func_get_args());
}
#Hyperf\Cache\Driver\RedisDriver
public function get($key, $default = null)
{
$res = $this->redis->get($this->getCacheKey($key));
if ($res === false) {
return $default;
}
return $this->packer->unpack($res);
}
public function set($key, $value, $ttl = null)
{
$seconds = $this->secondsUntil($ttl);
$res = $this->packer->pack($value);
if ($seconds > 0) {
return $this->redis->set($this->getCacheKey($key), $res, $seconds);
}
return $this->redis->set($this->getCacheKey($key), $res);
}
注释通过反射实现方法的调用,有两种注释方法。
/**
*@方法名(参数=值,参数=值)
**/
#[@方法名(参数:值,参数:值)]
依赖注入通过aop实现,没看通过视频教程大概知道调用。
设置文件之后会在runtime中再生成对应的文件,其中有aop的调用。
大概通过inject设置注入对象到AnnotationCollector类中,被注入对象调用构造再从AnnotationCollector类中获取。
#runtime/container/proxy/app_controller_TestController.proxy.php
class TestController extends AbstractController
{
use \Hyperf\Di\Aop\ProxyTrait;
use \Hyperf\Di\Aop\PropertyHandlerTrait;
function __construct()
{
if (method_exists(parent::class, '__construct')) {
parent::__construct(...func_get_args());
}
$this->__handlePropertyHandler(__CLASS__);
}
……
}
#Hyperf\Di\Aop\PropertyHandlerTrait
trait PropertyHandlerTrait
{
protected function __handlePropertyHandler(string $className)
{
……
// Inject the properties of current class
$handled = $this->__handle($className, $className, $properties);
// Inject the properties of traits.
// Because the property annotations of trait couldn't be collected by class.
$handled = $this->__handleTrait($reflectionClass, $handled, $className);
……
}
protected function __handle(string $currentClassName, string $targetClassName, array $properties): array
{
$handled = [];
foreach ($properties as $propertyName) {
$propertyMetadata = AnnotationCollector::getClassPropertyAnnotation($targetClassName, $propertyName);
if (! $propertyMetadata) {
continue;
}
foreach ($propertyMetadata as $annotationName => $annotation) {
if ($callbacks = PropertyHandlerManager::get($annotationName)) {
foreach ($callbacks as $callback) {
call($callback, [$this, $currentClassName, $targetClassName, $propertyName, $annotation]);
}
$handled[] = $propertyName;
}
}
}
return $handled;
}
}
#Hyperf\Di\Annotation\AnnotationCollector
public static function collectProperty(string $class, string $property, string $annotation, $value): void
{
static::$container[$class]['_p'][$property][$annotation] = $value;
}
public static function getClassPropertyAnnotation(string $class, string $property)
{
return static::get($class . '._p.' . $property);
}
#Hyperf\Di\Annotation\Inject
class Inject extends AbstractAnnotation
{
public function collectProperty(string $className, ?string $target): void
{
try {
$reflectionClass = ReflectionManager::reflectClass($className);
$reflectionProperty = $reflectionClass->getProperty($target);
if (method_exists($reflectionProperty, 'hasType') && $reflectionProperty->hasType()) {
/* @phpstan-ignore-next-line */
$this->value = $reflectionProperty->getType()->getName();
} else {
$this->value = PhpDocReaderManager::getInstance()->getPropertyClass($reflectionProperty);
}
if (empty($this->value)) {
throw new AnnotationException("The @Inject value is invalid for {$className}->{$target}");
}
if ($this->lazy) {
$this->value = 'HyperfLazy\\' . $this->value;
}
AnnotationCollector::collectProperty($className, $target, static::class, $this);
} catch (AnnotationException|DocReaderAnnotationException $exception) {
if ($this->required) {
throw new AnnotationException($exception->getMessage());
}
$this->value = '';
} catch (\Throwable $exception) {
throw new AnnotationException("The @Inject value is invalid for {$className}->{$target}. Because {$exception->getMessage()}");
}
}
}
#Hyperf\Di\Annotation\AbstractAnnotation
public function __construct(...$value)
{
$formattedValue = $this->formatParams($value);
foreach ($formattedValue as $key => $val) {
if (property_exists($this, $key)) {
$this->{$key} = $val;
}
}
}
通过注释实现缓存,主要是通过注入一个自定义类,在自定义类中设置注释。
# App\Controller\TestController 自定义控制器
/**
* @Inject
* @var Cache1Service
*/
public function index1(RequestInterface $request)
{
$user = $request->input('user', 'Hyperf');
$result = $this->service->getCache($user);
return $this->response->json($result);
}
#App\Service\Cache1Service
public function get($user = 'test')
{
sleep(2);
return $user . "_" . time();
}
/**
* @Cacheable(prefix="cache",ttl=10, listener="user-update")
*/
public function getCache($user = "test")
{
return $this->get($user);
}
b站视频:
【Hyperf 缓存】https://www.bilibili.com/video/BV1TS4y1g7tQ?vd_source=f1bd3b5218c30adf0a002c8c937e0a27
【Hyperf 2.0 - Inject 和 Aop】https://www.bilibili.com/video/BV1VT4y167cb?vd_source=f1bd3b5218c30adf0a002c8c937e0a27
强烈推荐,应该是swoole官方教程。
hyperf框架-设计模式(二)_hyperf inject_Aring88的博客-CSDN博客
百度结,但是果没看懂