hyperf官网学习
swoole.use_shortname = Off
安装 Hyperf
基于 Swoole 驱动:
composer create-project hyperf/hyperf-skeleton |
运行
/www/server/php/73/bin/php bin/hyperf.php start |
路由
通过配置文件定义路由
Router::addRoute(['GET', 'POST', 'HEAD'], '/channel', 'App\Controller\IndexController@channel'); |
通过@AutoController()注解形式定义路由
<?php declare(strict_types=1); namespace App\Controller; use Hyperf\HttpServer\Contract\RequestInterface; use Hyperf\HttpServer\Annotation\AutoController; /** * @AutoController() */ class UserController { // Hyperf 会自动为此方法生成一个 /user/index 的路由,允许通过 GET 或 POST 方式请求 public function index(RequestInterface $request) { // 从请求中获得 id 参数 $id = $request->input('id', 1); return (string)$id; } } |
IoC与DI
IoC,即控制反转,把对象的调用权交给容器,通过容器来实现对象的装配和管理
DI,即依赖注入,对象之间依赖关系由容器在运行期决定,由容器动态的将依赖关系注入到对象之中
DI是对Ioc更完善的描述
谁依赖谁?对象实例化依赖容器
为什么要依赖?对象实例化通过容器自动得到外部依赖
谁注入谁?容器注入对象的依赖到对象中
注入了什么?注入了对象的外部依赖
Hyperf的依赖注入
由hyperf/di组件提供功能支持
更符合长生命周期的应用使用
提供了注解、注解注入、AOP
基于PSR-11实现,可独立应用于其他框架
hyperf的依赖注入
注入方式
通过构造方法注入
通过@Inject注解注入
注入类型
简单对象注入
抽象对象注入
工厂对象注入
抽象对象注入
在对应的位置进行接口类与实现类的关系绑定
config/autoload/dependencies.php
对注解和DI的总结
PHP语法上没有支持注解,只能基于约束好的规定从注释上解析
注解只是元数据定义,实现功能时不利用这些数据的话没有任何作用
使用了注解的对象必须基于Hyperf的DI容器来创建对象才能生效
注解可以用在类、类方法、类成员属性上
DI容器是负责管理对象的创建和对象的依赖管理的
DI容器创建出来的对象是个单例,是长生命周期对象
通过$container->make()方法或make()函数创建短生命周期对象
通过new来实例化的对象注解不会生效,依赖需自行管理
不传参
array(1) {
[“value”]=>
string(3) “123”
}
传参
array(1) {
[“value”]=>
string(3) “123”
}
定义
<?php namespace App\Annotation; use Doctrine\Common\Annotations\Annotation; use Hyperf\Di\Annotation\AbstractAnnotation; /** * @Annotation * @Target({"CLASS","METHOD"}) */ class Foo extends AbstractAnnotation { /** * @var string */ public $bar; public function __construct($value = null) { var_dump($value); parent::__construct($value); } } |
调用
<?php namespace App\Controller; use App\Annotation\Foo; use Hyperf\Di\Annotation\AnnotationCollector; use Hyperf\HttpServer\Annotation\AutoController; /** * @AutoController() * @Foo(bar="123") */ class AnnotationDemoController { public function index() { // array(1) { // ["App\Controller\AnnotationDemoController"]=> // object(App\Annotation\Foo)#1733 (1) { // ["bar"]=> // string(3) "123" // } // } var_dump(AnnotationCollector::getClassByAnnotation(Foo::class)); return 1; } } |
AOP是什么?
面向切面编程,通过预编译方式或运行期动态代理实现程序功能的统一维护的一种技术
AOP在字面上与OOP很相似,但设计思想在目标上有着本质的差异
OOP是针对业务处理过程的实体与其属性和行为进行抽象封装,以获得更加清晰高效的逻辑单元划分
而AOP则是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果
Hyperf里的AOP
只需要理解一个概念即可,即Aspect(切面)
Aspect可以切入任意类或注解类
被切入的类必须由DI管理
相对于其他框架实现的AOP功能的使用方式,我们进一步简化了AOP的使用,不做过细的划分(如Before,After,AfterReturning,AfterThrowing),仅存在环绕(Around)一种通用形式
Hyperf的AOP是基于DI实现的
必须使用的是Hyperf的DI组件,即hyperf/di
必须通过DI创建的对象才能是AOP生效,直接new不行
必须当代理类缓存文件不存在时才会重新生成代理类
AOP应用场景
参数校验、日志、无侵入埋点、安全控制、性能统计、事务处理、异常处理、缓存、无侵入监控、资源池、连接池管理等
/hyperf-skeleton/runtime/container/proxy
思考:DI仅管理长生命周期的对象,即单例,且协程下,一个进程会同一时间周期内处理多个请求,下图的使用方式会带来什么问题呢?
class IndexController extends AbstractController { /** * @Inject * @var RequestInterface */ private $request; private $id; public function index(){ $this->id=$this->request->input('id'); $return $id; } } |
思考:
如何实现 $this->request这样的调用方式,而不会导致协程间的数据混淆?
文档上明确写了不允许这样储存状态值,为什么hyperf的Request和Response可以?
/** * @AutoController() */ class DemoController extends AbstractController { public $id = 1; public function get(){ return $this->id; } public function update(RequestInterface $request){ $this->id = $request->input('id'); return $this->id; } } |
/demo/get
/demo/update?id=3 会影响id变量
① 存储到协程上下文 解决请求变量冲突
public function get(){ return Context::get('id','null'); } public function update(RequestInterface $request){ $id = $request->input('id'); Context::set('id',$id); return Context::get('id'); } |
② 解决方式2
public function get() { return $this->id; } public function update(RequestInterface $request) { $this->id = $request->input('id'); return $this->id; } public function __get($name) { return Context::get(__CLASS__ . $name); } public function __set($name, $value) { Context::set(__CLASS__ . $name, $value); } |
③ 注入类数据混淆问题
<?php namespace App; class Foo { public $id; } /** * @Inject * @var Foo */ public $foo; public function get() { return $this->foo->id; } public function update(RequestInterface $request) { $this->foo->id = $request->input('id'); return $this->foo->id; } |
/demo/update?id=2
/demo/get
<?php namespace App\Controller; use Hyperf\HttpServer\Annotation\AutoController; use Hyperf\HttpServer\Contract\RequestInterface; use Hyperf\Utils\Parallel; use Hyperf\Utils\WaitGroup; use Swoole\Coroutine\Channel; use Hyperf\Guzzle\ClientFactory; use Hyperf\Di\Annotation\Inject; /** * @AutoController() */ class CoController { /** * @Inject * @var ClientFactory */ public $clientFactory; public function sleep(RequestInterface $request) { $seconds = $request->query('seconds', 20); sleep($seconds); return $seconds; } public function test(){ $channel = new Channel(); var_dump(1); co(function() use ($channel){ $client = $this->clientFactory->create(); var_dump(2); $client->get('127.0.0.1:9501/co/sleep?seconds=2'); var_dump(3); $channel->push(123); }); var_dump(4); co(function() use ($channel){ $client = $this->clientFactory->create(); var_dump(5); $client->get('127.0.0.1:9501/co/sleep?seconds=2'); var_dump(6); $channel->push(321); }); var_dump(7); $result[] = $channel->pop(); var_dump(8); $result[] = $channel->pop(); var_dump(9); // 访问/co/test输出如下: // int(1) // int(2) // int(4) // int(5) // int(7) // int(3) // int(8) // int(6) // int(9) return $result; } public function test2(){ $wg = new WaitGroup(); $result = []; // waitGroup多少个协程 $wg->add(2); co(function() use ($wg,&$result){ $client = $this->clientFactory->create(); $client->get('127.0.0.1:9501/co/sleep?seconds=2'); $result[] = 123; $wg->done(); }); co(function() use ($wg,&$result){ $client = $this->clientFactory->create(); $client->get('127.0.0.1:9501/co/sleep?seconds=2'); $result[] = 321; $wg->done(); }); // 等待子协程执行完毕 接着往下跑 $wg->wait(); return $result; } public function test3(){ $parallel = new Parallel(); $parallel->add(function(){ $client = $this->clientFactory->create(); $client->get('127.0.0.1:9501/co/sleep?seconds=2'); return 123; },'key1'); $parallel->add(function(){ $client = $this->clientFactory->create(); $client->get('127.0.0.1:9501/co/sleep?seconds=2'); return 321; },'key2'); $result = $parallel->wait(); return $result; } } |
中间件
命令创建中间件
/www/server/php/73/bin/php bin/hyperf.php gen:middleware ***Middleware(中间件名字)
/www/server/php/73/bin/php bin/hyperf.php gen:middleware FooMiddleware
中间件示例
class BarMiddleware implements MiddlewareInterface { /** * @var ContainerInterface */ protected $container; public function __construct(ContainerInterface $container) { $this->container = $container; } public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { echo 1; $response = $handler->handle($request); echo 4; return $response; } } |
class BazMiddleware implements MiddlewareInterface { /** * @var ContainerInterface */ protected $container; public function __construct(ContainerInterface $container) { $this->container = $container; } public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { echo 2; $response = $handler->handle($request); echo 5; return $response; } } |
class FooMiddleware implements MiddlewareInterface { /** * @var ContainerInterface */ protected $container; public function __construct(ContainerInterface $container) { $this->container = $container; } public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { echo 3; $response = $handler->handle($request); echo 6; return $response; } } |
<?php namespace App\Controller; use Hyperf\HttpServer\Annotation\AutoController; use Hyperf\HttpServer\Annotation\Middleware; use Hyperf\HttpServer\Annotation\Middlewares; use App\Middleware\BarMiddleware; use App\Middleware\BazMiddleware; use App\Middleware\FooMiddleware; /** * @AutoController * @Middlewares( * @Middleware(BarMiddleware::class), * @Middleware(BazMiddleware::class), * @Middleware(FooMiddleware::class), * ) */ class MiddlewareController { public function index(){ // return 'index'; } } |
控制台依次输出 123654
中间件获取响应内容的body并且修改
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { // echo 1; // $response = $handler->handle($request); // echo 4; // return $response; echo 1; $response = $handler->handle($request); $body = $response->getBody()->getContents(); return $response->withBody(new SwooleStream($body.'Bar')); } |
JSONRPC
指定php版本和composer安装服务项目
/www/server/php/73/bin/php /usr/bin/composer create-project hyperf/hyperf-skeleton provider
Which RPC protocol do you want to use ? [1] JSON RPC with Service Governance [2] JSON RPC [3] gRPC [n] None of the above |
选择 [1] JSON RPC with Service Governance 安装
复制消费者
cp -r provider consumer
启动消费者服务报错
PHP Fatal error: Interface ‘Hyperf\Signal\SignalHandlerInterface’ not found in /www/wwwroot/hyperf.wuxinzhe/hyperf-skeleton/grpc/consumer/vendor/hyperf/process/src/Handler/ProcessStopHandler.php on line 17
解决方案
/www/server/php/73/bin/php /usr/bin/composer require hyperf/signal /www/server/php/73/bin/php /usr/bin/composer require symfony/serializer |
参考:https://hyperf.wiki/2.2/#/zh-cn/quick-start/questions?id=php-73-%e7%89%88%e6%9c%ac%e5%af%b9-di-%e7%9a%84%e5%85%bc%e5%ae%b9%e6%80%a7%e6%9c%89%e6%89%80%e4%b8%8b%e9%99%8d
定义服务者
服务者接口类 provider/app/Rpc/CaculateServiceInterface.php
<?php namespace App\Rpc; Interface CaculateServiceInterface { public function add(int $a,int $b):int; public function minus(int $a,int $b):int; } |
服务者实现类 provider\app\Rpc\CaculateService.php
<?php namespace App\Rpc; use Hyperf\RpcServer\Annotation\RpcService; /** * @RpcService(name="CaculateService",protocol="jsonrpc-http",server="jsonrpc-http") * 定义服务提供者 注意,如希望通过服务中心来管理服务,需在注解内增加 publishTo 属性 */ class CaculateService implements CaculateServiceInterface { public function add(int $a, int $b): int { return $a + $b; } public function minus(int $a, int $b): int { return $a - $b; } } |
provider/config/autoload/server.php
'servers' => [ [ 'name' => 'http', 'type' => Server::SERVER_HTTP, 'host' => '0.0.0.0', 'port' => 9501, 'sock_type' => SWOOLE_SOCK_TCP, 'callbacks' => [ Event::ON_REQUEST => [Hyperf\HttpServer\Server::class, 'onRequest'], ], ], // 定义 JSON RPC Server HTTP Server (适配 jsonrpc-http 协议) [ 'name' => 'jsonrpc-http', 'type' => Server::SERVER_HTTP, 'host' => '0.0.0.0', 'port' => 9502, 'sock_type' => SWOOLE_SOCK_TCP, 'callbacks' => [ Event::ON_REQUEST => [\Hyperf\JsonRpc\HttpServer::class, 'onRequest'], ], ], ] |
定义消费者
接口类同 consumer/app/Rpc/CaculateServiceInterface.php
consumer/config/autoload/services.php
<?php use App\Rpc\CaculateServiceInterface; return [ // 一个 服务消费者(ServiceConsumer) 可以理解为就是一个客户端类 'consumers' => [ [ // 需要与服务提供者的 name 属性相同 'name' => 'CaculateService', 'service' => CaculateServiceInterface::class, // 如果没有指定上面的 registry 配置,即为直接对指定的节点进行消费,通过下面的 nodes 参数来配置服务提供者的节点信息 'nodes' => [ ['host' => '127.0.0.1','port' => 9502] ] ] ] ]; |
使用服务
<?php declare(strict_types=1); namespace App\Controller; use Hyperf\Di\Annotation\Inject; use App\Rpc\CaculateServiceInterface; class IndexController extends AbstractController { /** * @Inject() * @var CaculateServiceInterface */ protected $caculateService; public function index() { //return $this->caculateService->add(1,2); return $this->caculateService->minus(1,2); } } |
centos consul安装
官网地址 https://www.consul.io/downloads
centos安装consul命令
sudo yum install -y yum-utils sudo yum-config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo sudo yum -y install consul |
启动consul服务
consul agent -dev -client 0.0.0.0 -ui |
成功后可以通过http://127.0.0.1:8500访问
使用consul注册管理服务
服务者和消费者 安装服务治理组件
/www/server/php/73/bin/php /usr/bin/composer require hyperf/service-governance-consul
服务类
<?php namespace App\Rpc; use Hyperf\RpcServer\Annotation\RpcService; /** * @RpcService(name="CaculateService",protocol="jsonrpc-http",server="jsonrpc-http",publishTo="consul") * 定义服务提供者 注意,如希望通过服务中心来管理服务,需在注解内增加 publishTo 属性 */ class CaculateService implements CaculateServiceInterface { public function add(int $a, int $b): int { return $a + $b; } public function minus(int $a, int $b): int { return $a - $b; } } |
provider/config/autoload/services.php
<?php return [ 'enable' => [ 'discovery' => true, 'register' => true, ], 'consumers' => [], 'providers' => [], 'drivers' => [ 'consul' => [ 'uri' => 'http://127.0.0.1:8500', 'token' => '', ] ], ]; |
\consumer\config\autoload\services.php配置拉取服务节点信息
// 这个消费者要从哪个服务中心获取节点信息,如不配置则不会从服务中心获取节点信息 'registry' => [ 'protocol' => 'consul', 'address' => 'http://127.0.0.1:8500', ], |
请求报错
[ERROR] Invalid protocol of registry consul[197] in /www/wwwroot/hyperf.wuxinzhe/hyperf-skeleton/grpc/consumer/vendor/hyperf/rpc-client/src/AbstractServiceClient.php
消费端重新安装hyperf/service-governance-consul
/www/server/php/73/bin/php /usr/bin/composer require hyperf/service-governance-consul |
事件机制
生成监听文件 /hyperf-skeleton/app/Listener
/www/server/php/73/bin/php bin/hyperf.php gen:listener SendSmsListener
模型
生成数据表对应模型命令
/www/server/php/73/bin/php bin/hyperf.php gen:model