Hyperf 初体验-汇总

请求与协程生命周期

Swoole 在处理每个连接时,会默认创建一个协程去处理,主要体现在 onRequestonReceiveonConnect 事件,所以可以理解为每个请求都是一个协程,由于创建协程也是个常规操作,所以一个请求协程里面可能会包含很多个协程,同一个进程内协程之间是内存共享的,但调度顺序是非顺序的,且协程间本质上是相互独立的没有父子关系,所以对每个协程的状态处理都需要通过 协程上下文 来管理。

懒加载

Hyperf 的长生命周期依赖注入在项目启动时完成。这意味着长生命周期的类需要注意:

「用户不应该将容器作为参数传入对象然后在对象中通过容器获得对象的依赖。这样是把容器当作服务定位器来使用,而服务定位器是一种反模式」

目前解决方案是:只在实例中注入 Psr\Container\ContainerInterface ,而其他的组件在非构造函数执行时通过 container 获取。但 PSR-11 中指出:

PHP 中常用的惰性代理模式,注入一个代理对象,在使用时再实例化目标对象。Hyperf DI 组件设计了懒加载注入功能。

  • 构造函数时还不是协程环境,如果注入了可能会触发协程切换的类,就会导致框架启动失败。

  • 构造函数中要避免循环依赖(比较典型的例子为 Listener 和 EventDispatcherInterface),不然也会启动失败。

您还可以通过注解 @Inject(lazy=true) 注入懒加载代理。通过注解实现懒加载不用创建配置文件。

use Hyperf\Di\Annotation\Inject;
use App\Service\UserServiceInterface;

class Foo{
    /**
     * @Inject(lazy=true)
     * @var UserServiceInterface
     */
    public $service;
}

短生命周期对象

通过 new 关键词创建的对象毫无疑问的短生命周期的,那么如果希望创建一个短生命周期的对象但又希望使用 构造函数依赖自动注入功能 呢?这时我们可以通过 make(string $name, array $parameters = []) 函数来创建 $name 对应的的实例,代码示例如下:

$userService = make(UserService::class, ['enableCache' => true]);Copy to clipboardErrorCopied

注意仅 $name 对应的对象为短生命周期对象,该对象的所有依赖都是通过 get() 方法获取的,即为长生命周期的对象,可理解为该对象是一个浅拷贝的对象

@Inject 注入覆盖顺序[就近原则]

@Inject 覆盖顺序为子类覆盖 Trait 覆盖 父类,即 下述 Origin 的 foo 变量为本身注入的 Foo1

同理,假如 Origin 不存在变量 $foo 时,$foo 会被第一个 Trait 完成注入,注入类 Foo2

use Hyperf\Di\Annotation\Inject;

class ParentClass
{
    /**
     * @Inject
     * @var Foo4 
     */
    protected $foo;
}

trait Foo1{
    /**
     * @Inject
     * @var Foo2 
     */
    protected $foo;
}

trait Foo2{
    /**
     * @Inject
     * @var Foo3
     */
    protected $foo;
}

class Origin extends ParentClass
{
    use Foo1;
    use Foo2;
    /**
     * @Inject
     * @var Foo1
     */
    protected $foo;
}

  Hyperf 生命周期事件

注意事项

不要在 Listener 中注入 EventDispatcherInterface

因为 EventDispatcherInterface 依赖于 ListenerProviderInterface,而 ListenerProviderInterface 初始化的同时,会收集所有的 Listener

而如果 Listener 又依赖了 EventDispatcherInterface就会导致循坏依赖,进而导致内存溢出。

最好只在 Listener 中注入 ContainerInterface。

最好只在 Listener 中注入 ContainerInterface,而其他的组件在 process 中通过 container 获取。框架启动开始时,会实例化 EventDispatcherInterface,这个时候还不是协程环境,如果 Listener 中注入了可能会触发协程切换的类,就会导致框架启动失败。

用通俗的话来讲,就是在 Hyperf 里可以通过 切面(Aspect) 介入到任意类的任意方法的执行流程中去,从而改变或加强原方法的功能,这就是 AOP。

注意这里所指的任意类并不是完全意义上的所有类,在 Hyperf 启动初期用于实现 AOP 功能的类自身不能被切入。

常用注解 @Inject() @Crontab() @Listen() @Constants() declare(strict_types=1) @Aspect

<?php
namespace App\Aspect;

use App\Service\SomeClass;
use App\Annotation\SomeAnnotation;
use Hyperf\Di\Annotation\Aspect;
use Hyperf\Di\Aop\AbstractAspect;
use Hyperf\Di\Aop\ProceedingJoinPoint;

/**
 * @Aspect
 */
class FooAspect extends AbstractAspect
{
    // 要切入的类或 Trait,可以多个,亦可通过 :: 标识到具体的某个方法,通过 * 可以模糊匹配
    public $classes = [
        SomeClass::class,
        'App\Service\SomeClass::someMethod',
        'App\Service\SomeClass::*Method',
    ];

    // 要切入的注解,具体切入的还是使用了这些注解的类,仅可切入类注解和类方法注解
    public $annotations = [
        SomeAnnotation::class,
    ];

    public function process(ProceedingJoinPoint $proceedingJoinPoint)
    {
        // 切面切入后,执行对应的方法会由此来负责
        // $proceedingJoinPoint 为连接点,通过该类的 process() 方法调用原方法并获得结果
        // 在调用前进行某些处理
        $result = $proceedingJoinPoint->process();
        // 在调用后进行某些处理
        return $result;
    }
}
<?php
namespace App\Aspect;

use App\Service\SomeClass;
use App\Annotation\SomeAnnotation;
use Hyperf\Di\Annotation\Aspect;
use Hyperf\Di\Aop\AbstractAspect;
use Hyperf\Di\Aop\ProceedingJoinPoint;

/**
 * @Aspect(
 *   classes={
 *      SomeClass::class,
 *      "App\Service\SomeClass::someMethod",
 *      "App\Service\SomeClass::*Method"
 *   },
 *   annotations={
 *      SomeAnnotation::class
 *   }
 * )
 */
class FooAspect extends AbstractAspect
{
    public function process(ProceedingJoinPoint $proceedingJoinPoint)
    {
        // 切面切入后,执行对应的方法会由此来负责
        // $proceedingJoinPoint 为连接点,通过该类的 process() 方法调用原方法并获得结果
        // 在调用前进行某些处理
        $result = $proceedingJoinPoint->process();
        // 在调用后进行某些处理
        return $result;
    }
}

代理类缓存

所有被 AOP 影响的类,都会在 ./runtime/container/proxy/ 文件夹内生成对应的 代理类缓存,是否在启动时自动生成取决于 config/config.php 配置文件中 scan_cacheable 配置项的值,默认值为 false,如果该配置项为 true 则 Hyperf 不会扫描和生成代理类缓存,而是直接以现有的缓存文件作为最终的代理类。如果该配置项为 false,则 Hyperf 会在每次启动应用时扫描注解扫描域并自动的生成对应的代理类缓存,当代码发生变化时,代理类缓存也会自动的重新生成。

通常在开发环境下,该值为 false,这样更便于开发调试,而在部署生产环境时,我们可能会希望 Hyperf 提前将所有代理类提前生成,而不是使用时动态的生成,可以通过 php bin/hyperf.php 命令来生成所有代理类,然后再通过环境变量 SCAN_CACHEABLE 为 true 修改该配置项的值,以达到启动时间更短、应用内存占用更低的目的。

基于以上,如果您使用 Docker 或 Kubernetes 等虚拟化技术来部署您的应用的话,您可以在镜像构建阶段就生成对应的代理类缓存并写入到镜像中去,在运行镜像实例时,可大大减少启动时间和应用内存。

中间件的执行顺序

我们从上面可以了解到总共有 3 种级别的中间件,分别为 全局中间件类级别中间件方法级别中间件,如果都定义了这些中间件,执行顺序为:全局中间件 -> 类级别中间件 -> 方法级别中间件

全局更改请求和响应对象

首先,在协程上下文内是有存储最原始的 PSR-7 请求对象 和 响应对象 的,且根据 PSR-7 对相关对象所要求的 不可变性(immutable),也就意味着我们在调用 $response = $response->with***() 所调用得到的 $response,并非为改写原对象,而是一个 Clone 出来的新对象,也就意味着我们储存在协程上下文内的 请求对象 和 响应对象 是不会改变的,那么当我们在中间件内的某些逻辑改变了 请求对象 或 响应对象,而且我们希望对后续的 非传递性的 代码再获取改变后的 请求对象 或 响应对象那么我们便可以在改变对象后,将新的对象设置到上下文中,如代码所示:

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;

// $request 和 $response 为修改后的对象
$request = \Hyperf\Utils\Context::set(ServerRequestInterface::class, $request);
$response = \Hyperf\Utils\Context::set(ResponseInterface::class, $response);

避免协程间数据混淆

在传统的 PHP-FPM 的框架里,会习惯提供一个 AbstractController 或其它命名的 Controller 抽象父类,然后定义的 Controller 需要继承它用于获取一些请求数据或进行一些返回操作,在 Hyperf 里是 不能这样做 的,因为在 Hyperf 内绝大部分的对象包括 Controller 都是以 单例(Singleton) 形式存在的,这也是为了更好的复用对象,而对于与请求相关的数据在协程下也是需要储存到 协程上下文(Context) 内的,所以在编写代码时请务必注意 不要 将单个请求相关的数据储存在类属性内,包括非静态属性

当然如果非要通过类属性来储存请求数据的话,也不是没有办法的,我们可以注意到我们获取 请求(Request) 与 响应(Response) 对象时是通过注入 Hyperf\HttpServer\Contract\RequestInterface 和 Hyperf\HttpServer\Contract\ResponseInterface 来获取的,那对应的对象不也是个单例吗?这里是如何做到协程安全的呢?就 RequestInterface 来举例,对应的 Hyperf\HttpServer\Request 对象内部在获取 PSR-7 请求对象 时,都是从 协程上下文(Context) 获取的,所以实际使用的类仅仅是一个代理类,实际调用的都是从 协程上下文(Context) 中获取的。

好的,以下是一个安装 Hyperf 框架的 `yaml` 文件示例: ```yaml stages: - install - start install: stage: install image: docker:latest services: - docker:dind script: - docker run -d --name hyperf -v /mnt/hgfs/www/hyperf/:/hyperf-skeleton -p 9501:9501 -it --privileged -u root --entrypoint /bin/sh hyperf/hyperf:7.4-alpine-v3.11-swoole - docker exec -it hyperf sh -c "cd /hyperf-skeleton && composer config -g repo.packagist composer https://mirrors.aliyun.com/composer && composer create-project hyperf/hyperf-skeleton" artifacts: paths: - /mnt/hgfs/www/hyperf/hyperf-skeleton start: stage: start image: docker:latest services: - docker:dind script: - docker start hyperf - docker exec -it hyperf sh -c "cd /hyperf-skeleton && php /hyperf-skeleton/bin/hyperf.php start" ``` 这个 YAML 文件定义了两个阶段,第一个阶段为 `install`,用于在 Docker 容器中安装 Hyperf 框架及其相关依赖;第二个阶段为 `start`,用于在 Docker 容器中启动 Hyperf 框架。在 `install` 阶段中,我们使用了 `docker` 镜像,在 Docker 容器中执行了 `docker run` 命令来创建 Hyperf 容器,并执行了 `docker exec` 命令来执行安装 Hyperf 框架及其相关依赖的命令。在 `start` 阶段中,我们同样使用了 `docker` 镜像,在 Docker 容器中执行了 `docker start` 命令来启动 Hyperf 容器,并执行了 `docker exec` 命令来启动 Hyperf 框架。在 `install` 阶段中,我们还使用了 `artifacts` 关键字来指定将 Hyperf 框架所在的目录 `/mnt/hgfs/www/hyperf/hyperf-skeleton` 作为构建产物,以便在后续的阶段中使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值