Hyperf 学习日志

本文是根据Hyperf官方文档的学习日志, 看官请直接访问 Hyperf

什么是Hyperf ?

Hyperspeed + Flexibility = Hyperf

Hyperf 是基于 Swoole 4.4+ 实现的高性能、高灵活性的 PHP 协程框架,内置协程服务器及大量常用的组件,性能较传统基于 PHP-FPM 的框架有质的提升,提供超高性能的同时,也保持着极其灵活的可扩展性,标准组件均基于 PSR 标准 实现,基于强大的依赖注入设计,保证了绝大部分组件或类都是 可替换 与 可复用 的。

安装Hyperf

服务器要求

Hyperf 对系统环境有一些要求,仅可运行于 LinuxMac 环境下,但由于 Docker 虚拟化技术的发展,在 Windows 下也可以通过 Docker for Windows 来作为运行环境

hyperf-cloud\hyperf-docker 项目内已经为您准备好了各种版本的 Dockerfile ,或直接基于已经构建好的 hyperf\hyperf 镜像来运行。

确保你的运行环境达到了以下的要求:

  • PHP >= 7.2
  • Swoole PHP 扩展 >= 4.4,并关闭了 Short Name
  • OpenSSL PHP 扩展
  • JSON PHP 扩展
  • PDO PHP 扩展 (如需要使用到 MySQL 客户端)
  • Redis PHP 扩展 (如需要使用到 Redis 客户端)
  • Protobuf PHP 扩展 (如需要使用到 gRPC 服务端或客户端)

查看Swoole扩展版本

1
2
3
4
5
6
7
8
[root@caoxl ~]# php --ri swoole

swoole

Swoole => enabled
Author => Swoole Team <team@swoole.com>
Version => 4.4.7
Built => Oct 11 2019 10:26:37

升级Swoole扩展

1
pecl upgrade swoole

升级swoole版本的常见问题

查看PHP版本

1
2
3
4
5
[root@caoxl ~]# php -v
PHP 7.2.7 (cli) (built: Nov 15 2018 11:33:02) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies
with Zend OPcache v7.2.7, Copyright (c) 1999-2018, by Zend Technologies

升级PHP版本

Linux 下安装开发环境(多版本PHP)

安装Hyperf

Composer 安装

1
composer create-project hyperf/hyperf-skeleton 
  • 开始安装
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@caoxl www]# composer create-project hyperf/hyperf-skeleton 
Do not run Composer as root/super user! See https://getcomposer.org/root for details
Installing hyperf/hyperf-skeleton (v1.1.0)
- Installing hyperf/hyperf-skeleton (v1.1.0): Downloading (100%)
Created project in /var/www/hyperf-skeleton
> @php -r "file_exists('.env') || copy('.env.example', '.env');"
> Installer\Script::install
Setting up optional packages
Setup data and cache dir
Removing installer development dependencies
...
Writing lock file
Generating autoload files
ocramius/package-versions: Generating version class...
ocramius/package-versions: ...done generating version class
  • 安装完成
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@caoxl hyperf-skeleton]# ll -s
total 344K
4.0K drwxrwxr-x 6 root root 4.0K Oct 8 01:26 app
4.0K drwxrwxr-x 2 root root 4.0K Oct 8 01:26 bin
4.0K -rw-rw-r-- 1 root root 2.6K Oct 11 10:41 composer.json
296K -rw-rw-r-- 1 root root 295K Oct 11 10:41 composer.lock
4.0K drwxrwxr-x 3 root root 4.0K Oct 8 01:26 config
4.0K -rw-rw-r-- 1 root root 580 Oct 8 01:26 deploy.test.yml
4.0K -rw-rw-r-- 1 root root 1.6K Oct 8 01:26 Dockerfile
4.0K -rw-rw-r-- 1 root root 339 Oct 8 01:26 phpstan.neon
4.0K -rw-rw-r-- 1 root root 695 Oct 8 01:26 phpunit.xml
4.0K -rw-rw-r-- 1 root root 2.3K Oct 8 01:26 README.md
4.0K drwxrwxr-x 2 root root 4.0K Oct 11 10:40 runtime
4.0K drwxrwxr-x 3 root root 4.0K Oct 8 01:26 test
4.0K drwxrwxr-x 44 root root 4.0K Oct 11 10:41 vendor

Dockter 下开发

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 下载并运行 hyperf/hyperf 镜像,并将镜像内的项目目录绑定到宿主机的 /tmp/skeleton 目录
docker run -v /tmp/skeleton:/hyperf-skeleton -p 9501:9501 -it --entrypoint /bin/sh hyperf/hyperf:7.2-alpine-cli

# 镜像容器运行后,在容器内安装 Composer
wget https://github.com/composer/composer/releases/download/1.8.6/composer.phar
chmod u+x composer.phar
mv composer.phar /usr/local/bin/composer

# 将 Composer 镜像设置为阿里云镜像,加速国内下载速度
composer config -g repo.packagist composer https://mirrors.aliyun.com/composer

# 通过 Composer 安装 hyperf/hyperf-skeleton 项目
composer create-project hyperf/hyperf-skeleton

# 进入安装好的 Hyperf 项目目录
cd hyperf-skeleton
# 启动 Hyperf
php bin/hyperf.php start

接下来,就可以在 /tmp/skeleton 中看到您安装好的代码了。由于 Hyperf 是持久化的 CLI 框架,当您修改完您的代码后,通过 CTRL + C 终止当前启动的进程实例,并重新执行 php bin/hyperf.php start 启动命令即可。

存在兼容性问题的扩展

由于 Hyperf 基于 Swoole 协程实现,而 Swoole 4 带来的协程功能是 PHP 前所未有的,所以与不少扩展都仍存在兼容性的问题。
以下扩展(包括但不限于)都会造成一定的兼容性问题,不能与之共用或共存:

  • xhprof
  • xdebug
  • blackfire
  • trace
  • uopz

快速开始

为了让您更快的了解 Hyperf 的使用,本章节将以 创建一个 HTTP Server 为例,通过对路由、控制器的定义实现一个简单的 Web 服务,但 Hyperf 不止于此,完善的服务治理、gRPC 服务、注解、AOP 等功能将由具体的章节阐述。

定义访问路由

Hyperf 使用 nikic/fast-route 作为默认的路由组件并提供服务,您可以很方便的在 config/routes.php 中定义您的路由。

通过配置文件定义路由

路由的文件位于 hyperf-skeleton 项目的 config/routes.php ,下面是一些常用的用法示例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?php

declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://doc.hyperf.io
* @contact group@hyperf.io
* @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
*/

use Hyperf\HttpServer\Router\Router;

Router::addRoute(['GET', 'POST', 'HEAD'], '/', 'App\Controller\IndexController@index');

// 此处代码示例为每个示例都提供了三种不同的绑定定义方式,实际配置时仅可采用一种且仅定义一次相同的路由

// 设置一个 GET 请求的路由,绑定访问地址 '/get' 到 App\Controller\IndexController 的 get 方法
Router::get('/get', 'App\Controller\IndexController::get');
Router::get('/get', 'App\Controller\IndexController@get');
Router::get('/get', [\App\Controller\IndexController::class, 'get']);

// 设置一个 POST 请求的路由,绑定访问地址 '/post' 到 App\Controller\IndexController 的 post 方法
Router::post('/post', 'App\Controller\IndexController::post');
Router::post('/post', 'App\Controller\IndexController@post');
Router::post('/post', [\App\Controller\IndexController::class, 'post']);

// 设置一个允许 GET、POST 和 HEAD 请求的路由,绑定访问地址 '/multi' 到 App\Controller\IndexController 的 multi 方法
Router::addRoute(['GET', 'POST', 'HEAd'], 'multi', 'App\Controller\IndexController::multi');
Router::addRoute(['GET', 'POST', 'HEAd'], 'multi', 'App\Controller\IndexController@multi');
Router::addRoute(['GET', 'POST', 'HEAd'], 'multi', [\App\Controller\IndexController::class, 'multi']);

通过注解来定义路由

Hyperf 提供了极其强大和方便灵活的 注解 功能,在路由的定义上也毫无疑问地提供了注解定义的方式,Hyperf 提供了 @Controller@AutoController 两种注解来定义一个 Controller.

通过 @AutoController 注解定义路由

@AutoController 为绝大多数简单的访问场景提供路由绑定支持,使用 @AutoController 时则 Hyperf 会自动解析所在类的所有 public 方法并提供 GETPOST 两种请求方式。

使用 @AutoController 注解时需 use Hyperf\HttpServer\Annotation\AutoController; 命名空间;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
declare(strict_types=1);

namespace App\Controller;

use Hyperf\HttpServer\Contract\RequestInterface;
use Hyperf\HttpServer\Annotation\AutoController;

/**
* @AutoController()
*/
class IndexController
{
// Hyperf 会自动为此方法生成一个 /index/index 的路由,允许通过 GET 或 POST 方式请求
public function index(RequestInterface $request)
{
// 从请求中获得 id 参数
$id = $request->input('id', 1);
return (string)$id;
}
}

通过 @Controller 注解定义路由

@Controller 为满足更细致的路由定义需求而存在,使用 @Controller 注解用于表明当前类为一个 Controller 类,同时需配合 @RequestMapping 注解来对请求方法和请求路径进行更详细的定义。

  • 使用 @Controller 注解时需 use Hyperf\HttpServer\Annotation\Controller; 命名空间;

  • 使用 @RequestMapping 注解时需 use Hyperf\HttpServer\Annotation\RequestMapping; 命名空间;

  • 使用 @GetMapping 注解时需 use Hyperf\HttpServer\Annotation\GetMapping; 命名空间;

  • 使用 @PostMapping 注解时需 use Hyperf\HttpServer\Annotation\PostMapping; 命名空间;

  • 使用 @PutMapping 注解时需 use Hyperf\HttpServer\Annotation\PutMapping; 命名空间;

  • 使用 @PatchMapping 注解时需 use Hyperf\HttpServer\Annotation\PatchMapping; 命名空间;

  • 使用 @DeleteMapping 注解时需 use Hyperf\HttpServer\Annotation\DeleteMapping; 命名空间;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php
declare(strict_types=1);

namespace App\Controller;

use Hyperf\HttpServer\Contract\RequestInterface;
use Hyperf\HttpServer\Annotation\Controller;
use Hyperf\HttpServer\Annotation\RequestMapping;

/**
* @Controller()
*/
class IndexController
{
// Hyperf 会自动为此方法生成一个 /index/index 的路由,允许通过 GET 或 POST 方式请求
/**
* @RequestMapping(path="index", methods="get,post")
*/
public function index(RequestInterface $request)
{
// 从请求中获得 id 参数
$id = $request->input('id', 1);
return (string)$id;
}
}

处理HTTP请求

Hyperf 是完全开放的,本质上没有规定您必须基于某种模式下去实现请求的处理,您可以采用传统的 MVC模式,亦可以采用 RequestHandler模式 来进行开发。

在 app 文件夹内创建一个 Controller 文件夹并创建 IndexController.php 如下,index 方法内从请求中获取了 id 参数,并转换为 字符串 类型返回到客户端。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
declare(strict_types=1);

namespace App\Controller;

use Hyperf\HttpServer\Contract\RequestInterface;
use Hyperf\HttpServer\Annotation\AutoController;

/**
* @AutoController()
*/
class IndexController
{
// Hyperf 会自动为此方法生成一个 /index/index 的路由,允许通过 GET 或 POST 方式请求
public function index(RequestInterface $request)
{
// 从请求中获得 id 参数
$id = $request->input('id', 1);
// 转换 $id 为字符串格式并以 plain/text 的 Content-Type 返回 $id 的值给客户端
return (string)$id;
}
}

依赖自动注入

依赖自动注入是 Hyperf 提供的一个非常强大的功能,也是保持框架灵活性的根基。
Hyperf 提供了两种注入方式,一种是大家常见的通过构造函数注入,另一种是通过 @Inject 注解注入

通过构造函数注入

只需在构造函数的参数内声明参数的类型,Hyperf 会自动注入对应的对象或值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?php
declare(strict_types=1);

namespace App\Controller;

use Hyperf\HttpServer\Contract\RequestInterface;
use Hyperf\HttpServer\Annotation\AutoController;
use App\Service\UserService;

/**
* @AutoController()
*/
class IndexController
{
/**
* @var UserService
*/
private $userService;

// 在构造函数声明参数的类型,Hyperf 会自动注入对应的对象或值
public function __construct(UserService $userService)
{
$this->userService = $userService;
}

// /index/info
public function info(RequestInterface $request)
{
$id = $request->input('id', 1);
return $this->userService->getInfoById((int)$id);
}
}

通过 @Inject 注解注入

只需对对应的类属性通过 @var 声明参数的类型,并使用 @Inject 注解标记属性 ,Hyperf 会自动注入对应的对象或值。

使用 @Inject 注解时需 use Hyperf\Di\Annotation\Inject; 命名空间;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php
declare(strict_types=1);

namespace App\Controller;

use Hyperf\HttpServer\Contract\RequestInterface;
use Hyperf\HttpServer\Annotation\AutoController;
use Hyperf\Di\Annotation\Inject;
use App\Service\UserService;

/**
* @AutoController()
*/
class IndexController
{
/**
* @Inject()
* @var UserService
*/
private $userService;

// /index/info
public function info(RequestInterface $request)
{
$id = $request->input('id', 1);
return $this->userService->getInfoById((int)$id);
}
}

启动 Hyperf 服务

由于 Hyperf 内置了协程服务器,也就意味着 Hyperf 将以 CLI 的形式去运行,所以在定义好路由及实际的逻辑代码之后,我们需要在项目根目录并通过命令行运行 php bin/hyperf.php start 来启动服务。

1
2
3
4
5
[root@caoxl hyperf-skeleton]# php bin/hyperf.php start
Detected an available cache, skip the app scan process.
Detected an available cache, skip the vendor scan process.
[DEBUG] Event Hyperf\Framework\Event\BootApplication handled by Hyperf\Di\Listener\BootApplicationListener listener.
...

Console 界面显示服务启动后便可通过 cURL 或 浏览器对服务正常发起访问了,默认服务会提供一个首页 http://127.0.0.1:9501/,对于本章示例引导的情况下,也就是上面的例子所对应的访问地址为 http://127.0.0.1:9501/index/info?id=1

访问: http://caoxl.com:9501/

1
2
3
4
{
method: "GET",
message: "Hello Hyperf."
}

重新加载代码

由于 Hyperf 是持久化的 CLI 应用,也就意味着一旦进程启动,已解析的 PHP 代码会持久化在进程中,也就意味着启动服务后您再修改的 PHP 代码不会改变已启动的服务,如希望服务重新加载您修改后的代码,需要通过在启动的 Console 中键入 CTRL + C 终止服务,再重新执行启动命令完成重启和重新加载。

核心架构

生命周期

框架生命周期

Hyperf 是运行于 Swoole 之上的,想要理解透彻 Hyperf 的生命周期,那么理解 Swoole 的生命周期也至关重要。
Hyperf 的命令管理默认由 symfony/console 提供支持(如果您希望更换该组件您也可以通过改变 skeleton 的入口文件更换成您希望使用的组件),在执行 php bin/hyperf.php start 后,将由 Hyperf\Server\Command\StartServer 命令类接管,并根据配置文件 config/autoload/server.php 内定义的 Server 逐个启动。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[DEBUG] Event Hyperf\Framework\Event\BootApplication handled by Hyperf\Di\Listener\BootApplicationListener listener.
[DEBUG] Event Hyperf\Framework\Event\BootApplication handled by Hyperf\Config\Listener\RegisterPropertyHandlerListener listener.
[DEBUG] Event Hyperf\Framework\Event\BootApplication handled by Hyperf\Paginator\Listener\PageResolverListener listener.
[DEBUG] Event Hyperf\Framework\Event\BeforeMainServerStart handled by Hyperf\Amqp\Listener\BeforeMainServerStartListener listener.
[DEBUG] Event Hyperf\Framework\Event\BeforeMainServerStart handled by Hyperf\Process\Listener\BootProcessListener listener.
[DEBUG] Event Hyperf\Framework\Event\OnStart handled by Hyperf\Server\Listener\InitProcessTitleListener listener.
[DEBUG] Event Hyperf\Framework\Event\OnManagerStart handled by Hyperf\Server\Listener\InitProcessTitleListener listener.
[DEBUG] Event Hyperf\Framework\Event\MainWorkerStart handled by Hyperf\Amqp\Listener\MainWorkerStartListener listener.
[INFO] Worker#0 started.
[DEBUG] Event Hyperf\Framework\Event\AfterWorkerStart handled by Hyperf\Server\Listener\InitProcessTitleListener listener.
[INFO] HTTP Server listening at 0.0.0.0:9501
[DEBUG] Event Hyperf\Framework\Event\AfterWorkerStart handled by Hyperf\Server\Listener\AfterWorkerStartListener listener.
[INFO] Worker#1 started.
[DEBUG] Event Hyperf\Framework\Event\AfterWorkerStart handled by Hyperf\Server\Listener\InitProcessTitleListener listener.
[DEBUG] Event Hyperf\Framework\Event\AfterWorkerStart handled by Hyperf\Server\Listener\AfterWorkerStartListener listener.

关于依赖注入容器的初始化工作,并没有由组件来实现,因为一旦交由组件来实现,这个耦合就会非常的明显,所以在默认的情况下,是由入口文件来加载 config/container.php 来实现的。

请求与协程生命周期

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

协程上下文

由于同一个进程内协程间是内存共享的,但协程的执行/切换是非顺序的,也就意味着我们很难掌控当前的协程是哪一个(事实上可以,但通常没人这么干),所以我们需要在发生协程切换时能够同时切换对应的上下文。

Hyperf 里实现协程的上下文管理将非常简单,基于 Hyperf\Utils\Context 类的 set(string $id, $value)get(string $id, $default = null)has(string $id)override(string $id, \Closure $closure) 静态方法即可完成上下文数据的管理,通过这些方法设置和获取的值,都仅限于当前的协程,在协程结束时,对应的上下文也会自动跟随释放掉,无需手动管理,无需担忧内存泄漏的风险。

Hyperf\Utils\Context::set()

通过调用 set(string $id, $value) 方法储存一个值到当前协程的上下文中,如下:

1
2
3
4
5
6
<?php
use Hyperf\Utils\Context;

// 将 bar 字符串以 foo 为 key 储存到当前协程上下文中
$foo = Context::set('foo', 'bar');
// set 方法会再将 value 作为方法的返回值返回回来,所以 $foo 的值为 bar
Hyperf\Utils\Context::get()

通过调用 get(string $id, $default = null) 方法可从当前协程的上下文中取出一个以 $idkey 储存的值,如不存在则返回 $default ,如下:

1
2
3
4
5
<?php
use Hyperf\Utils\Context;

// 从当前协程上下文中取出 key 为 foo 的值,如不存在则返回 bar 字符串
$foo = Context::get('foo', 'bar');
Hyperf\Utils\Context::has()

通过调用 has(string $id) 方法可判断当前协程的上下文中是否存在以 $idkey 储存的值,如存在则返回 true,不存在则返回 false,如下:

1
2
3
4
5
<?php
use Hyperf\Utils\Context;

// 从当前协程上下文中判断 key 为 foo 的值是否存在
$foo = Context::has('foo');
Hyperf\Utils\Context::override()

Override 方法仅可在 1.0.12 版本或更高版本使用

当我们需要做一些复杂的上下文处理,比如先判断一个 key 是否存在,如果存在则取出 value 来再对 value 进行某些修改,然后再将 value 设置回上下文容器中,此时会有比较繁杂的判断条件,可直接通过调用 override 方法来实现这个逻辑,如下:

1
2
3
4
5
6
7
8
<?php
use Psr\Http\Message\ServerRequestInterface;
use Hyperf\Utils\Context;

// 从协程上下文取出 $request 对象并设置 key 为 foo 的 Header,然后再保存到协程上下文中
$request = Context::override(ServerRequestInterface::class, function (ServerRequestInterface $request) {
return $request->withAddedHeader('foo', 'bar');
});

协程

概念

Hyperf 是运行于 Swoole 4 的协程之上的,这也是 Hyperf 能提供高性能的其中一个很大的因素。

Hyperf 协程
Swoole 协程

配置

安装

1
composer require hyperf/config

配置文件结构

以下结构仅为 Hyperf-Skeleton 所提供的默认配置的情况下的结构,实际情况由于依赖或使用的组件的差异,文件会有差异。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
config
├── autoload // 此文件夹内的配置文件会被配置组件自己加载,并以文件夹内的文件名作为第一个键值
│ ├── amqp.php // 用于管理 AMQP 组件
│ ├── annotations.php // 用于管理注解
│ ├── apollo.php // 用于管理基于 Apollo 实现的配置中心
│ ├── aspects.php // 用于管理 AOP 切面
│ ├── async_queue.php // 用于管理基于 Redis 实现的简易队列服务
│ ├── cache.php // 用于管理缓存组件
│ ├── commands.php // 用于管理自定义命令
│ ├── consul.php // 用于管理 Consul 客户端
│ ├── databases.php // 用于管理数据库客户端
│ ├── devtool.php // 用于管理开发者工具
│ ├── exceptions.php // 用于管理异常处理器
│ ├── listeners.php // 用于管理事件监听者
│ ├── logger.php // 用于管理日志
│ ├── middlewares.php // 用于管理中间件
│ ├── opentracing.php // 用于管理调用链追踪
│ ├── processes.php // 用于管理自定义进程
│ ├── redis.php // 用于管理 Redis 客户端
│ └── server.php // 用于管理 Server 服务
├── config.php // 用于管理用户或框架的配置,如配置相对独立亦可放于 autoload 文件夹内
├── container.php // 负责容器的初始化,作为一个配置文件运行并最终返回一个 Psr\Container\ContainerInterface 对象
├── dependencies.php // 用于管理 DI 的依赖关系和类对应关系
└── routes.php // 用于管理路由
设置配置

只需在 config/config.phpconfig/autoload/server.phpautoload 文件夹内的配置,都能在服务启动时被扫描并注入到 Hyperf\Contract\ConfigInterface 对应的对象中,这个流程是由 Hyperf\Config\ConfigFactoryConfig 对象实例化时完成的。

获取配置

Config 组件提供了三种方式获取配置,

  • 通过 Hyperf\Config\Config 对象获取
1
2
3
4
5
/**
* @var \Hyperf\Contract\ConfigInterface
*/
// 通过 get(string $key, $default): mixed 方法获取 $key 所对应的配置,$key 值可以通过 . 连接符定位到下级数组,$default 则是当对应的值不存在时返回的默认值
$config->get($key,$default);
  • 通过 @Value 注解获取
1
2
3
4
5
6
7
8
9
10
11
12
class IndexController
{
/**
* @Value("config.key")
*/
private $configValue;

public function index()
{
return $this->configValue;
}
}
  • 通过 config(string $key, $default) 函数获取。

在任意地方可以通过 config(string $key, $default) 函数获取对应的配置,但这样的使用方式也就意味着您对 hyperf/confighyperf/utils 组件是强依赖的。

判断配置是否存在
1
2
3
4
5
/**
* @var \Hyperf\Contract\ConfigInterface
*/
// 通过 has(): bool 方法判断对应的 $key 值是否存在于配置中,$key 值可以通过 . 连接符定位到下级数组
$config->has($key);

环境变量

读取环境变量
1
2
3
4
// config/config.php
return [
'app_name' => env('APP_NAME', 'Hyperf Skeleton'),
];

注解

什么是注解? 什么是注释?

在解释注解之前我们需要先定义一下 注解注释 的区别:

  • 注释: 给程序员看,帮助理解代码,对代码起到解释、说明的作用。
  • 注解: 给应用程序看,用于元数据的定义,单独使用时没有任何作用,需配合应用程序对其元数据进行利用才有作用。

注解解析如何实现?

Hyperf 使用了 doctrine/annotations 包来对代码内的注解进行解析,注解必须写在下面示例的标准注释块才能被正确解析,其它格式均不能被正确解析。 注释块示例:

1
2
3
/**
* @AnnotationClass()
*/

注解是如何发挥作用的?

我们有说到注解只是元数据的定义,需配合应用程序才能发挥作用,在 Hyperf 里,注解内的数据会被收集到 Hyperf\Di\Annotation\AnnotationCollector 类供应用程序使用,当然根据您的实际情况,也可以收集到您自定义的类去,随后在这些注解本身希望发挥作用的地方对已收集的注解元数据进行读取和利用,以达到期望的功能实现。

忽略某些注解

我们可以通过在 config/autoload/annotations.php 内将相关注解设置为忽略。

1
2
3
4
5
6
7
8
return [
'scan' => [
// ignore_annotations 数组内的注解都会被注解扫描器忽略
'ignore_annotations' => [
'mixin',
],
],
];

使用注解

注解一共有3种应用对象,分别是 类方法类属性

使用类注解

类注解定义是在 class 关键词上方的注释块内,比如常用的 @Controller@AutoController 就是类注解的使用典范,下面的代码示例则为一个正确使用类注解的示例,表明 @ClassAnnotation 注解应用于 Foo 类。

1
2
3
4
/**
* @ClassAnnotation()
*/
class Foo {}
使用类方法注解

类方法注解定义是在方法上方的注释块内,比如常用的 @RequestMapping 就是类方法注解的使用典范,下面的代码示例则为一个正确使用类方法注解的示例,表明 @MethodAnnotation 注解应用于 Foo::bar() 方法。

1
2
3
4
5
6
7
8
9
10
class Foo
{
/**
* @MethodAnnotation()
*/
public function bar()
{
// some code
}
}
使用类属性注解

类属性注解定义是在属性上方的注释块内,比如常用的 @Value@Inject 就是类属性注解的使用典范,下面的代码示例则为一个正确使用类属性注解的示例,表明 @PropertyAnnotation 注解应用于 Foo 类的 $bar 属性。

1
2
3
4
5
6
7
class Foo
{
/**
* @PropertyAnnotation()
*/
private $bar;
}
注解参数传递
  • 传递主要的单个参数 @DemoAnnotation("value")
  • 传递字符串参数 @DemoAnnotation(key1="value1", key2="value2")
  • 传递数组参数 @DemoAnnotation(key={"value1", "value2"})

自定义注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?php

namespace App\Annotation;

use Hyperf\Di\Annotation\AbstractAnnotation;

/**
* Class Bar
* @package App\Annotation
* @Annotation
* @Target({"METHOD", "PROPERTY"})
*/
class Bar extends AbstractAnnotation
{
// some code
}

/**
* Class Foo
* @package App\Annotation
* @Target("CLASS")
*/
class Foo extends AbstractAnnotation
{
// some code
}

注意注解类的 @Annotation@Target 注解为全局注解,无需 use

其中 @Target 有如下参数:

  • METHOD 注解允许定义在类方法上
  • PROPERTY 注解允许定义在类属性上
  • CLASS 注解允许定义在类上
  • ALL 注解允许定义在任何地方

自定义注解收集器

注解的收集时具体的执行流程也是在注解类内实现的,相关的方法由 Hyperf\Di\Annotation\AnnotationInterface 约束着,该接口类要求了下面 3 个方法的实现,您可以根据自己的需求实现对应的逻辑:

  • public function collectClass(string $className): void;注解定义在类时被扫描时会触发该方法
  • public function collectMethod(string $className, ?string $target): void;注解定义在类方法时被扫描时会触发该方法
  • public function collectProperty(string $className, ?string $target): void;注解定义在类属性时被扫描时会触发该方法

依赖注入

安装

该组件默认存在 hyperf-skeleton 项目中并作为主要组件存在,如希望在其它框架内使用该组件可通过下面的命令安装。

1
composer require hyperf/di

事件机制

事件模式必须基于 PSR-14 去实现。
Hyperf 的事件管理器默认由 hyperf/event 实现,该组件亦可用于其它框架或应用,只需通过 Composer 将该组件引入即可。

1
composer require hyperf/event

事件模式是一种经过了充分测试的可靠机制,是一种非常适用于解耦的机制,分别存在以下 3 种角色:

  • 事件(Event) 是传递于应用代码与 监听器(Listener) 之间的通讯对象
  • 监听器(Listener) 是用于监听 事件(Event) 的发生的监听对象
  • 事件调度器(EventDispatcher) 是用于触发 事件(Event) 和管理 监听器(Listener)事件(Event) 之间的关系的管理者对象

常见问题

Swoole 短名未关闭

[ERROR] Swoole short name have to disable before start server, please set swoole.use_shortname = ‘Off’ into your php.ini.

1
2
3
4
5
6
7
// 这些都是错误的,注意 `大小写``引号`
swoole.use_shortname = 'off' // 经测试这样也可以
swoole.use_shortname = off
swoole.use_shortname = Off

// 下面的才是正确的
swoole.use_shortname = 'Off'

不能通过全局变量获取属性参数

PHP-FPM 下可以通过全局变量获取到请求的参数,服务器的参数等,在 HyperfSwoole 内,都 无法 通过 $_GET/$_POST/$_REQUEST/$_SESSION/$_COOKIE/$_SERVER$_开头的变量获取到任何属性参数。

参考

Powered by Hexo and Hexo-theme-hiker

Copyright © 2017 - 2023 Keep It Simple And Stupid All Rights Reserved.

访客数 : | 访问量 :