目标
上手,打通通用功能使用障碍,swoole相关错误调试。
1、课程学习
《黄朝晖:Hyperf从入门到精通系列》
php知识点:
__invoke(): 类被函数式调用时触发执行
避免cli编程的内存泄漏
普通函数请求和响应对象:
public function index(RequestInterface $request, ResponseInterface $response){}
实际实例化的对象:
HyperfHttpServerRequest
-- Context::get(ServerRequestInterface::class)
这些对象存于协程上下文资源,即时释放。注意公共对象资源的修改!
a.依赖注入
简单注入
@AutoController 的路由信息注入
@inject 的简单对象注入 [ == 通过构造方法注入]
高级注入
# 抽象对象注入
由于 Hyperf\HttpServer\ConfigProvider.dependencies [
RequestInterface::class => Request::class
ResponseInterface::class => Response::class
]
在控制器接收方法里,直接使用抽象对象即依赖自实例对象
b.切片编程
AOP(Aspect Oriented Programming):面向切片编程
注解
App\Annotation\FooAnnotation.php
/**
* @Annotation
* @Target({"CLASS","METHOD","PROPERTY"})
*/
class FooAnnotation extends AbstractAnnotation{}
-- 添加到注解树 @Annotation
-把类路径、方法、属性添加到注解树
注解是添作使用类(当前类)的一部分
切片
App\Aspect\FooSpect.php
/**
* @Aspect
*/
class FooSpect extends AbstractAspect{
//定义切入类
public $classes = [];
/** 注解引入、重写切片“环绕”处理方法
* @param ProceedingJoinPoint $proceedingJoinPoint
* @return mixed|void
*/
public function process(ProceedingJoinPoint $proceedingJoinPoint){}
}
-- 添加切片类、方法 [注解方式] 到切面树
-重写定义切入类 $classes
-ProceedingJoinPoint $proceedingJoinPoint
控制器
App\Controller\FooController.php
/**
* @AutoController()
* @FooAnnotation(bar="123", calc=11)
*/
class FooController{}
--修改注解属性 @FooAnnotation(bar="123", calc=11)
curl -v http://127.0.0.1:9501/foo/index 或 test
c.协程
震惊:parallel():2个闭包函数的协程!!
namespace App\Controller;
use Hyperf\Di\Annotation\Inject;
use Hyperf\Guzzle\ClientFactory;
use Hyperf\HttpServer\Annotation\AutoController;
use Hyperf\HttpServer\Contract\RequestInterface;
class CoroutineController
{
/**
* @Inject()
* @var ClientFactory
*/
private $clientFactory;
public function sleep(RequestInterface $request)
{
$sec = $request->query('second',1);
sleep($sec);
return $sec;
}
/** Parallel 特性
* 便捷版 WaitGroup
* @return array
*/
public function testCo33()
{
$time = (float) time() + floatval(microtime());
$result = parallel([
function () {
$client = $this->clientFactory->create();
$client->get('127.0.0.1:9501/Coroutine/sleep?second=1');
return '123: '. \Hyperf\Utils\Coroutine::id();
},
function () {
$client = $this->clientFactory->create();
$client->get('127.0.0.1:9501/Coroutine/sleep?second=2');
return '321: '. \Hyperf\Utils\Coroutine::id();
}
]);
return [__FUNCTION__.' ok: '. round((float) time() + floatval(microtime()) - $time,4), $result];
}
/** Concurrent 协程运行控制
* 高级控制版 WaitGroup
* @return array
*/
public function testCo9()
{
$time = (float) time() + floatval(microtime());
$concurrent = new \Hyperf\Utils\Coroutine\Concurrent(5);
$result = [];
for ($i = 0; $i < 15; ++$i) {
$concurrent->create(function () use ($i, &$result) {
$client = $this->clientFactory->create();
$client->get('127.0.0.1:9501/Coroutine/sleep?second='. ($i%5+1));
$result[] = [$i. ': '. \Hyperf\Utils\Coroutine::id()];
return 1;
});
}
//由于并行机制 句柄移交的原因, 这里result结果输出数是 15-5=12 个
return [__FUNCTION__.' ok: '. round((float) time() + floatval(microtime()) - $time,4), $result];
}
}
本文实例:[CoroutineController.php]
d.中间件
与切片同理,
/* 添加中间件多个、一个
* @Middlewares({
* @Middleware(Test1MIddleware::class),
* @Middleware(Test2Middleware::class)
* })
*/
class MidController{}
修改传值注意:保存到 Context会话对象
。
Hyperf\Utils\Context::set(ServerRequestInterface::class, $request->withAttribute()...)
App\Controller\MidController.php
/** curl -v http://127.0.0.1:9501/mid/index
* b. 方法中间件
* @Middleware(FooMiddleware::class)
*/
public function index(RequestInterface $request){}
App\Middleware\FooMiddleware.php
class FooMiddleware implements MiddlewareInterface
{
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
// $request 和 $response 为修改后的对象
$request = Context::set(ServerRequestInterface::class, $request->withAttribute('foo', 'test2 set into value a...'));
$response = $handler->handle($request);
$body = $response->getBody()->getContents();
echo __CLASS__ .__LINE__.PHP_EOL;
return $response->withBody(new SwooleStream($body. PHP_EOL. ' in func see Foo deal.'));
}
}
执行顺序是:先入、先执行、后出。
e.RPC调用
建立项目
composer create-project hyperf/hyperf-skeleton rpc-server
cp rpc-server rpc-client -r
业务处理
[文件夹 rpc-server]
创建服务
App\Rpc\CalculatorService.php:
use Hyperf\RpcServer\Annotation\RpcService;
/**
* @RpcService(name="CalculatorService", protocol="jsonrpc-http", server="rpc", publishTo="consul")
*/
class CalculatorService implements CalculatorServiceInterface
{
public function add(int $a, int $b): int
{
return $a + $b;
}
public function minus(int $a, int $b): int
{
return $a - $b;
}
}
@RpcService 引用RpcService类
这里protocol="jsonrpc-http/tcp", server配置在config/autoload/server.php的servers中,publishTo="consul"注册到consul。
在编辑器中右键,Refactor导出接口CalculatorServiceInterface.php[本例到相同目录]。
配置主机端口
server.php:
"servers"=> #添加或替换
[
'name' => 'rpc', //与CalculatorService.php:server="rpc"相同
'type' => Server::SERVER_HTTP,
'host' => '0.0.0.0',
'port' => 9600,
'sock_type' => SWOOLE_SOCK_TCP,
'callbacks' => [
SwooleEvent::ON_REQUEST => [\Hyperf\JsonRpc\HttpServer::class, 'onRequest'],
],
],
添加接口实例化依赖
添加依赖后,接口的对象自动实例化
config/autoload/dependencies.php:
/**
* 接口的实体化依赖
*/
App\Rpc\CalculatorServiceInterface::class => App\Rpc\CalculatorService::class
业务处理
[文件夹 rpc-client]
接口文件同样放到 appRpc 下
class CalcController extends AbstractController
{
/**
* @Inject()
* @var CalculatorServiceInterface
*/
private $calcService;
public function add(){ return $this->calcService->add(12, 56); }
public function minus(){ return $this->calcService->minus(23, 78); }
}
添加 config/autoload/services.php 参考官方添加:
'consumers'=>[
'name' => 'CalculatorService',
'service' => \App\Rpc\CalculatorServiceInterface::class,
'registry' => [
'protocol' => 'consul',
'address' => 'http://172.10.1.11:8500', //服务群端主节点
],
'nodes' => [
['host' => '172.10.1.22', 'port' => 8500], //备用:客户群群端节点
],
]
启动验证
php rpc-server/bin/hyperf.php start [9600,发布到8500]
php rpc-client/bin/hyperf.php start [9501,注册到8500]
curl -v http://127.0.0.1:9501/calc/add //和minus
f.事件监听
命令行生成模板文件:
php bin/hyperf.php list
php bin/hyperf.php gen:listener SendSmsListener
定义服务
/**
* @Inject()
* @var EventDispatcherInterface
*/
private $eventDispatcher; //引入事件监听分发类
//用户注册之前
$beforeRegister = new BeforeRegister();
$this->eventDispatcher->dispatch($beforeRegister);
if($beforeRegister->shouldRegister){
//注册用户
$userId = rand(1,99999);
}
//注册成功后
if($userId){
$this->eventDispatcher->dispatch(new UserRegistered($userId));
}
添加监听服务
Listener:
/** 权重默认是1
* @Listener(priority=2)
*/
class SendSmsListener implements ListenerInterface
{
public function listen(): array
{
return [
UserRegistered::class
];
}
/**
* @param UserRegistered $event
*/
public function process(object $event)
{
echo '发送短信给'. $event->userId .PHP_EOL;
}
}
class VaildRegisterListener implements ListenerInterface
{
public function listen(): array
{
return [
BeforeRegister::class
];
}
/**
* @param BeforeRegister $event
*/
public function process(object $event)
{
$event->shouldRegister = (bool) rand(0,2);
echo '注册身份验证'. ($event->shouldRegister ? '通过' : '失败') .PHP_EOL;
}
}
Event:
class UserRegistered
{
public $userId;
public function __construct(int $userId) { $this->userId = $userId; }
}
class BeforeRegister
{
public $shouldRegister = false;
}
Controller:
class ListenController
{
/**
* @Inject()
* @var UserService
*/
public $userService;
public function test()
{
return $this->userService->register();
}
}
实例代码上传:
https://github.com/cffycls/cl...
2、应用点
a.文件上传:GuzzleClient客户端
文件上传、接收,[这里文件类型判断需要启用fileinfo扩展]:
客户端
$body = [
'multipart' =>
[
[
'name' => 'data',
'contents' => '{"field_1":"Test","field_2":"Test","field_3":"Test"}',
'headers' =>
[
'Content-Type' => 'application/json',
],
],
[
'name' => 'file',
'filename' => 'README.md',
'Mime-Type' => 'application/text',
'contents' => file_get_contents('./README.md'),
]
]
];
$res = (new GuzzleHttp\Client())->request('POST', 'http://127.0.0.1:9501/guzzle_client/write', $body);
服务端:
$files = $this->request->getUploadedFiles();
//var_dump($files);
foreach ($files as $f => $fileObj){
//2者等效,同一对象
$file = $this->request->file($f);
var_dump($file->getMimeType());
$fileInfo = $file->toArray();
var_dump($fileInfo);
echo '以下是接收的文件内容: '. PHP_EOL;
var_dump( file_get_contents($fileInfo['tmp_file']) );
if(file_exists('/tmp/README.md.tmp')){
echo '文件已存在: '. PHP_EOL;
}else{
$file->moveTo('/tmp/README.md.tmp'); //保存文件
// 通过 isMoved(): bool 方法判断方法是否已移动
if ($file->isMoved()) {
echo $fileInfo['name'] .'文件已上传 '. PHP_EOL;
unlink('/tmp/README.md.tmp');
return $fileInfo;
}
}
}
b.组件列表
路由,事件,日志,命令,数据库,依赖注入容器,服务,客户端,消息队列,配置中心,RPC,服务治理,定时任务,ID 生成器,文档生成,Graphql,热更新/热重载,Swoole,开发调试,权限认证,第三方 SDK
移步官网 组件列表
小结
hyperf功能全面,文档齐全、降低了上手门槛。
代码上传:
https://github.com/cffycls/default/tree/master/hyperf
https://github.com/cffycls/cluster/tree/master/html/rpc-server
https://github.com/cffycls/cluster/tree/master/html/rpc-client