mirror of
https://gitee.com/hyperf/hyperf.git
synced 2024-12-02 19:58:22 +08:00
Merge branch 'master' into pr/1137
This commit is contained in:
commit
ab0f17bbf5
@ -5,13 +5,13 @@ sudo: required
|
||||
matrix:
|
||||
include:
|
||||
- php: 7.2
|
||||
env: SW_VERSION="4.4.12"
|
||||
env: SW_VERSION="4.4.14"
|
||||
- php: 7.3
|
||||
env: SW_VERSION="4.4.12"
|
||||
env: SW_VERSION="4.4.14"
|
||||
- php: 7.4
|
||||
env: SW_VERSION="4.4.12"
|
||||
env: SW_VERSION="4.4.14"
|
||||
- php: master
|
||||
env: SW_VERSION="4.4.12"
|
||||
env: SW_VERSION="4.4.14"
|
||||
|
||||
allow_failures:
|
||||
- php: master
|
||||
|
@ -5,6 +5,6 @@ tar -xf swoole.tar.gz -C swoole --strip-components=1
|
||||
rm swoole.tar.gz
|
||||
cd swoole
|
||||
phpize
|
||||
./configure --enable-openssl --enable-mysqlnd
|
||||
./configure --enable-openssl --enable-mysqlnd --enable-http2
|
||||
make -j$(nproc)
|
||||
make install
|
||||
|
43
CHANGELOG.md
43
CHANGELOG.md
@ -1,4 +1,45 @@
|
||||
# v1.1.11 - TBD
|
||||
# v1.1.13 - TBD
|
||||
|
||||
## Added
|
||||
|
||||
- [#1165](https://github.com/hyperf/hyperf/pull/1165) Added a method `route` for `Hyperf\HttpServer\Contract\RequestInterface`.
|
||||
- [#1195](https://github.com/hyperf/hyperf/pull/1195) Added max offset for `Cacheable` and `CachePut`.
|
||||
- [#1216](https://github.com/hyperf/hyperf/pull/1216) Added default value for `$data` of `RenderInterface::render()`.
|
||||
|
||||
## Fixed
|
||||
|
||||
- [#1175](https://github.com/hyperf/hyperf/pull/1175) Fixed `Hyperf\Utils\Collection::random` does not works when the number is null.
|
||||
- [#1200](https://github.com/hyperf/hyperf/pull/1200) Request path shouldn't include query parameters in hyperf/metric middleware.
|
||||
- [#1210](https://github.com/hyperf/hyperf/pull/1210) Fixed validation `size` does not works without `numeric` or `integer` rules when the type of value is numeric.
|
||||
|
||||
# v1.1.12 - 2019-12-26
|
||||
|
||||
## Added
|
||||
|
||||
- [#1177](https://github.com/hyperf/hyperf/pull/1177) Added protocol `jsonrpc-tcp-length-check` for `jsonrpc`.
|
||||
|
||||
## Fixed
|
||||
|
||||
- [#1175](https://github.com/hyperf/hyperf/pull/1175) Fixed `Hyperf\Utils\Collection::random` does not works when the number is null.
|
||||
- [#1178](https://github.com/hyperf/hyperf/pull/1178) Fixed `Hyperf\Database\Query\Builder::chunkById` does not works when the collection item is array.
|
||||
- [#1189](https://github.com/hyperf/hyperf/pull/1189) Fixed default operator does not works for `Hyperf\Utils\Collection::operatorForWhere`.
|
||||
|
||||
## Optimized
|
||||
|
||||
- [#1186](https://github.com/hyperf/hyperf/pull/1186) Automatically added default constructor's configuration, when you forgetton to set it.
|
||||
|
||||
# v1.1.11 - 2019-12-19
|
||||
|
||||
## Added
|
||||
|
||||
- [#849](https://github.com/hyperf/hyperf/pull/849) Added configuration of span tag for `tracer` component.
|
||||
|
||||
## Fixed
|
||||
|
||||
- [#1142](https://github.com/hyperf/hyperf/pull/1142) Fixed bug that Register::resolveConnection will return null.
|
||||
- [#1144](https://github.com/hyperf/hyperf/pull/1144) Fixed rate-limit config does not works.
|
||||
- [#1145](https://github.com/hyperf/hyperf/pull/1145) Fixed error return value for method `CoroutineMemoryDriver::delKey`.
|
||||
- [#1153](https://github.com/hyperf/hyperf/pull/1153) Fixed validation rule `alpha_num` does not works.
|
||||
|
||||
# v1.1.10 - 2019-12-12
|
||||
|
||||
|
@ -10,7 +10,7 @@
|
||||
|
||||
Hyperf 是基于 `Swoole 4.4+` 实现的高性能、高灵活性的 PHP 协程框架,内置协程服务器及大量常用的组件,性能较传统基于 `PHP-FPM` 的框架有质的提升,提供超高性能的同时,也保持着极其灵活的可扩展性,标准组件均基于 [PSR 标准](https://www.php-fig.org/psr) 实现,基于强大的依赖注入设计,保证了绝大部分组件或类都是 `可替换` 与 `可复用` 的。
|
||||
|
||||
框架组件库除了常见的协程版的 `MySQL 客户端`、`Redis 客户端`,还为您准备了协程版的 `Eloquent ORM`、`WebSocket 服务端及客户端`、`JSON RPC 服务端及客户端`、`GRPC 服务端及客户端`、`Zipkin/Jaeger (OpenTracing) 客户端`、`Guzzle HTTP 客户端`、`Elasticsearch 客户端`、`Consul 客户端`、`ETCD 客户端`、`AMQP 组件`、`Apollo 配置中心`、`阿里云 ACM 应用配置管理`、`ETCD 配置中心`、`基于令牌桶算法的限流器`、`通用连接池`、`熔断器`、`Swagger 文档生成`、`Swoole Tracker`、`Blade 、 Smarty 、 Twig 、 Plates 和 ThinkTemplate 视图引擎`、`Snowflake 全局ID生成器` 等组件,省去了自己实现对应协程版本的麻烦。
|
||||
框架组件库除了常见的协程版的 `MySQL 客户端`、`Redis 客户端`,还为您准备了协程版的 `Eloquent ORM`、`WebSocket 服务端及客户端`、`JSON RPC 服务端及客户端`、`GRPC 服务端及客户端`、`Zipkin/Jaeger (OpenTracing) 客户端`、`Guzzle HTTP 客户端`、`Elasticsearch 客户端`、`Consul 客户端`、`ETCD 客户端`、`AMQP 组件`、`Apollo 配置中心`、`阿里云 ACM 应用配置管理`、`ETCD 配置中心`、`基于令牌桶算法的限流器`、`通用连接池`、`熔断器`、`Swagger 文档生成`、`Swoole Tracker`、`视图引擎`、`Snowflake 全局 ID 生成器` 等组件,省去了自己实现对应协程版本的麻烦。
|
||||
|
||||
Hyperf 还提供了 `基于 PSR-11 的依赖注入容器`、`注解`、`AOP 面向切面编程`、`基于 PSR-15 的中间件`、`自定义进程`、`基于 PSR-14 的事件管理器`、`Redis/RabbitMQ 消息队列`、`自动模型缓存`、`基于 PSR-16 的缓存`、`Crontab 秒级定时任务`、`Translation 国际化`、`Validation 验证器` 等非常便捷的功能,满足丰富的技术场景和业务场景,开箱即用。
|
||||
|
||||
@ -29,7 +29,7 @@ Hyperf 还提供了 `基于 PSR-11 的依赖注入容器`、`注解`、`AOP 面
|
||||
|
||||
# 生产可用
|
||||
|
||||
我们为组件进行了大量的单元测试以保证逻辑的正确,目前存在 `1120` 个单测共 `3369` 个断言条件,同时维护了高质量的文档,在 Hyperf 正式对外开放(2019年6月20日)之前,便已经过了严酷的生产环境的考验,我们才正式的对外开放该项目,现在已有很多的大型互联网企业都已将 Hyperf 部署到了自己的生产环境上并稳定运行。
|
||||
我们为组件进行了大量的单元测试以保证逻辑的正确,目前存在 `1120` 个单测共 `3369` 个断言条件,同时维护了高质量的文档,在 Hyperf 正式对外开放(2019年6月20日)之前,便已经过了严酷的生产环境的考验,我们才正式的对外开放该项目,现在已有很多的大型互联网企业将 Hyperf 部署到了自己的生产环境上并稳定运行。
|
||||
|
||||
# 运行环境
|
||||
|
||||
|
@ -237,6 +237,7 @@
|
||||
"HyperfTest\\Pool\\": "src/pool/tests/",
|
||||
"HyperfTest\\Process\\": "src/process/tests/",
|
||||
"HyperfTest\\Protocol\\": "src/protocol/tests/",
|
||||
"HyperfTest\\RateLimit\\": "src/rate-limit/tests/",
|
||||
"HyperfTest\\Redis\\": "src/redis/tests/",
|
||||
"HyperfTest\\Retry\\": "src/retry/tests/",
|
||||
"HyperfTest\\Rpc\\": "src/rpc/tests/",
|
||||
|
@ -100,6 +100,7 @@ use App\Middleware\BarMiddleware;
|
||||
use App\Middleware\FooMiddleware;
|
||||
use Hyperf\HttpServer\Annotation\AutoController;
|
||||
use Hyperf\HttpServer\Annotation\Middleware;
|
||||
use Hyperf\HttpServer\Annotation\Middlewares;
|
||||
|
||||
/**
|
||||
* @AutoController()
|
||||
|
@ -101,7 +101,7 @@
|
||||
|
||||
## 热更新/热重载
|
||||
|
||||
- [ha-ni-cc/hyperf-watch](https://github.com/ha-ni-cc/hyperf-watch) 一个基于 fswatch 实现的通用热更新组件
|
||||
- [ha-ni-cc/hyperf-watch](https://github.com/ha-ni-cc/hyperf-watch) 一个基于 Swoole 实现的通用热更新组件
|
||||
- [mix-php/swoolefor](https://github.com/mix-php/swoolefor) 一个由 Mixphp 实现的通用热更新组件
|
||||
- [buexplain/go-watch](https://github.com/buexplain/go-watch) 一个基于 Go 语言实现的通用热更新组件
|
||||
- [remy/nodemon](https://github.com/remy/nodemon) 一个基于 node.js 实现的通用热更新组件
|
||||
@ -117,6 +117,10 @@
|
||||
|
||||
- [firstphp/wsdebug](https://github.com/lamplife/wsdebug) 通过 `WebSocket` 实时观测异常错误的开发调试组件
|
||||
|
||||
## 权限认证
|
||||
|
||||
- [donjan-deng/hyperf-permission](https://github.com/donjan-deng/hyperf-permission) 基于 [spatie/laravel-permission](https://github.com/spatie/laravel-permission) 开发的适配 Hyperf 的权限组件
|
||||
|
||||
## 第三方 SDK
|
||||
|
||||
- [yurunsoft/pay-sdk](https://github.com/Yurunsoft/PaySDK) 支持 Swoole 协程的支付宝/微信支付 SDK
|
||||
|
@ -1,5 +1,66 @@
|
||||
# 版本更新记录
|
||||
|
||||
# v1.1.12 - 2019-12-26
|
||||
|
||||
## 新增
|
||||
|
||||
- [#1177](https://github.com/hyperf/hyperf/pull/1177) 为 `jsonrpc` 组件增加了新的协议 `jsonrpc-tcp-length-check`,并对部分代码进行了优化。
|
||||
|
||||
## 修复
|
||||
|
||||
- [#1175](https://github.com/hyperf/hyperf/pull/1175) 修复 `Hyperf\Utils\Collection::random` 方法不支持传入 `null`。
|
||||
- [#1178](https://github.com/hyperf/hyperf/pull/1178) 修复 `Hyperf\Database\Query\Builder::chunkById` 方法不支持元素是 `array` 的情况。
|
||||
- [#1189](https://github.com/hyperf/hyperf/pull/1189) 修复 `Hyperf\Utils\Collection::operatorForWhere` 方法,`operator` 只能传入 `string` 的BUG。
|
||||
|
||||
## 优化
|
||||
|
||||
- [#1186](https://github.com/hyperf/hyperf/pull/1186) 日志配置中,只填写 `formatter.class` 的情况下,可以使用默认的 `formatter.constructor` 配置。
|
||||
|
||||
# v1.1.11 - 2019-12-19
|
||||
|
||||
## 新增
|
||||
|
||||
- [#849](https://github.com/hyperf/hyperf/pull/849) 为 hyperf/tracer 组件增加 span tag 配置功能;
|
||||
|
||||
## 修复
|
||||
|
||||
- [#1142](https://github.com/hyperf/hyperf/pull/1142) 修复 `Register::resolveConnection` 会返回 null 的问题;
|
||||
- [#1144](https://github.com/hyperf/hyperf/pull/1144) 修复配置文件形式下服务限流会失效的问题;
|
||||
- [#1145](https://github.com/hyperf/hyperf/pull/1145) 修复 `CoroutineMemoryDriver::delKey` 方法的返回值错误的问题;
|
||||
- [#1153](https://github.com/hyperf/hyperf/pull/1153) 修复验证器的 `alpha_num` 规则无法按预期运行的问题;
|
||||
|
||||
# v1.1.10 - 2019-12-12
|
||||
|
||||
## 修复
|
||||
|
||||
- [#1104](https://github.com/hyperf/hyperf/pull/1104) 修复了 Guzzle 客户端的重试中间件的状态码识别范围为 2xx;
|
||||
- [#1105](https://github.com/hyperf/hyperf/pull/1105) 修复了 Retry 组件在重试尝试前不还原管道堆栈的问题;
|
||||
- [#1106](https://github.com/hyperf/hyperf/pull/1106) 修复了数据库在开启 `sticky` 模式时连接回归连接池时没有重置状态的问题;
|
||||
- [#1119](https://github.com/hyperf/hyperf/pull/1119) 修复 TCP 协议下的 JSONRPC Server 在解析 JSON 失败时无法正确的返回预期的 Error Response 的问题;
|
||||
- [#1124](https://github.com/hyperf/hyperf/pull/1124) 修复 Session 中间件在储存当前的 URL 时,当 URL 以 `/` 结尾时会忽略斜杠的问题;
|
||||
|
||||
## 变更
|
||||
|
||||
- [#1108](https://github.com/hyperf/hyperf/pull/1108) 重命名 `Hyperf\Tracer\Middleware\TraceMiddeware` 为 `Hyperf\Tracer\Middleware\TraceMiddleware`;
|
||||
- [#1108](https://github.com/hyperf/hyperf/pull/1111) 升级 `Hyperf\ServiceGovernance\Listener\ServiceRegisterListener` 类的成员属性和方法的等级为 `protected`,以便更好的重写相关方法;
|
||||
|
||||
# v1.1.9 - 2019-12-05
|
||||
|
||||
## 新增
|
||||
|
||||
- [#948](https://github.com/hyperf/hyperf/pull/948) 为 DI Container 增加懒加载功能;
|
||||
- [#1044](https://github.com/hyperf/hyperf/pull/1044) 为 AMQP Consumer 增加 `basic_qos` 配置;
|
||||
- [#1056](https://github.com/hyperf/hyperf/pull/1056) [#1081](https://github.com/hyperf/hyperf/pull/1081) DI Container 增加 `define()` 和 `set()` 方法,同时增加 `Hyperf\Contract\ContainerInterface`;
|
||||
- [#1059](https://github.com/hyperf/hyperf/pull/1059) `job.stub` 模板增加构造函数;
|
||||
- [#1084](https://github.com/hyperf/hyperf/pull/1084) 支持 PHP 7.4,TrvisCI 增加 PHP 7.4 运行支持;
|
||||
|
||||
## 修复
|
||||
|
||||
- [#1007](https://github.com/hyperf/hyperf/pull/1007) 修复 `vendor:: publish` 的命令返回值;
|
||||
- [#1049](https://github.com/hyperf/hyperf/pull/1049) 修复 `Hyperf\Cache\Driver\RedisDriver::clear` 会有可能删除所有缓存失败的问题;
|
||||
- [#1055](https://github.com/hyperf/hyperf/pull/1055) 修复 Image 验证时后缀大小写的问题;
|
||||
- [#1085](https://github.com/hyperf/hyperf/pull/1085) [#1091](https://github.com/hyperf/hyperf/pull/1091) Fixed `@Retry` 注解使用时会找不到容器的问题;
|
||||
|
||||
# v1.1.8 - 2019-11-28
|
||||
|
||||
## 新增
|
||||
|
@ -21,7 +21,7 @@ $ php bin/hyperf.php db:model table_name
|
||||
| 参数 | 类型 | 默认值 | 备注 |
|
||||
|:------------------:|:------:|:-----------------:|:-----------------------------------------------:|
|
||||
| --pool | string | `default` | 连接池,脚本会根据当前连接池配置创建 |
|
||||
| --path | string | `app\Model` | 模型路径 |
|
||||
| --path | string | `app/Model` | 模型路径 |
|
||||
| --force-casts | bool | `false` | 是否强制重置 `casts` 参数 |
|
||||
| --prefix | string | 空字符串 | 表前缀 |
|
||||
| --inheritance | string | `Model` | 父类 |
|
||||
|
@ -306,7 +306,7 @@ Hyperf 的长生命周期依赖注入在项目启动时完成。这意味着长
|
||||
|
||||
* 构造函数时还不是协程环境,如果注入了可能会触发协程切换的类,就会导致框架启动失败。
|
||||
|
||||
* 构造函数中要避免循坏依赖(比较典型的例子为 `Listener` 和 `EventDispatcherInterface`),不然也会启动失败。
|
||||
* 构造函数中要避免循环依赖(比较典型的例子为 `Listener` 和 `EventDispatcherInterface`),不然也会启动失败。
|
||||
|
||||
目前解决方案是:只在实例中注入 `Psr\Container\ContainerInterface` ,而其他的组件在非构造函数执行时通过 `container` 获取。但 PSR-11 中指出:
|
||||
|
||||
|
@ -111,6 +111,41 @@ return [
|
||||
'settings' => [
|
||||
'open_eof_split' => true,
|
||||
'package_eof' => "\r\n",
|
||||
'package_max_length' => 1024 * 1024 * 2,
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
TCP Server (适配 `jsonrpc-tcp-length-check` 协议)
|
||||
|
||||
当前协议为 `jsonrpc` 的扩展协议,用户可以很方便的修改对应的 `settings` 使用此协议,示例如下。
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
use Hyperf\Server\Server;
|
||||
use Hyperf\Server\SwooleEvent;
|
||||
|
||||
return [
|
||||
// 这里省略了该文件的其它配置
|
||||
'servers' => [
|
||||
[
|
||||
'name' => 'jsonrpc',
|
||||
'type' => Server::SERVER_BASE,
|
||||
'host' => '0.0.0.0',
|
||||
'port' => 9503,
|
||||
'sock_type' => SWOOLE_SOCK_TCP,
|
||||
'callbacks' => [
|
||||
SwooleEvent::ON_RECEIVE => [\Hyperf\JsonRpc\TcpServer::class, 'onReceive'],
|
||||
],
|
||||
'settings' => [
|
||||
'open_length_check' => true,
|
||||
'package_length_type' => 'N',
|
||||
'package_length_offset' => 0,
|
||||
'package_body_offset' => 4,
|
||||
'package_max_length' => 1024 * 1024 * 2,
|
||||
],
|
||||
],
|
||||
],
|
||||
@ -154,6 +189,7 @@ return [
|
||||
// 对应容器对象 ID,可选,默认值等于 service 配置的值,用来定义依赖注入的 key
|
||||
'id' => \App\JsonRpc\CalculatorServiceInterface::class,
|
||||
// 服务提供者的服务协议,可选,默认值为 jsonrpc-http
|
||||
// 可选 jsonrpc-http jsonrpc jsonrpc-tcp-length-check
|
||||
'protocol' => 'jsonrpc-http',
|
||||
// 负载均衡算法,可选,默认值为 random
|
||||
'load_balancer' => 'random',
|
||||
@ -166,6 +202,29 @@ return [
|
||||
'nodes' => [
|
||||
['host' => '127.0.0.1', 'port' => 9504],
|
||||
],
|
||||
// 配置项,会影响到 Packer 和 Transporter
|
||||
'options' => [
|
||||
'connect_timeout' => 5.0,
|
||||
'recv_timeout' => 5.0,
|
||||
'settings' => [
|
||||
// 根据协议不同,区分配置
|
||||
'open_eof_split' => true,
|
||||
'package_eof' => "\r\n",
|
||||
// 'open_length_check' => true,
|
||||
// 'package_length_type' => 'N',
|
||||
// 'package_length_offset' => 0,
|
||||
// 'package_body_offset' => 4,
|
||||
],
|
||||
// 当使用 JsonRpcPoolTransporter 时会用到以下配置
|
||||
'pool' => [
|
||||
'min_connections' => 1,
|
||||
'max_connections' => 32,
|
||||
'connect_timeout' => 10.0,
|
||||
'wait_timeout' => 3.0,
|
||||
'heartbeat' => -1,
|
||||
'max_idle_time' => 60.0,
|
||||
],
|
||||
],
|
||||
]
|
||||
],
|
||||
];
|
||||
@ -370,9 +429,7 @@ use Hyperf\JsonRpc\JsonRpcPoolTransporter;
|
||||
use Hyperf\JsonRpc\JsonRpcTransporter;
|
||||
|
||||
return [
|
||||
JsonRpcTransporter::class => function () {
|
||||
return make(JsonRpcPoolTransporter::class);
|
||||
},
|
||||
JsonRpcTransporter::class => JsonRpcPoolTransporter::class,
|
||||
];
|
||||
|
||||
```
|
||||
|
@ -102,6 +102,7 @@ use App\Middleware\BarMiddleware;
|
||||
use App\Middleware\FooMiddleware;
|
||||
use Hyperf\HttpServer\Annotation\AutoController;
|
||||
use Hyperf\HttpServer\Annotation\Middleware;
|
||||
use Hyperf\HttpServer\Annotation\Middlewares;
|
||||
|
||||
/**
|
||||
* @AutoController()
|
||||
|
@ -225,11 +225,13 @@ class IndexController
|
||||
|
||||
## 重新加载代码
|
||||
|
||||
由于 `Hyperf` 是持久化的 `CLI` 应用,也就意味着一旦进程启动,已解析的 `PHP` 代码会持久化在进程中,也就意味着启动服务后您再修改的 `PHP` 代码不会改变已启动的服务,如您希望服务重新加载您修改后的代码,您需要通过在启动的 `Console` 中键入 `CTRL + C` 终止服务,再重新执行启动命令完成重启和重新加载。
|
||||
由于 `Hyperf` 是持久化的 `CLI` 应用,也就意味着一旦进程启动,已解析的 `PHP` 代码会持久化在进程中,也就意味着启动服务后您再修改的 `PHP` 代码不会改变已启动的服务,如您希望服务重新加载您修改后的代码,您需要通过在启动的 `Console` 中键入 `CTRL + C` 终止服务,再重新执行启动命令 `php bin/hyperf.php start` 完成启动和重新加载。
|
||||
|
||||
> Tips: 您也可以将启动 Server 的命令配置在 IDE 上,便可直接通过 IDE 的 `启动/停止` 操作快捷的完成 `启动服务` 或 `重启服务` 的操作。
|
||||
> 且非视图开发时可以采用 [TDD(Test-Driven Development)](https://baike.baidu.com/item/TDD/9064369) 测试驱动开发来进行开发,这样不仅可以省略掉服务重启和频繁切换窗口的麻烦,还可保证接口数据的正确性。
|
||||
|
||||
> 另外,在文档 [协程组件库](./zh-cn/awesome-components?id=%e7%83%ad%e6%9b%b4%e6%96%b0%e7%83%ad%e9%87%8d%e8%bd%bd) 一章中提供了多种由社区开发者支持的 热更新/热重载 的解决方案,如仍希望采用 热更新/热重载 方案可再深入了解。
|
||||
|
||||
## 多端口监听
|
||||
|
||||
`Hyperf` 支持监听多个端口,但因为 `callbacks` 中的对象直接从容器中获取,所以相同的 `Hyperf\HttpServer\Server::class` 会在容器中被覆盖。所以我们需要在依赖关系中,重新定义 `Server`,确保对象隔离。
|
||||
|
@ -76,6 +76,32 @@ class IndexController
|
||||
}
|
||||
```
|
||||
|
||||
除了可以通过依赖注入获取路由参数,还可以通过`route`方法获取,如下所示:
|
||||
|
||||
```php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use Hyperf\HttpServer\Contract\RequestInterface;
|
||||
use Hyperf\HttpServer\Annotation\AutoController;
|
||||
|
||||
/**
|
||||
* @AutoController()
|
||||
*/
|
||||
class IndexController
|
||||
{
|
||||
public function info(RequestInterface $request)
|
||||
{
|
||||
// 存在则返回,不存在则返回默认值 null
|
||||
$id = $request->route('id');
|
||||
// 存在则返回,不存在则返回默认值 0
|
||||
$id = $request->route('id', 0);
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 请求路径 & 方法
|
||||
|
||||
`Hyperf\HttpServer\Contract\RequestInterface` 除了使用 [PSR-7](https://www.php-fig.org/psr/psr-7/) 标准定义的 `APIs` 之外,还提供了多种方法来检查请求,下面我们提供一些方法的示例:
|
||||
|
@ -332,7 +332,7 @@ $result = \Hyperf\Retry\Retry::with(
|
||||
$result = \Hyperf\Retry\Retry::whenReturns(false) // 当返回false时重试
|
||||
->max(3) // 最多3次
|
||||
->inSeconds(5) // 最长5秒
|
||||
->sleep(1) // 间隔1秒
|
||||
->sleep(1) // 间隔1毫秒
|
||||
->fallback(function(){return true;}) // fallback函数
|
||||
->call(function(){
|
||||
if (rand(1, 100) >= 20){
|
||||
|
@ -173,13 +173,25 @@ Router::get('/user/{id}', 'App\Controller\UserController::info')
|
||||
```
|
||||
|
||||
```php
|
||||
public function index(int $id)
|
||||
public function info(int $id)
|
||||
{
|
||||
$user = User::find($id);
|
||||
return $user->toArray();
|
||||
}
|
||||
```
|
||||
|
||||
通过`route`方法获取
|
||||
|
||||
```php
|
||||
public function index(RequestInterface $request)
|
||||
{
|
||||
// 存在则返回,不存在则返回默认值 null
|
||||
$id = $request->route('id');
|
||||
// 存在则返回,不存在则返回默认值 0
|
||||
$id = $request->route('id', 0);
|
||||
}
|
||||
```
|
||||
|
||||
#### 必填参数
|
||||
|
||||
我们可以对 `$uri` 进行一些参数定义,通过 `{}` 来声明参数,如 `/user/{id}` 则声明了 `id` 值为一个必填参数。
|
||||
|
@ -180,6 +180,33 @@ return [
|
||||
],
|
||||
];
|
||||
```
|
||||
### 配置 Span tag
|
||||
|
||||
`1.1.11` 版本后增加了 Span Tag 配置的功能,对于一些 Hyperf 自动收集追踪信息的 Span Tag 名称,可以通过更改 Span Tag 配置来更改对应的名称,只需在配置文件 `config/autolaod/opentracing.php` 内增加 `tags` 配置即可,参考配置如下。如配置项存在,则以配置项的值为准,如配置项不存在,则以组件的默认值为准。
|
||||
|
||||
```php
|
||||
return [
|
||||
'tags' => [
|
||||
// HTTP 客户端 (Guzzle)
|
||||
'http_client' => [
|
||||
'http.url' => 'http.url',
|
||||
'http.method' => 'http.method',
|
||||
'http.status_code' => 'http.status_code',
|
||||
],
|
||||
// Redis 客户端
|
||||
'redis' => [
|
||||
'arguments' => 'arguments',
|
||||
'result' => 'result',
|
||||
],
|
||||
// 数据库客户端 (hyper/database)
|
||||
'db' => [
|
||||
'db.query' => 'db.query',
|
||||
'db.statement' => 'db.statement',
|
||||
'db.query_time' => 'db.query_time',
|
||||
],
|
||||
]
|
||||
];
|
||||
```
|
||||
|
||||
### 更换采样器
|
||||
|
||||
|
@ -101,7 +101,7 @@
|
||||
|
||||
## 熱更新/熱重載
|
||||
|
||||
- [ha-ni-cc/hyperf-watch](https://github.com/ha-ni-cc/hyperf-watch) 一個基於 fswatch 實現的通用熱更新組件
|
||||
- [ha-ni-cc/hyperf-watch](https://github.com/ha-ni-cc/hyperf-watch) 一個基於 Swoole 實現的通用熱更新組件
|
||||
- [mix-php/swoolefor](https://github.com/mix-php/swoolefor) 一個由 Mixphp 實現的通用熱更新組件
|
||||
- [buexplain/go-watch](https://github.com/buexplain/go-watch) 一個基於 Go 語言實現的通用熱更新組件
|
||||
- [remy/nodemon](https://github.com/remy/nodemon) 一個基於 node.js 實現的通用熱更新組件
|
||||
@ -117,6 +117,10 @@
|
||||
|
||||
- [firstphp/wsdebug](https://github.com/lamplife/wsdebug) 通過 `WebSocket` 實時觀測異常錯誤的開發調試組件
|
||||
|
||||
## 權限認證
|
||||
|
||||
- [donjan-deng/hyperf-permission](https://github.com/donjan-deng/hyperf-permission) 基於 [spatie/laravel-permission](https://github.com/spatie/laravel-permission) 開發的適配 Hyperf 的權限組件
|
||||
|
||||
## 第三方 SDK
|
||||
|
||||
- [yurunsoft/pay-sdk](https://github.com/Yurunsoft/PaySDK) 支持 Swoole 協程的支付寶/微信支付 SDK
|
||||
|
@ -21,7 +21,7 @@ $ php bin/hyperf.php db:model table_name
|
||||
| 參數 | 類型 | 默認值 | 備註 |
|
||||
|:------------------:|:------:|:-----------------:|:-----------------------------------------------:|
|
||||
| --pool | string | `default` | 連接池,腳本會根據當前連接池配置創建 |
|
||||
| --path | string | `app\Model` | 模型路徑 |
|
||||
| --path | string | `app/Model` | 模型路徑 |
|
||||
| --force-casts | bool | `false` | 是否強制重置 `casts` 參數 |
|
||||
| --prefix | string | 空字符串 | 表前綴 |
|
||||
| --inheritance | string | `Model` | 父類 |
|
||||
|
@ -102,6 +102,7 @@ use App\Middleware\BarMiddleware;
|
||||
use App\Middleware\FooMiddleware;
|
||||
use Hyperf\HttpServer\Annotation\AutoController;
|
||||
use Hyperf\HttpServer\Annotation\Middleware;
|
||||
use Hyperf\HttpServer\Annotation\Middlewares;
|
||||
|
||||
/**
|
||||
* @AutoController()
|
||||
|
@ -225,11 +225,13 @@ class IndexController
|
||||
|
||||
## 重新加載代碼
|
||||
|
||||
由於 `Hyperf` 是持久化的 `CLI` 應用,也就意味着一旦進程啟動,已解析的 `PHP` 代碼會持久化在進程中,也就意味着啟動服務後您再修改的 `PHP` 代碼不會改變已啟動的服務,如您希望服務重新加載您修改後的代碼,您需要通過在啟動的 `Console` 中鍵入 `CTRL + C` 終止服務,再重新執行啟動命令完成重啟和重新加載。
|
||||
由於 `Hyperf` 是持久化的 `CLI` 應用,也就意味着一旦進程啟動,已解析的 `PHP` 代碼會持久化在進程中,也就意味着啟動服務後您再修改的 `PHP` 代碼不會改變已啟動的服務,如您希望服務重新加載您修改後的代碼,您需要通過在啟動的 `Console` 中鍵入 `CTRL + C` 終止服務,再重新執行啟動命令 `php bin/hyperf.php start` 完成啟動和重新加載。
|
||||
|
||||
> Tips: 您也可以將啟動 Server 的命令配置在 IDE 上,便可直接通過 IDE 的 `啟動/停止` 操作快捷的完成 `啟動服務` 或 `重啟服務` 的操作。
|
||||
> 且非視圖開發時可以採用 [TDD(Test-Driven Development)](https://baike.baidu.com/item/TDD/9064369) 測試驅動開發來進行開發,這樣不僅可以省略掉服務重啟和頻繁切換窗口的麻煩,還可保證接口數據的正確性。
|
||||
|
||||
> 另外,在文檔 [協程組件庫](./zh-cn/awesome-components?id=%e7%83%ad%e6%9b%b4%e6%96%b0%e7%83%ad%e9%87%8d%e8%bd%bd) 一章中提供了多種由社區開發者支持的 熱更新/熱重載 的解決方案,如仍希望採用 熱更新/熱重載 方案可再深入瞭解。
|
||||
|
||||
## 多端口監聽
|
||||
|
||||
`Hyperf` 支持監聽多個端口,但因為 `callbacks` 中的對象直接從容器中獲取,所以相同的 `Hyperf\HttpServer\Server::class` 會在容器中被覆蓋。所以我們需要在依賴關係中,重新定義 `Server`,確保對象隔離。
|
||||
|
@ -173,7 +173,7 @@ Router::get('/user/{id}', 'App\Controller\UserController::info')
|
||||
```
|
||||
|
||||
```php
|
||||
public function index(int $id)
|
||||
public function info(int $id)
|
||||
{
|
||||
$user = User::find($id);
|
||||
return $user->toArray();
|
||||
|
@ -180,6 +180,33 @@ return [
|
||||
],
|
||||
];
|
||||
```
|
||||
### 配置 Span tag
|
||||
|
||||
`1.1.11` 版本後增加了 Span Tag 配置的功能,對於一些 Hyperf 自動收集追蹤信息的 Span Tag 名稱,可以通過更改 Span Tag 配置來更改對應的名稱,只需在配置文件 `config/autolaod/opentracing.php` 內增加 `tags` 配置即可,參考配置如下。如配置項存在,則以配置項的值為準,如配置項不存在,則以組件的默認值為準。
|
||||
|
||||
```php
|
||||
return [
|
||||
'tags' => [
|
||||
// HTTP 客户端 (Guzzle)
|
||||
'http_client' => [
|
||||
'http.url' => 'http.url',
|
||||
'http.method' => 'http.method',
|
||||
'http.status_code' => 'http.status_code',
|
||||
],
|
||||
// Redis 客户端
|
||||
'redis' => [
|
||||
'arguments' => 'arguments',
|
||||
'result' => 'result',
|
||||
],
|
||||
// 數據庫客户端 (hyper/database)
|
||||
'db' => [
|
||||
'db.query' => 'db.query',
|
||||
'db.statement' => 'db.statement',
|
||||
'db.query_time' => 'db.query_time',
|
||||
],
|
||||
]
|
||||
];
|
||||
```
|
||||
|
||||
### 更換採樣器
|
||||
|
||||
|
@ -101,7 +101,7 @@
|
||||
|
||||
## 熱更新/熱過載
|
||||
|
||||
- [ha-ni-cc/hyperf-watch](https://github.com/ha-ni-cc/hyperf-watch) 一個基於 fswatch 實現的通用熱更新元件
|
||||
- [ha-ni-cc/hyperf-watch](https://github.com/ha-ni-cc/hyperf-watch) 一個基於 Swoole 實現的通用熱更新元件
|
||||
- [mix-php/swoolefor](https://github.com/mix-php/swoolefor) 一個由 Mixphp 實現的通用熱更新元件
|
||||
- [buexplain/go-watch](https://github.com/buexplain/go-watch) 一個基於 Go 語言實現的通用熱更新元件
|
||||
- [remy/nodemon](https://github.com/remy/nodemon) 一個基於 node.js 實現的通用熱更新元件
|
||||
@ -117,6 +117,10 @@
|
||||
|
||||
- [firstphp/wsdebug](https://github.com/lamplife/wsdebug) 通過 `WebSocket` 實時觀測異常錯誤的開發除錯元件
|
||||
|
||||
## 許可權認證
|
||||
|
||||
- [donjan-deng/hyperf-permission](https://github.com/donjan-deng/hyperf-permission) 基於 [spatie/laravel-permission](https://github.com/spatie/laravel-permission) 開發的適配 Hyperf 的許可權元件
|
||||
|
||||
## 第三方 SDK
|
||||
|
||||
- [yurunsoft/pay-sdk](https://github.com/Yurunsoft/PaySDK) 支援 Swoole 協程的支付寶/微信支付 SDK
|
||||
|
@ -21,7 +21,7 @@ $ php bin/hyperf.php db:model table_name
|
||||
| 引數 | 型別 | 預設值 | 備註 |
|
||||
|:------------------:|:------:|:-----------------:|:-----------------------------------------------:|
|
||||
| --pool | string | `default` | 連線池,指令碼會根據當前連線池配置建立 |
|
||||
| --path | string | `app\Model` | 模型路徑 |
|
||||
| --path | string | `app/Model` | 模型路徑 |
|
||||
| --force-casts | bool | `false` | 是否強制重置 `casts` 引數 |
|
||||
| --prefix | string | 空字串 | 表字首 |
|
||||
| --inheritance | string | `Model` | 父類 |
|
||||
|
@ -102,6 +102,7 @@ use App\Middleware\BarMiddleware;
|
||||
use App\Middleware\FooMiddleware;
|
||||
use Hyperf\HttpServer\Annotation\AutoController;
|
||||
use Hyperf\HttpServer\Annotation\Middleware;
|
||||
use Hyperf\HttpServer\Annotation\Middlewares;
|
||||
|
||||
/**
|
||||
* @AutoController()
|
||||
|
@ -225,11 +225,13 @@ class IndexController
|
||||
|
||||
## 重新載入程式碼
|
||||
|
||||
由於 `Hyperf` 是持久化的 `CLI` 應用,也就意味著一旦程序啟動,已解析的 `PHP` 程式碼會持久化在程序中,也就意味著啟動服務後您再修改的 `PHP` 程式碼不會改變已啟動的服務,如您希望服務重新載入您修改後的程式碼,您需要通過在啟動的 `Console` 中鍵入 `CTRL + C` 終止服務,再重新執行啟動命令完成重啟和重新載入。
|
||||
由於 `Hyperf` 是持久化的 `CLI` 應用,也就意味著一旦程序啟動,已解析的 `PHP` 程式碼會持久化在程序中,也就意味著啟動服務後您再修改的 `PHP` 程式碼不會改變已啟動的服務,如您希望服務重新載入您修改後的程式碼,您需要通過在啟動的 `Console` 中鍵入 `CTRL + C` 終止服務,再重新執行啟動命令 `php bin/hyperf.php start` 完成啟動和重新載入。
|
||||
|
||||
> Tips: 您也可以將啟動 Server 的命令配置在 IDE 上,便可直接通過 IDE 的 `啟動/停止` 操作快捷的完成 `啟動服務` 或 `重啟服務` 的操作。
|
||||
> 且非檢視開發時可以採用 [TDD(Test-Driven Development)](https://baike.baidu.com/item/TDD/9064369) 測試驅動開發來進行開發,這樣不僅可以省略掉服務重啟和頻繁切換視窗的麻煩,還可保證介面資料的正確性。
|
||||
|
||||
> 另外,在文件 [協程元件庫](./zh-cn/awesome-components?id=%e7%83%ad%e6%9b%b4%e6%96%b0%e7%83%ad%e9%87%8d%e8%bd%bd) 一章中提供了多種由社群開發者支援的 熱更新/熱過載 的解決方案,如仍希望採用 熱更新/熱過載 方案可再深入瞭解。
|
||||
|
||||
## 多埠監聽
|
||||
|
||||
`Hyperf` 支援監聽多個埠,但因為 `callbacks` 中的物件直接從容器中獲取,所以相同的 `Hyperf\HttpServer\Server::class` 會在容器中被覆蓋。所以我們需要在依賴關係中,重新定義 `Server`,確保物件隔離。
|
||||
|
@ -173,7 +173,7 @@ Router::get('/user/{id}', 'App\Controller\UserController::info')
|
||||
```
|
||||
|
||||
```php
|
||||
public function index(int $id)
|
||||
public function info(int $id)
|
||||
{
|
||||
$user = User::find($id);
|
||||
return $user->toArray();
|
||||
|
@ -180,6 +180,33 @@ return [
|
||||
],
|
||||
];
|
||||
```
|
||||
### 配置 Span tag
|
||||
|
||||
`1.1.11` 版本後增加了 Span Tag 配置的功能,對於一些 Hyperf 自動收集追蹤資訊的 Span Tag 名稱,可以通過更改 Span Tag 配置來更改對應的名稱,只需在配置檔案 `config/autolaod/opentracing.php` 內增加 `tags` 配置即可,參考配置如下。如配置項存在,則以配置項的值為準,如配置項不存在,則以元件的預設值為準。
|
||||
|
||||
```php
|
||||
return [
|
||||
'tags' => [
|
||||
// HTTP 客戶端 (Guzzle)
|
||||
'http_client' => [
|
||||
'http.url' => 'http.url',
|
||||
'http.method' => 'http.method',
|
||||
'http.status_code' => 'http.status_code',
|
||||
],
|
||||
// Redis 客戶端
|
||||
'redis' => [
|
||||
'arguments' => 'arguments',
|
||||
'result' => 'result',
|
||||
],
|
||||
// 資料庫客戶端 (hyper/database)
|
||||
'db' => [
|
||||
'db.query' => 'db.query',
|
||||
'db.statement' => 'db.statement',
|
||||
'db.query_time' => 'db.query_time',
|
||||
],
|
||||
]
|
||||
];
|
||||
```
|
||||
|
||||
### 更換取樣器
|
||||
|
||||
|
@ -39,6 +39,7 @@
|
||||
<directory suffix="Test.php">./src/paginator/tests</directory>
|
||||
<directory suffix="Test.php">./src/pool/tests</directory>
|
||||
<directory suffix="Test.php">./src/protocol/tests</directory>
|
||||
<directory suffix="Test.php">./src/rate-limit/tests</directory>
|
||||
<directory suffix="Test.php">./src/redis/tests</directory>
|
||||
<directory suffix="Test.php">./src/retry/tests</directory>
|
||||
<directory suffix="Test.php">./src/rpc/tests</directory>
|
||||
|
7
src/cache/src/Annotation/CachePut.php
vendored
7
src/cache/src/Annotation/CachePut.php
vendored
@ -35,6 +35,12 @@ class CachePut extends AbstractAnnotation
|
||||
*/
|
||||
public $ttl;
|
||||
|
||||
/**
|
||||
* The max offset for ttl.
|
||||
* @var int
|
||||
*/
|
||||
public $offset = 0;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
@ -44,5 +50,6 @@ class CachePut extends AbstractAnnotation
|
||||
{
|
||||
parent::__construct($value);
|
||||
$this->ttl = (int) $this->ttl;
|
||||
$this->offset = (int) $this->offset;
|
||||
}
|
||||
}
|
||||
|
7
src/cache/src/Annotation/Cacheable.php
vendored
7
src/cache/src/Annotation/Cacheable.php
vendored
@ -42,6 +42,12 @@ class Cacheable extends AbstractAnnotation
|
||||
*/
|
||||
public $listener;
|
||||
|
||||
/**
|
||||
* The max offset for ttl.
|
||||
* @var int
|
||||
*/
|
||||
public $offset = 0;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
@ -56,6 +62,7 @@ class Cacheable extends AbstractAnnotation
|
||||
{
|
||||
parent::__construct($value);
|
||||
$this->ttl = (int) $this->ttl;
|
||||
$this->offset = (int) $this->offset;
|
||||
}
|
||||
|
||||
public function collectMethod(string $className, ?string $target): void
|
||||
|
13
src/cache/src/AnnotationManager.php
vendored
13
src/cache/src/AnnotationManager.php
vendored
@ -50,7 +50,7 @@ class AnnotationManager
|
||||
$group = $annotation->group;
|
||||
$ttl = $annotation->ttl ?? $this->config->get("cache.{$group}.ttl", 3600);
|
||||
|
||||
return [$key, $ttl, $group, $annotation];
|
||||
return [$key, $ttl + $this->getRandomOffset($annotation->offset), $group, $annotation];
|
||||
}
|
||||
|
||||
public function getCacheEvictValue(string $className, string $method, array $arguments): array
|
||||
@ -79,7 +79,7 @@ class AnnotationManager
|
||||
$group = $annotation->group;
|
||||
$ttl = $annotation->ttl ?? $this->config->get("cache.{$group}.ttl", 3600);
|
||||
|
||||
return [$key, $ttl, $group, $annotation];
|
||||
return [$key, $ttl + $this->getRandomOffset($annotation->offset), $group, $annotation];
|
||||
}
|
||||
|
||||
public function getFailCacheValue(string $className, string $method, array $arguments): array
|
||||
@ -95,6 +95,15 @@ class AnnotationManager
|
||||
return [$key, $ttl, $group, $annotation];
|
||||
}
|
||||
|
||||
protected function getRandomOffset(int $offset): int
|
||||
{
|
||||
if ($offset > 0) {
|
||||
return rand(0, $offset);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected function getAnnotation(string $annotation, string $className, string $method): AbstractAnnotation
|
||||
{
|
||||
$collector = AnnotationCollector::get($className);
|
||||
|
@ -106,6 +106,7 @@ class CoroutineMemoryDriver extends Driver implements KeyCollectorInterface
|
||||
}
|
||||
}
|
||||
$instance->put($collector, $result);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function getCollection()
|
||||
|
35
src/cache/tests/Cases/AnnotationTest.php
vendored
35
src/cache/tests/Cases/AnnotationTest.php
vendored
@ -14,6 +14,10 @@ namespace HyperfTest\Cache\Cases;
|
||||
|
||||
use Hyperf\Cache\Annotation\Cacheable;
|
||||
use Hyperf\Cache\Annotation\CachePut;
|
||||
use Hyperf\Cache\AnnotationManager;
|
||||
use Hyperf\Contract\ConfigInterface;
|
||||
use Hyperf\Contract\StdoutLoggerInterface;
|
||||
use Mockery;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
@ -43,9 +47,40 @@ class AnnotationTest extends TestCase
|
||||
$annotation = new CachePut([
|
||||
'prefix' => 'test',
|
||||
'ttl' => '3600',
|
||||
'offset' => '100',
|
||||
]);
|
||||
|
||||
$this->assertSame('test', $annotation->prefix);
|
||||
$this->assertSame(3600, $annotation->ttl);
|
||||
$this->assertSame(100, $annotation->offset);
|
||||
}
|
||||
|
||||
public function testAnnotationManager()
|
||||
{
|
||||
$cacheable = new Cacheable(['prefix' => 'test', 'ttl' => 3600, 'offset' => 100]);
|
||||
$cacheable2 = new Cacheable(['prefix' => 'test', 'ttl' => 3600]);
|
||||
$cacheput = new CachePut(['prefix' => 'test', 'ttl' => 3600, 'offset' => 100]);
|
||||
$config = Mockery::mock(ConfigInterface::class);
|
||||
$logger = Mockery::mock(StdoutLoggerInterface::class);
|
||||
/** @var AnnotationManager $manager */
|
||||
$manager = Mockery::mock(AnnotationManager::class . '[getAnnotation]', [$config, $logger]);
|
||||
$manager->shouldAllowMockingProtectedMethods();
|
||||
$manager->shouldReceive('getAnnotation')->with(Cacheable::class, Mockery::any(), Mockery::any())->once()->andReturn($cacheable);
|
||||
$manager->shouldReceive('getAnnotation')->with(Cacheable::class, Mockery::any(), Mockery::any())->once()->andReturn($cacheable2);
|
||||
$manager->shouldReceive('getAnnotation')->with(CachePut::class, Mockery::any(), Mockery::any())->once()->andReturn($cacheput);
|
||||
|
||||
[$key, $ttl] = $manager->getCacheableValue('Foo', 'test', ['id' => $id = uniqid()]);
|
||||
$this->assertSame('test:' . $id, $key);
|
||||
$this->assertGreaterThanOrEqual(3600, $ttl);
|
||||
$this->assertLessThanOrEqual(3700, $ttl);
|
||||
|
||||
[$key, $ttl] = $manager->getCachePutValue('Foo', 'test', ['id' => $id = uniqid()]);
|
||||
$this->assertSame('test:' . $id, $key);
|
||||
$this->assertGreaterThanOrEqual(3600, $ttl);
|
||||
$this->assertLessThanOrEqual(3700, $ttl);
|
||||
|
||||
[$key, $ttl] = $manager->getCacheableValue('Foo', 'test', ['id' => $id = uniqid()]);
|
||||
$this->assertSame('test:' . $id, $key);
|
||||
$this->assertSame(3600, $ttl);
|
||||
}
|
||||
}
|
||||
|
@ -45,4 +45,21 @@ class CoroutineMemoryDriverTest extends TestCase
|
||||
$this->assertSame('xxx2', $driver->get('test', null));
|
||||
}]);
|
||||
}
|
||||
|
||||
public function testKeyCollectorInterface()
|
||||
{
|
||||
$container = Mockery::mock(ContainerInterface::class);
|
||||
$container->shouldReceive('get')->with(PhpSerializerPacker::class)->andReturn(new PhpSerializerPacker());
|
||||
|
||||
$driver = new CoroutineMemoryDriver($container, []);
|
||||
$driver->addKey('test', 'key1');
|
||||
$driver->addKey('test', 'key2');
|
||||
$this->assertEquals(['key1', 'key2'], $driver->keys('test'));
|
||||
$driver->delKey('test', 'key2');
|
||||
$this->assertEquals(['key1'], $driver->keys('test'));
|
||||
|
||||
parallel([function () use ($driver) {
|
||||
$this->assertEquals([], $driver->keys('test'));
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
@ -1175,6 +1175,7 @@ class Connection implements ConnectionInterface
|
||||
|
||||
/**
|
||||
* Fire the given event if possible.
|
||||
* @param mixed $event
|
||||
*/
|
||||
protected function event($event)
|
||||
{
|
||||
|
@ -1934,7 +1934,8 @@ class Builder
|
||||
return false;
|
||||
}
|
||||
|
||||
$lastId = $results->last()->{$alias};
|
||||
$lastResult = $results->last();
|
||||
$lastId = is_array($lastResult) ? $lastResult[$alias] : $lastResult->{$alias};
|
||||
|
||||
unset($results);
|
||||
} while ($countResults == $count);
|
||||
|
@ -2502,6 +2502,29 @@ class QueryBuilderTest extends TestCase
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
public function testChunkPaginatesUsingIdWithArray()
|
||||
{
|
||||
$builder = $this->getMockQueryBuilder();
|
||||
$builder->orders[] = ['column' => 'foobar', 'direction' => 'asc'];
|
||||
|
||||
$chunk1 = collect([['someIdField' => 1], ['someIdField' => 2]]);
|
||||
$chunk2 = collect([['someIdField' => 10]]);
|
||||
$builder->shouldReceive('forPageAfterId')->once()->with(2, 0, 'someIdField')->andReturnSelf();
|
||||
$builder->shouldReceive('forPageAfterId')->once()->with(2, 2, 'someIdField')->andReturnSelf();
|
||||
$builder->shouldReceive('get')->times(2)->andReturn($chunk1, $chunk2);
|
||||
|
||||
$callbackAssertor = Mockery::mock(stdClass::class);
|
||||
$callbackAssertor->shouldReceive('doSomething')->once()->with($chunk1);
|
||||
$callbackAssertor->shouldReceive('doSomething')->once()->with($chunk2);
|
||||
|
||||
$builder->chunkById(2, function ($results) use ($callbackAssertor) {
|
||||
$callbackAssertor->doSomething($results);
|
||||
}, 'someIdField');
|
||||
|
||||
// Avoid 'This test did not perform any assertions' notice
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
public function testChunkPaginatesUsingIdWithLastChunkPartial()
|
||||
{
|
||||
$builder = $this->getMockQueryBuilder();
|
||||
|
@ -27,6 +27,7 @@ use Hyperf\Database\ConnectionResolverInterface;
|
||||
use Hyperf\Database\Connectors\ConnectionFactory;
|
||||
use Hyperf\Database\Connectors\MySqlConnector;
|
||||
use Hyperf\Database\Migrations\MigrationRepositoryInterface;
|
||||
use Hyperf\DbConnection\Listener\RegisterConnectionResolverListener;
|
||||
use Hyperf\DbConnection\Pool\PoolFactory;
|
||||
|
||||
class ConfigProvider
|
||||
@ -54,6 +55,9 @@ class ConfigProvider
|
||||
GenSeederCommand::class,
|
||||
SeedCommand::class,
|
||||
],
|
||||
'listeners' => [
|
||||
RegisterConnectionResolverListener::class,
|
||||
],
|
||||
'annotations' => [
|
||||
'scan' => [
|
||||
'paths' => [
|
||||
|
@ -14,6 +14,7 @@ namespace Hyperf\DbConnection;
|
||||
|
||||
use Generator;
|
||||
use Hyperf\Database\ConnectionInterface;
|
||||
use Hyperf\Database\ConnectionResolverInterface;
|
||||
use Hyperf\Database\Query\Builder;
|
||||
use Hyperf\Database\Query\Expression;
|
||||
use Hyperf\Utils\ApplicationContext;
|
||||
@ -72,7 +73,7 @@ class Db
|
||||
|
||||
private function __connection($pool = 'default'): ConnectionInterface
|
||||
{
|
||||
$resolver = $this->container->get(ConnectionResolver::class);
|
||||
$resolver = $this->container->get(ConnectionResolverInterface::class);
|
||||
return $resolver->connection($pool);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,48 @@
|
||||
<?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/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace Hyperf\DbConnection\Listener;
|
||||
|
||||
use Hyperf\Contract\ContainerInterface;
|
||||
use Hyperf\Database\ConnectionResolverInterface;
|
||||
use Hyperf\Database\Model\Register;
|
||||
use Hyperf\Event\Contract\ListenerInterface;
|
||||
use Hyperf\Framework\Event\BootApplication;
|
||||
|
||||
class RegisterConnectionResolverListener implements ListenerInterface
|
||||
{
|
||||
/**
|
||||
* @var ContainerInterface
|
||||
*/
|
||||
protected $container;
|
||||
|
||||
public function __construct(ContainerInterface $container)
|
||||
{
|
||||
$this->container = $container;
|
||||
}
|
||||
|
||||
public function listen(): array
|
||||
{
|
||||
return [
|
||||
BootApplication::class,
|
||||
];
|
||||
}
|
||||
|
||||
public function process(object $event)
|
||||
{
|
||||
if ($this->container->has(ConnectionResolverInterface::class)) {
|
||||
Register::setConnectionResolver(
|
||||
$this->container->get(ConnectionResolverInterface::class)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -13,8 +13,8 @@ declare(strict_types=1);
|
||||
namespace Hyperf\DbConnection\Model;
|
||||
|
||||
use Hyperf\Database\ConnectionInterface;
|
||||
use Hyperf\Database\ConnectionResolverInterface;
|
||||
use Hyperf\Database\Model\Model as BaseModel;
|
||||
use Hyperf\DbConnection\ConnectionResolver;
|
||||
use Hyperf\Utils\ApplicationContext;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
@ -33,7 +33,7 @@ class Model extends BaseModel
|
||||
public function getConnection(): ConnectionInterface
|
||||
{
|
||||
$connectionName = $this->getConnectionName();
|
||||
$resolver = $this->getContainer()->get(ConnectionResolver::class);
|
||||
$resolver = $this->getContainer()->get(ConnectionResolverInterface::class);
|
||||
return $resolver->connection($connectionName);
|
||||
}
|
||||
|
||||
|
@ -13,8 +13,8 @@ declare(strict_types=1);
|
||||
namespace HyperfTest\DbConnection;
|
||||
|
||||
use Hyperf\Contract\ConfigInterface;
|
||||
use Hyperf\Database\ConnectionResolverInterface;
|
||||
use Hyperf\DbConnection\Connection;
|
||||
use Hyperf\DbConnection\ConnectionResolver;
|
||||
use Hyperf\DbConnection\Pool\PoolFactory;
|
||||
use Hyperf\Utils\Context;
|
||||
use HyperfTest\DbConnection\Stubs\ConnectionStub;
|
||||
@ -39,7 +39,7 @@ class ConnectionTest extends TestCase
|
||||
{
|
||||
$container = ContainerStub::mockContainer();
|
||||
|
||||
$resolver = $container->get(ConnectionResolver::class);
|
||||
$resolver = $container->get(ConnectionResolverInterface::class);
|
||||
|
||||
$connection = $resolver->connection();
|
||||
|
||||
@ -65,7 +65,7 @@ class ConnectionTest extends TestCase
|
||||
{
|
||||
$container = ContainerStub::mockContainer();
|
||||
|
||||
$resolver = $container->get(ConnectionResolver::class);
|
||||
$resolver = $container->get(ConnectionResolverInterface::class);
|
||||
|
||||
/** @var \Hyperf\Database\Connection $connection */
|
||||
$connection = $resolver->connection();
|
||||
@ -95,7 +95,7 @@ class ConnectionTest extends TestCase
|
||||
{
|
||||
$container = ContainerStub::mockReadWriteContainer();
|
||||
|
||||
$resolver = $container->get(ConnectionResolver::class);
|
||||
$resolver = $container->get(ConnectionResolverInterface::class);
|
||||
|
||||
/** @var \Hyperf\Database\Connection $connection */
|
||||
$connection = $resolver->connection();
|
||||
@ -112,7 +112,7 @@ class ConnectionTest extends TestCase
|
||||
$container = ContainerStub::mockReadWriteContainer();
|
||||
|
||||
parallel([function () use ($container) {
|
||||
$resolver = $container->get(ConnectionResolver::class);
|
||||
$resolver = $container->get(ConnectionResolverInterface::class);
|
||||
|
||||
/** @var \Hyperf\Database\Connection $connection */
|
||||
$connection = $resolver->connection();
|
||||
@ -126,7 +126,7 @@ class ConnectionTest extends TestCase
|
||||
}]);
|
||||
|
||||
parallel([function () use ($container) {
|
||||
$resolver = $container->get(ConnectionResolver::class);
|
||||
$resolver = $container->get(ConnectionResolverInterface::class);
|
||||
|
||||
/** @var \Hyperf\Database\Connection $connection */
|
||||
$connection = $resolver->connection();
|
||||
|
19
src/db-connection/tests/FooModel.php
Normal file
19
src/db-connection/tests/FooModel.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?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/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace HyperfTest\DbConnection;
|
||||
|
||||
use Hyperf\DbConnection\Model\Model;
|
||||
|
||||
class FooModel extends Model
|
||||
{
|
||||
}
|
44
src/db-connection/tests/RelationTest.php
Normal file
44
src/db-connection/tests/RelationTest.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?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/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace HyperfTest\DbConnection;
|
||||
|
||||
use Hyperf\Database\ConnectionResolverInterface;
|
||||
use Hyperf\Database\Model\Register;
|
||||
use Hyperf\Database\Model\Relations\Pivot;
|
||||
use Hyperf\Utils\Context;
|
||||
use HyperfTest\DbConnection\Stubs\ContainerStub;
|
||||
use Mockery;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @coversNothing
|
||||
*/
|
||||
class RelationTest extends TestCase
|
||||
{
|
||||
protected function tearDown()
|
||||
{
|
||||
Mockery::close();
|
||||
Context::set('database.connection.default', null);
|
||||
}
|
||||
|
||||
public function testPivot()
|
||||
{
|
||||
$container = ContainerStub::mockContainer();
|
||||
Register::setConnectionResolver($container->get(ConnectionResolverInterface::class));
|
||||
|
||||
$pivot = Pivot::fromAttributes(new FooModel(), ['created_at' => '2019-12-15 00:00:00'], 'foo', true);
|
||||
|
||||
$this->assertSame(['created_at' => '2019-12-15 00:00:00'], $pivot->toArray());
|
||||
}
|
||||
}
|
@ -15,6 +15,7 @@ namespace HyperfTest\DbConnection\Stubs;
|
||||
use Hyperf\Config\Config;
|
||||
use Hyperf\Contract\ConfigInterface;
|
||||
use Hyperf\Contract\StdoutLoggerInterface;
|
||||
use Hyperf\Database\ConnectionResolverInterface;
|
||||
use Hyperf\Database\Connectors\ConnectionFactory;
|
||||
use Hyperf\Database\Connectors\MySqlConnector;
|
||||
use Hyperf\DbConnection\ConnectionResolver;
|
||||
@ -40,7 +41,7 @@ class ContainerStub
|
||||
$container->shouldReceive('get')->with(PoolFactory::class)->andReturn($factory);
|
||||
|
||||
$resolver = new ConnectionResolver($container);
|
||||
$container->shouldReceive('get')->with(ConnectionResolver::class)->andReturn($resolver);
|
||||
$container->shouldReceive('get')->with(ConnectionResolverInterface::class)->andReturn($resolver);
|
||||
|
||||
$config = new Config([
|
||||
StdoutLoggerInterface::class => [
|
||||
@ -103,7 +104,7 @@ class ContainerStub
|
||||
$container->shouldReceive('get')->with(PoolFactory::class)->andReturn($factory);
|
||||
|
||||
$resolver = new ConnectionResolver($container);
|
||||
$container->shouldReceive('get')->with(ConnectionResolver::class)->andReturn($resolver);
|
||||
$container->shouldReceive('get')->with(ConnectionResolverInterface::class)->andReturn($resolver);
|
||||
|
||||
$config = new Config([
|
||||
'databases' => [
|
||||
|
@ -65,6 +65,12 @@ interface RequestInterface extends ServerRequestInterface
|
||||
*/
|
||||
public function header(string $key, $default = null);
|
||||
|
||||
/**
|
||||
* Retrieve the data from route parameters.
|
||||
* @param mixed $default
|
||||
*/
|
||||
public function route(string $key, $default = null);
|
||||
|
||||
/**
|
||||
* Returns the path being requested relative to the executed script.
|
||||
* The path info always starts with a /.
|
||||
|
@ -14,6 +14,7 @@ namespace Hyperf\HttpServer;
|
||||
|
||||
use Hyperf\HttpMessage\Upload\UploadedFile;
|
||||
use Hyperf\HttpServer\Contract\RequestInterface;
|
||||
use Hyperf\HttpServer\Router\Dispatched;
|
||||
use Hyperf\Utils\Arr;
|
||||
use Hyperf\Utils\Context;
|
||||
use Hyperf\Utils\Str;
|
||||
@ -58,6 +59,20 @@ class Request implements RequestInterface
|
||||
}
|
||||
return data_get($this->getQueryParams(), $key, $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the data from route parameters.
|
||||
*
|
||||
* @param mixed $default
|
||||
*/
|
||||
public function route(string $key, $default = null)
|
||||
{
|
||||
$route = $this->getAttribute(Dispatched::class);
|
||||
if (is_null($route)) {
|
||||
return $default;
|
||||
}
|
||||
return array_key_exists($key, $route->params) ? $route->params[$key] : $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the data from parsed body, if $key is null, will return all parsed body.
|
||||
|
@ -22,17 +22,14 @@ use Psr\Http\Message\ServerRequestInterface;
|
||||
class CoreMiddleware extends \Hyperf\RpcServer\CoreMiddleware
|
||||
{
|
||||
/**
|
||||
* @var \Hyperf\JsonRpc\ResponseBuilder
|
||||
* @var ResponseBuilder
|
||||
*/
|
||||
protected $responseBuilder;
|
||||
|
||||
public function __construct(ContainerInterface $container, Protocol $protocol, string $serverName)
|
||||
public function __construct(ContainerInterface $container, Protocol $protocol, ResponseBuilder $builder, string $serverName)
|
||||
{
|
||||
parent::__construct($container, $protocol, $serverName);
|
||||
$this->responseBuilder = make(ResponseBuilder::class, [
|
||||
'dataFormatter' => $protocol->getDataFormatter(),
|
||||
'packer' => $protocol->getPacker(),
|
||||
]);
|
||||
$this->responseBuilder = $builder;
|
||||
}
|
||||
|
||||
protected function handleFound(Dispatched $dispatched, ServerRequestInterface $request)
|
||||
|
@ -70,7 +70,7 @@ class HttpServer extends Server
|
||||
|
||||
protected function createCoreMiddleware(): CoreMiddlewareInterface
|
||||
{
|
||||
return new HttpCoreMiddleware($this->container, $this->protocol, $this->serverName);
|
||||
return new HttpCoreMiddleware($this->container, $this->protocol, $this->responseBuilder, $this->serverName);
|
||||
}
|
||||
|
||||
protected function initRequestAndResponse(SwooleRequest $request, SwooleResponse $response): array
|
||||
|
@ -50,14 +50,9 @@ class JsonRpcPoolTransporter implements TransporterInterface
|
||||
*/
|
||||
private $recvTimeout = 5;
|
||||
|
||||
/**
|
||||
* TODO: Set config.
|
||||
* @var array
|
||||
*/
|
||||
private $config = [
|
||||
'connect_timeout' => 5.0,
|
||||
'eof' => "\r\n",
|
||||
'options' => [],
|
||||
'settings' => [],
|
||||
'pool' => [
|
||||
'min_connections' => 1,
|
||||
'max_connections' => 32,
|
||||
@ -73,6 +68,9 @@ class JsonRpcPoolTransporter implements TransporterInterface
|
||||
{
|
||||
$this->factory = $factory;
|
||||
$this->config = array_replace_recursive($this->config, $config);
|
||||
|
||||
$this->recvTimeout = $this->config['recv_timeout'] ?? 5.0;
|
||||
$this->connectTimeout = $this->config['connect_timeout'] ?? 5.0;
|
||||
}
|
||||
|
||||
public function send(string $data)
|
||||
@ -83,7 +81,7 @@ class JsonRpcPoolTransporter implements TransporterInterface
|
||||
try {
|
||||
/** @var RpcConnection $client */
|
||||
$client = $connection->getConnection();
|
||||
if ($client->send($data . $this->getEof()) === false) {
|
||||
if ($client->send($data) === false) {
|
||||
if ($client->errCode == 104) {
|
||||
throw new RuntimeException('Connect to server failed.');
|
||||
}
|
||||
@ -114,21 +112,10 @@ class JsonRpcPoolTransporter implements TransporterInterface
|
||||
'host' => $node->host,
|
||||
'port' => $node->port,
|
||||
'connect_timeout' => $this->config['connect_timeout'],
|
||||
'settings' => $this->config['settings'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function getClient(): Pool
|
||||
{
|
||||
$node = $this->getNode();
|
||||
$config = [
|
||||
'host' => $node->host,
|
||||
'port' => $node->port,
|
||||
'connectTimeout' => $this->connectTimeout,
|
||||
];
|
||||
$name = $node->host . ':' . $node->port;
|
||||
return $this->factory->getPool($name, $config);
|
||||
}
|
||||
|
||||
public function getLoadBalancer(): ?LoadBalancerInterface
|
||||
{
|
||||
return $this->loadBalancer;
|
||||
@ -162,11 +149,6 @@ class JsonRpcPoolTransporter implements TransporterInterface
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
private function getEof(): string
|
||||
{
|
||||
return $this->config['eof'] ?? "\r\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* If the load balancer is exists, then the node will select by the load balancer,
|
||||
* otherwise will get a random node.
|
||||
|
@ -44,16 +44,23 @@ class JsonRpcTransporter implements TransporterInterface
|
||||
private $recvTimeout = 5;
|
||||
|
||||
/**
|
||||
* TODO: Set config.
|
||||
* @var array
|
||||
*/
|
||||
private $config;
|
||||
private $config = [];
|
||||
|
||||
public function __construct(array $config = [])
|
||||
{
|
||||
$this->config = array_replace_recursive($this->config, $config);
|
||||
|
||||
$this->recvTimeout = $this->config['recv_timeout'] ?? 5.0;
|
||||
$this->connectTimeout = $this->config['connect_timeout'] ?? 5.0;
|
||||
}
|
||||
|
||||
public function send(string $data)
|
||||
{
|
||||
$client = retry(2, function () use ($data) {
|
||||
$client = $this->getClient();
|
||||
if ($client->send($data . $this->getEof()) === false) {
|
||||
if ($client->send($data) === false) {
|
||||
if ($client->errCode == 104) {
|
||||
throw new RuntimeException('Connect to server failed.');
|
||||
}
|
||||
@ -66,7 +73,7 @@ class JsonRpcTransporter implements TransporterInterface
|
||||
public function getClient(): SwooleClient
|
||||
{
|
||||
$client = new SwooleClient(SWOOLE_SOCK_TCP);
|
||||
$client->set($this->config['options'] ?? []);
|
||||
$client->set($this->config['settings'] ?? []);
|
||||
|
||||
return retry(2, function () use ($client) {
|
||||
$node = $this->getNode();
|
||||
@ -105,11 +112,6 @@ class JsonRpcTransporter implements TransporterInterface
|
||||
return $this->nodes;
|
||||
}
|
||||
|
||||
private function getEof()
|
||||
{
|
||||
return "\r\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* If the load balancer is exists, then the node will select by the load balancer,
|
||||
* otherwise will get a random node.
|
||||
|
@ -18,6 +18,8 @@ use Hyperf\Framework\Event\BootApplication;
|
||||
use Hyperf\JsonRpc\DataFormatter;
|
||||
use Hyperf\JsonRpc\JsonRpcHttpTransporter;
|
||||
use Hyperf\JsonRpc\JsonRpcTransporter;
|
||||
use Hyperf\JsonRpc\Packer\JsonEofPacker;
|
||||
use Hyperf\JsonRpc\Packer\JsonLengthPacker;
|
||||
use Hyperf\JsonRpc\PathGenerator;
|
||||
use Hyperf\Rpc\ProtocolManager;
|
||||
use Hyperf\Utils\Packer\JsonPacker;
|
||||
@ -50,11 +52,19 @@ class RegisterProtocolListener implements ListenerInterface
|
||||
public function process(object $event)
|
||||
{
|
||||
$this->protocolManager->register('jsonrpc', [
|
||||
'packer' => JsonPacker::class,
|
||||
'packer' => JsonEofPacker::class,
|
||||
'transporter' => JsonRpcTransporter::class,
|
||||
'path-generator' => PathGenerator::class,
|
||||
'data-formatter' => DataFormatter::class,
|
||||
]);
|
||||
|
||||
$this->protocolManager->register('jsonrpc-tcp-length-check', [
|
||||
'packer' => JsonLengthPacker::class,
|
||||
'transporter' => JsonRpcTransporter::class,
|
||||
'path-generator' => PathGenerator::class,
|
||||
'data-formatter' => DataFormatter::class,
|
||||
]);
|
||||
|
||||
$this->protocolManager->register('jsonrpc-http', [
|
||||
'packer' => JsonPacker::class,
|
||||
'transporter' => JsonRpcHttpTransporter::class,
|
||||
|
40
src/json-rpc/src/Packer/JsonEofPacker.php
Normal file
40
src/json-rpc/src/Packer/JsonEofPacker.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?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/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace Hyperf\JsonRpc\Packer;
|
||||
|
||||
use Hyperf\Contract\PackerInterface;
|
||||
|
||||
class JsonEofPacker implements PackerInterface
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $eof;
|
||||
|
||||
public function __construct(array $options = [])
|
||||
{
|
||||
$this->eof = $options['settings']['package_eof'] ?? "\r\n";
|
||||
}
|
||||
|
||||
public function pack($data): string
|
||||
{
|
||||
$data = json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
|
||||
return $data . $this->eof;
|
||||
}
|
||||
|
||||
public function unpack(string $data)
|
||||
{
|
||||
return json_decode($data, true);
|
||||
}
|
||||
}
|
53
src/json-rpc/src/Packer/JsonLengthPacker.php
Normal file
53
src/json-rpc/src/Packer/JsonLengthPacker.php
Normal file
@ -0,0 +1,53 @@
|
||||
<?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/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace Hyperf\JsonRpc\Packer;
|
||||
|
||||
use Hyperf\Contract\PackerInterface;
|
||||
|
||||
class JsonLengthPacker implements PackerInterface
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $type;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $length;
|
||||
|
||||
protected $defaultOptions = [
|
||||
'package_length_type' => 'N',
|
||||
'package_body_offset' => 4,
|
||||
];
|
||||
|
||||
public function __construct(array $options = [])
|
||||
{
|
||||
$options = array_merge($this->defaultOptions, $options['settings'] ?? []);
|
||||
|
||||
$this->type = $options['package_length_type'];
|
||||
$this->length = $options['package_body_offset'];
|
||||
}
|
||||
|
||||
public function pack($data): string
|
||||
{
|
||||
$data = json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
return pack($this->type, strlen($data)) . $data;
|
||||
}
|
||||
|
||||
public function unpack(string $data)
|
||||
{
|
||||
$data = substr($data, $this->length);
|
||||
return json_decode($data, true);
|
||||
}
|
||||
}
|
@ -39,7 +39,7 @@ class RpcConnection extends BaseConnection implements ConnectionInterface
|
||||
'host' => 'localhost',
|
||||
'port' => 9501,
|
||||
'connect_timeout' => 5.0,
|
||||
'options' => [],
|
||||
'settings' => [],
|
||||
];
|
||||
|
||||
public function __construct(ContainerInterface $container, Pool $pool, array $config)
|
||||
@ -82,7 +82,7 @@ class RpcConnection extends BaseConnection implements ConnectionInterface
|
||||
$connectTimeout = $this->config['connect_timeout'];
|
||||
|
||||
$client = new SwooleClient(SWOOLE_SOCK_TCP);
|
||||
$client->set($this->config['options'] ?? []);
|
||||
$client->set($this->config['settings'] ?? []);
|
||||
$result = $client->connect($host, $port, $connectTimeout);
|
||||
if ($result === false && ($client->errCode === 114 || $client->errCode === 115)) {
|
||||
// Force close and reconnect to server.
|
||||
|
@ -12,6 +12,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Hyperf\JsonRpc;
|
||||
|
||||
use Hyperf\Contract\ConfigInterface;
|
||||
use Hyperf\Contract\PackerInterface;
|
||||
use Hyperf\Contract\StdoutLoggerInterface;
|
||||
use Hyperf\ExceptionHandler\ExceptionHandlerDispatcher;
|
||||
@ -23,6 +24,7 @@ use Hyperf\Rpc\Protocol;
|
||||
use Hyperf\Rpc\ProtocolManager;
|
||||
use Hyperf\RpcServer\RequestDispatcher;
|
||||
use Hyperf\RpcServer\Server;
|
||||
use Hyperf\Server\Exception\InvalidArgumentException;
|
||||
use Hyperf\Server\ServerManager;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
@ -41,6 +43,16 @@ class TcpServer extends Server
|
||||
*/
|
||||
protected $packer;
|
||||
|
||||
/**
|
||||
* @var ProtocolManager
|
||||
*/
|
||||
protected $protocolManager;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $serverConfig;
|
||||
|
||||
public function __construct(
|
||||
ContainerInterface $container,
|
||||
RequestDispatcher $dispatcher,
|
||||
@ -48,20 +60,55 @@ class TcpServer extends Server
|
||||
ProtocolManager $protocolManager,
|
||||
StdoutLoggerInterface $logger
|
||||
) {
|
||||
$protocol = new Protocol($container, $protocolManager, 'jsonrpc');
|
||||
parent::__construct($container, $dispatcher, $exceptionDispatcher, $logger);
|
||||
|
||||
parent::__construct($container, $dispatcher, $exceptionDispatcher, $protocol, $logger);
|
||||
$this->protocolManager = $protocolManager;
|
||||
}
|
||||
|
||||
$this->packer = $protocol->getPacker();
|
||||
public function initCoreMiddleware(string $serverName): void
|
||||
{
|
||||
$this->initServerConfig($serverName);
|
||||
|
||||
$this->initProtocol();
|
||||
|
||||
parent::initCoreMiddleware($serverName);
|
||||
}
|
||||
|
||||
protected function initProtocol()
|
||||
{
|
||||
$protocol = 'jsonrpc';
|
||||
if ($this->isLengthCheck()) {
|
||||
$protocol = 'jsonrpc-tcp-length-check';
|
||||
}
|
||||
|
||||
$this->protocol = new Protocol($this->container, $this->protocolManager, $protocol, $this->serverConfig);
|
||||
$this->packer = $this->protocol->getPacker();
|
||||
$this->responseBuilder = make(ResponseBuilder::class, [
|
||||
'dataFormatter' => $protocol->getDataFormatter(),
|
||||
'dataFormatter' => $this->protocol->getDataFormatter(),
|
||||
'packer' => $this->packer,
|
||||
]);
|
||||
}
|
||||
|
||||
protected function isLengthCheck(): bool
|
||||
{
|
||||
return boolval($this->serverConfig['settings']['open_length_check'] ?? false);
|
||||
}
|
||||
|
||||
protected function initServerConfig(string $serverName): array
|
||||
{
|
||||
$servers = $this->container->get(ConfigInterface::class)->get('server.servers', []);
|
||||
foreach ($servers as $server) {
|
||||
if ($server['name'] === $serverName) {
|
||||
return $this->serverConfig = $server;
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException(sprintf('Server name %s is invalid.', $serverName));
|
||||
}
|
||||
|
||||
protected function createCoreMiddleware(): CoreMiddlewareInterface
|
||||
{
|
||||
return new CoreMiddleware($this->container, $this->protocol, $this->serverName);
|
||||
return new CoreMiddleware($this->container, $this->protocol, $this->responseBuilder, $this->serverName);
|
||||
}
|
||||
|
||||
protected function buildResponse(int $fd, SwooleServer $server): ResponseInterface
|
||||
|
@ -27,6 +27,7 @@ use Hyperf\JsonRpc\DataFormatter;
|
||||
use Hyperf\JsonRpc\JsonRpcHttpTransporter;
|
||||
use Hyperf\JsonRpc\JsonRpcTransporter;
|
||||
use Hyperf\JsonRpc\NormalizeDataFormatter;
|
||||
use Hyperf\JsonRpc\Packer\JsonEofPacker;
|
||||
use Hyperf\JsonRpc\PathGenerator;
|
||||
use Hyperf\JsonRpc\ResponseBuilder;
|
||||
use Hyperf\Logger\Logger;
|
||||
@ -60,7 +61,11 @@ class AnyParamCoreMiddlewareTest extends TestCase
|
||||
CalculatorService::class, 'sum',
|
||||
]);
|
||||
$protocol = new Protocol($container, $container->get(ProtocolManager::class), 'jsonrpc');
|
||||
$middleware = new CoreMiddleware($container, $protocol, 'jsonrpc');
|
||||
$builder = $container->make(ResponseBuilder::class, [
|
||||
'dataFormatter' => $protocol->getDataFormatter(),
|
||||
'packer' => $protocol->getPacker(),
|
||||
]);
|
||||
$middleware = new CoreMiddleware($container, $protocol, $builder, 'jsonrpc');
|
||||
$handler = \Mockery::mock(RequestHandlerInterface::class);
|
||||
$request = (new Request('POST', new Uri('/CalculatorService/sum')))
|
||||
->withParsedBody([
|
||||
@ -86,7 +91,11 @@ class AnyParamCoreMiddlewareTest extends TestCase
|
||||
CalculatorService::class, 'array',
|
||||
]);
|
||||
$protocol = new Protocol($container, $container->get(ProtocolManager::class), 'jsonrpc');
|
||||
$middleware = new CoreMiddleware($container, $protocol, 'jsonrpc');
|
||||
$builder = $container->make(ResponseBuilder::class, [
|
||||
'dataFormatter' => $protocol->getDataFormatter(),
|
||||
'packer' => $protocol->getPacker(),
|
||||
]);
|
||||
$middleware = new CoreMiddleware($container, $protocol, $builder, 'jsonrpc');
|
||||
$handler = \Mockery::mock(RequestHandlerInterface::class);
|
||||
$request = (new Request('POST', new Uri('/CalculatorService/array')))
|
||||
->withParsedBody([1, 2]);
|
||||
@ -109,7 +118,11 @@ class AnyParamCoreMiddlewareTest extends TestCase
|
||||
CalculatorService::class, 'divide',
|
||||
]);
|
||||
$protocol = new Protocol($container, $container->get(ProtocolManager::class), 'jsonrpc');
|
||||
$middleware = new CoreMiddleware($container, $protocol, 'jsonrpc');
|
||||
$builder = $container->make(ResponseBuilder::class, [
|
||||
'dataFormatter' => $protocol->getDataFormatter(),
|
||||
'packer' => $protocol->getPacker(),
|
||||
]);
|
||||
$middleware = new CoreMiddleware($container, $protocol, $builder, 'jsonrpc');
|
||||
$handler = \Mockery::mock(RequestHandlerInterface::class);
|
||||
$request = (new Request('POST', new Uri('/CalculatorService/divide')))
|
||||
->withParsedBody([3, 0]);
|
||||
@ -181,6 +194,10 @@ class AnyParamCoreMiddlewareTest extends TestCase
|
||||
return new ResponseBuilder(...array_values($args));
|
||||
});
|
||||
$container->shouldReceive('get')->with(RequestDispatcher::class)->andReturn(new RequestDispatcher($container));
|
||||
$container->shouldReceive('make')->with(JsonPacker::class, \Mockery::any())->andReturn(new JsonPacker());
|
||||
$container->shouldReceive('make')->with(JsonEofPacker::class, \Mockery::any())->andReturnUsing(function ($_, $args) {
|
||||
return new JsonEofPacker(...array_values($args));
|
||||
});
|
||||
|
||||
ApplicationContext::setContainer($container);
|
||||
return $container;
|
||||
|
@ -27,6 +27,7 @@ use Hyperf\JsonRpc\CoreMiddleware;
|
||||
use Hyperf\JsonRpc\DataFormatter;
|
||||
use Hyperf\JsonRpc\Exception\Handler\HttpExceptionHandler;
|
||||
use Hyperf\JsonRpc\JsonRpcTransporter;
|
||||
use Hyperf\JsonRpc\Packer\JsonEofPacker;
|
||||
use Hyperf\JsonRpc\PathGenerator;
|
||||
use Hyperf\JsonRpc\ResponseBuilder;
|
||||
use Hyperf\Logger\Logger;
|
||||
@ -58,7 +59,11 @@ class CoreMiddlewareTest extends TestCase
|
||||
CalculatorService::class, 'add',
|
||||
]);
|
||||
$protocol = new Protocol($container, $container->get(ProtocolManager::class), 'jsonrpc');
|
||||
$middleware = new CoreMiddleware($container, $protocol, 'jsonrpc');
|
||||
$builder = $container->make(ResponseBuilder::class, [
|
||||
'dataFormatter' => $protocol->getDataFormatter(),
|
||||
'packer' => $protocol->getPacker(),
|
||||
]);
|
||||
$middleware = new CoreMiddleware($container, $protocol, $builder, 'jsonrpc');
|
||||
$handler = \Mockery::mock(RequestHandlerInterface::class);
|
||||
$request = (new Request('POST', new Uri('/CalculatorService/add')))
|
||||
->withParsedBody([1, 2]);
|
||||
@ -80,7 +85,11 @@ class CoreMiddlewareTest extends TestCase
|
||||
CalculatorService::class, 'array',
|
||||
]);
|
||||
$protocol = new Protocol($container, $container->get(ProtocolManager::class), 'jsonrpc');
|
||||
$middleware = new CoreMiddleware($container, $protocol, 'jsonrpc');
|
||||
$builder = $container->make(ResponseBuilder::class, [
|
||||
'dataFormatter' => $protocol->getDataFormatter(),
|
||||
'packer' => $protocol->getPacker(),
|
||||
]);
|
||||
$middleware = new CoreMiddleware($container, $protocol, $builder, 'jsonrpc');
|
||||
$handler = \Mockery::mock(RequestHandlerInterface::class);
|
||||
$request = (new Request('POST', new Uri('/CalculatorService/array')))
|
||||
->withParsedBody([1, 2]);
|
||||
@ -102,7 +111,11 @@ class CoreMiddlewareTest extends TestCase
|
||||
CalculatorService::class, 'divide',
|
||||
]);
|
||||
$protocol = new Protocol($container, $container->get(ProtocolManager::class), 'jsonrpc');
|
||||
$middleware = new CoreMiddleware($container, $protocol, 'jsonrpc');
|
||||
$builder = $container->make(ResponseBuilder::class, [
|
||||
'dataFormatter' => $protocol->getDataFormatter(),
|
||||
'packer' => $protocol->getPacker(),
|
||||
]);
|
||||
$middleware = new CoreMiddleware($container, $protocol, $builder, 'jsonrpc');
|
||||
$handler = \Mockery::mock(RequestHandlerInterface::class);
|
||||
$request = (new Request('POST', new Uri('/CalculatorService/divide')))
|
||||
->withParsedBody([3, 0]);
|
||||
@ -133,7 +146,11 @@ class CoreMiddlewareTest extends TestCase
|
||||
CalculatorService::class, 'divide',
|
||||
]);
|
||||
$protocol = new Protocol($container, $container->get(ProtocolManager::class), 'jsonrpc');
|
||||
$middleware = new CoreMiddleware($container, $protocol, 'jsonrpc');
|
||||
$builder = $container->make(ResponseBuilder::class, [
|
||||
'dataFormatter' => $protocol->getDataFormatter(),
|
||||
'packer' => $protocol->getPacker(),
|
||||
]);
|
||||
$middleware = new CoreMiddleware($container, $protocol, $builder, 'jsonrpc');
|
||||
$handler = \Mockery::mock(RequestHandlerInterface::class);
|
||||
$request = (new Request('POST', new Uri('/CalculatorService/divide')))
|
||||
->withParsedBody([3, 0]);
|
||||
@ -200,7 +217,10 @@ class CoreMiddlewareTest extends TestCase
|
||||
->andReturnUsing(function ($class, $args) {
|
||||
return new ResponseBuilder(...array_values($args));
|
||||
});
|
||||
|
||||
$container->shouldReceive('make')->with(JsonPacker::class, \Mockery::any())->andReturn(new JsonPacker());
|
||||
$container->shouldReceive('make')->with(JsonEofPacker::class, \Mockery::any())->andReturnUsing(function ($_, $args) {
|
||||
return new JsonEofPacker(...array_values($args));
|
||||
});
|
||||
ApplicationContext::setContainer($container);
|
||||
return $container;
|
||||
}
|
||||
|
67
src/json-rpc/tests/JsonPackerTest.php
Normal file
67
src/json-rpc/tests/JsonPackerTest.php
Normal file
@ -0,0 +1,67 @@
|
||||
<?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/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace HyperfTest\JsonRpc;
|
||||
|
||||
use Hyperf\JsonRpc\Packer\JsonEofPacker;
|
||||
use Hyperf\JsonRpc\Packer\JsonLengthPacker;
|
||||
use Hyperf\Utils\Str;
|
||||
use Mockery;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @coversNothing
|
||||
*/
|
||||
class JsonPackerTest extends TestCase
|
||||
{
|
||||
protected function tearDown()
|
||||
{
|
||||
Mockery::close();
|
||||
}
|
||||
|
||||
public function testJsonEofPacker()
|
||||
{
|
||||
$packer = new JsonEofPacker([
|
||||
'settings' => [
|
||||
'package_eof' => "\r\n",
|
||||
],
|
||||
]);
|
||||
|
||||
$string = $packer->pack(['id' => 1]);
|
||||
$this->assertTrue(Str::endsWith($string, "\r\n"));
|
||||
|
||||
$packer = new JsonEofPacker([
|
||||
'settings' => [
|
||||
'package_eof' => "\r\n\r\n",
|
||||
],
|
||||
]);
|
||||
$string = $packer->pack(['id' => 1]);
|
||||
$this->assertTrue(Str::endsWith($string, "\r\n\r\n"));
|
||||
}
|
||||
|
||||
public function testPackOpenLengthCheck()
|
||||
{
|
||||
$packer = new JsonLengthPacker([
|
||||
'settings' => [
|
||||
'open_length_check' => true,
|
||||
'package_length_type' => 'N',
|
||||
'package_length_offset' => 0,
|
||||
'package_body_offset' => 4,
|
||||
],
|
||||
]);
|
||||
$string = $packer->pack($data = ['id' => 1]);
|
||||
$expected = json_encode($data);
|
||||
$this->assertSame(pack('N', strlen($expected)) . $expected, $string);
|
||||
$this->assertSame($data, $packer->unpack($string));
|
||||
}
|
||||
}
|
@ -12,8 +12,17 @@ declare(strict_types=1);
|
||||
|
||||
namespace HyperfTest\JsonRpc;
|
||||
|
||||
use Hyperf\Di\Container;
|
||||
use Hyperf\JsonRpc\JsonRpcPoolTransporter;
|
||||
use Hyperf\JsonRpc\Packer\JsonLengthPacker;
|
||||
use Hyperf\JsonRpc\Pool\Frequency;
|
||||
use Hyperf\JsonRpc\Pool\PoolFactory;
|
||||
use Hyperf\JsonRpc\Pool\RpcPool;
|
||||
use Hyperf\LoadBalancer\Node;
|
||||
use Hyperf\Pool\Channel;
|
||||
use Hyperf\Pool\PoolOption;
|
||||
use Hyperf\Utils\ApplicationContext;
|
||||
use HyperfTest\JsonRpc\Stub\RpcPoolStub;
|
||||
use Mockery;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
@ -31,9 +40,80 @@ class JsonRpcPoolTransporterTest extends TestCase
|
||||
public function testJsonRpcPoolTransporterConfig()
|
||||
{
|
||||
$factory = Mockery::mock(PoolFactory::class);
|
||||
$transporter = new JsonRpcPoolTransporter($factory, ['pool' => ['min_connections' => 10]]);
|
||||
$transporter = new JsonRpcPoolTransporter($factory, [
|
||||
'pool' => ['min_connections' => 10],
|
||||
'settings' => $settings = [
|
||||
'open_length_check' => true,
|
||||
'package_length_type' => 'N',
|
||||
'package_length_offset' => 0,
|
||||
'package_body_offset' => 4,
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertSame(10, $transporter->getConfig()['pool']['min_connections']);
|
||||
$this->assertSame(32, $transporter->getConfig()['pool']['max_connections']);
|
||||
$this->assertSame($settings, $transporter->getConfig()['settings']);
|
||||
}
|
||||
|
||||
public function testJsonRpcPoolTransporterSendLengthCheck()
|
||||
{
|
||||
$container = $this->getContainer();
|
||||
$factory = $container->get(PoolFactory::class);
|
||||
$transporter = new JsonRpcPoolTransporter($factory, $options = [
|
||||
'pool' => ['min_connections' => 10],
|
||||
'settings' => [
|
||||
'open_length_check' => true,
|
||||
'package_length_type' => 'N',
|
||||
'package_length_offset' => 0,
|
||||
'package_body_offset' => 4,
|
||||
],
|
||||
]);
|
||||
$transporter->setNodes([new Node('127.0.0.1', 9504)]);
|
||||
|
||||
$packer = new JsonLengthPacker($options);
|
||||
|
||||
$string = $transporter->send($packer->pack($data = ['id' => $id = uniqid()]));
|
||||
|
||||
$this->assertSame($data, $packer->unpack($string));
|
||||
}
|
||||
|
||||
public function testJsonRpcPoolTransporterSendEofCheck()
|
||||
{
|
||||
$container = $this->getContainer();
|
||||
$factory = $container->get(PoolFactory::class);
|
||||
$transporter = new JsonRpcPoolTransporter($factory, $options = [
|
||||
'pool' => ['min_connections' => 10],
|
||||
'settings' => [
|
||||
'open_eof_split' => true,
|
||||
'package_eof' => "\r\n",
|
||||
],
|
||||
]);
|
||||
$transporter->setNodes([new Node('127.0.0.1', 9504)]);
|
||||
|
||||
$packer = new JsonLengthPacker($options);
|
||||
|
||||
$string = $transporter->send($packer->pack($data = ['id' => $id = uniqid()]));
|
||||
|
||||
$this->assertSame($data, $packer->unpack($string));
|
||||
}
|
||||
|
||||
protected function getContainer()
|
||||
{
|
||||
$container = Mockery::mock(Container::class);
|
||||
ApplicationContext::setContainer($container);
|
||||
|
||||
$container->shouldReceive('get')->with(PoolFactory::class)->andReturn(new PoolFactory($container));
|
||||
$container->shouldReceive('make')->with(RpcPool::class, Mockery::any())->andReturnUsing(function ($_, $args) use ($container) {
|
||||
return new RpcPoolStub($container, $args['name'], $args['config']);
|
||||
});
|
||||
$container->shouldReceive('make')->with(Frequency::class, Mockery::any())->andReturn(null);
|
||||
$container->shouldReceive('make')->with(PoolOption::class, Mockery::any())->andReturnUsing(function ($_, $args) {
|
||||
return new PoolOption(...array_values($args));
|
||||
});
|
||||
$container->shouldReceive('make')->with(Channel::class, Mockery::any())->andReturnUsing(function ($_, $args) {
|
||||
return new Channel(10);
|
||||
});
|
||||
|
||||
return $container;
|
||||
}
|
||||
}
|
||||
|
45
src/json-rpc/tests/Stub/RpcConnectionStub.php
Normal file
45
src/json-rpc/tests/Stub/RpcConnectionStub.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?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/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace HyperfTest\JsonRpc\Stub;
|
||||
|
||||
use Hyperf\JsonRpc\Pool\RpcConnection;
|
||||
|
||||
class RpcConnectionStub extends RpcConnection
|
||||
{
|
||||
public $lastData = '';
|
||||
|
||||
public function __call($name, $arguments)
|
||||
{
|
||||
if ($name == 'send') {
|
||||
$this->lastData = $arguments[0];
|
||||
return strlen($arguments[0]);
|
||||
}
|
||||
|
||||
if ($name == 'recv') {
|
||||
return $this->lastData;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function __get($name)
|
||||
{
|
||||
return false;
|
||||
// return $this->connection->{$name};
|
||||
}
|
||||
|
||||
public function reconnect(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
24
src/json-rpc/tests/Stub/RpcPoolStub.php
Normal file
24
src/json-rpc/tests/Stub/RpcPoolStub.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?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/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace HyperfTest\JsonRpc\Stub;
|
||||
|
||||
use Hyperf\Contract\ConnectionInterface;
|
||||
use Hyperf\JsonRpc\Pool\RpcPool;
|
||||
|
||||
class RpcPoolStub extends RpcPool
|
||||
{
|
||||
protected function createConnection(): ConnectionInterface
|
||||
{
|
||||
return new RpcConnectionStub($this->container, $this, $this->config);
|
||||
}
|
||||
}
|
@ -104,6 +104,11 @@ class LoggerFactory
|
||||
foreach ($handlerConfigs as $value) {
|
||||
$class = $value['class'] ?? $defaultHandlerConfig['class'];
|
||||
$constructor = $value['constructor'] ?? $defaultHandlerConfig['constructor'];
|
||||
if (isset($value['formatter'])) {
|
||||
if (! isset($value['formatter']['constructor'])) {
|
||||
$value['formatter']['constructor'] = $defaultFormatterConfig['constructor'];
|
||||
}
|
||||
}
|
||||
$formatterConfig = $value['formatter'] ?? $defaultFormatterConfig;
|
||||
|
||||
$handlers[] = $this->handler($class, $constructor, $formatterConfig);
|
||||
|
@ -130,12 +130,18 @@ class LoggerFactoryTest extends TestCase
|
||||
'stream' => BASE_PATH . '/runtime/logs/hyperf.log',
|
||||
'level' => \Monolog\Logger::DEBUG,
|
||||
],
|
||||
'formatter' => [
|
||||
'class' => \Monolog\Formatter\LineFormatter::class,
|
||||
],
|
||||
],
|
||||
[
|
||||
'class' => \Monolog\Handler\TestHandler::class,
|
||||
'constructor' => [
|
||||
'level' => \Monolog\Logger::DEBUG,
|
||||
],
|
||||
'formatter' => [
|
||||
'class' => \Monolog\Formatter\LineFormatter::class,
|
||||
],
|
||||
],
|
||||
],
|
||||
'formatter' => [
|
||||
|
@ -41,7 +41,7 @@ class MetricMiddleware implements MiddlewareInterface
|
||||
{
|
||||
$labels = [
|
||||
'request_status' => '500', //default to 500 incase uncaught exception occur
|
||||
'request_path' => $request->getRequestTarget(),
|
||||
'request_path' => $request->getUri()->getPath(),
|
||||
'request_method' => $request->getMethod(),
|
||||
];
|
||||
$timer = new Timer('http_requests', $labels);
|
||||
|
@ -15,6 +15,7 @@ namespace HyperfTest\ModelCache\Stub;
|
||||
use Hyperf\Config\Config;
|
||||
use Hyperf\Contract\ConfigInterface;
|
||||
use Hyperf\Contract\StdoutLoggerInterface;
|
||||
use Hyperf\Database\ConnectionResolverInterface;
|
||||
use Hyperf\Database\Connectors\ConnectionFactory;
|
||||
use Hyperf\Database\Connectors\MySqlConnector;
|
||||
use Hyperf\DbConnection\ConnectionResolver;
|
||||
@ -47,7 +48,7 @@ class ContainerStub
|
||||
$container->shouldReceive('get')->with(PoolFactory::class)->andReturn($factory);
|
||||
|
||||
$resolver = new ConnectionResolver($container);
|
||||
$container->shouldReceive('get')->with(ConnectionResolver::class)->andReturn($resolver);
|
||||
$container->shouldReceive('get')->with(ConnectionResolverInterface::class)->andReturn($resolver);
|
||||
|
||||
$config = new Config([
|
||||
StdoutLoggerInterface::class => [
|
||||
|
1
src/rate-limit/.gitattributes
vendored
Normal file
1
src/rate-limit/.gitattributes
vendored
Normal file
@ -0,0 +1 @@
|
||||
/tests export-ignore
|
@ -40,6 +40,7 @@
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"HyperfTest\\RateLimit\\": "tests/"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
|
9
src/rate-limit/publish/rate_limit.php
Normal file
9
src/rate-limit/publish/rate_limit.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'create' => 1,
|
||||
'consume' => 1,
|
||||
'capacity' => 2,
|
||||
'limitCallback' => [],
|
||||
'waitTimeout' => 1,
|
||||
];
|
@ -23,22 +23,22 @@ class RateLimit extends AbstractAnnotation
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
public $create = 1;
|
||||
public $create;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
public $consume = 1;
|
||||
public $consume;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
public $capacity = 2;
|
||||
public $capacity;
|
||||
|
||||
/**
|
||||
* @var callable
|
||||
*/
|
||||
public $limitCallback = [];
|
||||
public $limitCallback;
|
||||
|
||||
/**
|
||||
* @var callable|string
|
||||
@ -48,5 +48,5 @@ class RateLimit extends AbstractAnnotation
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
public $waitTimeout = 1;
|
||||
public $waitTimeout;
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ namespace Hyperf\RateLimit\Aspect;
|
||||
|
||||
use bandwidthThrottle\tokenBucket\storage\StorageException;
|
||||
use Hyperf\Contract\ConfigInterface;
|
||||
use Hyperf\Contract\StdoutLoggerInterface;
|
||||
use Hyperf\Di\Annotation\Aspect;
|
||||
use Hyperf\Di\Aop\AroundInterface;
|
||||
use Hyperf\Di\Aop\ProceedingJoinPoint;
|
||||
@ -21,6 +22,7 @@ use Hyperf\HttpServer\Contract\RequestInterface;
|
||||
use Hyperf\RateLimit\Annotation\RateLimit;
|
||||
use Hyperf\RateLimit\Exception\RateLimitException;
|
||||
use Hyperf\RateLimit\Handler\RateLimitHandler;
|
||||
use Hyperf\Utils\ApplicationContext;
|
||||
use Swoole\Coroutine;
|
||||
|
||||
/**
|
||||
@ -57,7 +59,7 @@ class RateLimitAnnotationAspect implements AroundInterface
|
||||
public function __construct(ConfigInterface $config, RequestInterface $request, RateLimitHandler $rateLimitHandler)
|
||||
{
|
||||
$this->annotationProperty = get_object_vars(new RateLimit());
|
||||
$this->config = $config->get('rate-limit', []);
|
||||
$this->config = $this->parseConfig($config);
|
||||
$this->request = $request;
|
||||
$this->rateLimitHandler = $rateLimitHandler;
|
||||
}
|
||||
@ -125,4 +127,34 @@ class RateLimitAnnotationAspect implements AroundInterface
|
||||
$metadata->method[RateLimit::class] ?? null,
|
||||
];
|
||||
}
|
||||
|
||||
public function getConfig(): array
|
||||
{
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
protected function parseConfig(ConfigInterface $config)
|
||||
{
|
||||
if ($config->has('rate_limit')) {
|
||||
return $config->get('rate_limit');
|
||||
}
|
||||
|
||||
// TODO: Removed in v1.2
|
||||
if ($config->has('rate-limit')) {
|
||||
if (ApplicationContext::hasContainer() && ApplicationContext::getContainer()->has(StdoutLoggerInterface::class)) {
|
||||
$logger = ApplicationContext::getContainer()->get(StdoutLoggerInterface::class);
|
||||
$logger->warning('Config rate-limit.php will be removed in v1.2, please use rate_limit.php instead.');
|
||||
}
|
||||
|
||||
return $config->get('rate-limit');
|
||||
}
|
||||
|
||||
return [
|
||||
'create' => 1,
|
||||
'consume' => 1,
|
||||
'capacity' => 2,
|
||||
'limitCallback' => [],
|
||||
'waitTimeout' => 1,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,14 @@ class ConfigProvider
|
||||
],
|
||||
],
|
||||
],
|
||||
'publish' => [
|
||||
[
|
||||
'id' => 'config',
|
||||
'description' => 'The config for rate-limit.',
|
||||
'source' => __DIR__ . '/../publish/rate_limit.php',
|
||||
'destination' => BASE_PATH . '/config/autoload/rate_limit.php',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
92
src/rate-limit/tests/RateLimitTest.php
Normal file
92
src/rate-limit/tests/RateLimitTest.php
Normal file
@ -0,0 +1,92 @@
|
||||
<?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/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace HyperfTest\RateLimit;
|
||||
|
||||
use Hyperf\Config\Config;
|
||||
use Hyperf\Contract\ConfigInterface;
|
||||
use Hyperf\Contract\StdoutLoggerInterface;
|
||||
use Hyperf\HttpServer\Contract\RequestInterface;
|
||||
use Hyperf\RateLimit\Aspect\RateLimitAnnotationAspect;
|
||||
use Hyperf\RateLimit\Handler\RateLimitHandler;
|
||||
use Hyperf\Utils\ApplicationContext;
|
||||
use Mockery;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @coversNothing
|
||||
*/
|
||||
class RateLimitTest extends TestCase
|
||||
{
|
||||
protected function tearDown()
|
||||
{
|
||||
Mockery::close();
|
||||
}
|
||||
|
||||
public function testAspectConfig()
|
||||
{
|
||||
$request = Mockery::mock(RequestInterface::class);
|
||||
$handler = Mockery::mock(RateLimitHandler::class);
|
||||
$aspect = new RateLimitAnnotationAspect(new Config($config = [
|
||||
'rate_limit' => [
|
||||
'create' => 1,
|
||||
'consume' => 1,
|
||||
'capacity' => 2,
|
||||
'limitCallback' => [],
|
||||
'waitTimeout' => 1,
|
||||
],
|
||||
]), $request, $handler);
|
||||
|
||||
$this->assertSame($config['rate_limit'], $aspect->getConfig());
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
public function testAspectConfigDeprecated()
|
||||
{
|
||||
$container = $this->getContainer();
|
||||
$container->shouldReceive('has')->andReturn(true);
|
||||
$container->shouldReceive('get')->with(StdoutLoggerInterface::class)->andReturnUsing(function () {
|
||||
$logger = Mockery::mock(StdoutLoggerInterface::class);
|
||||
$logger->shouldReceive('warning')->andReturnUsing(function ($message) {
|
||||
$this->assertSame('Config rate-limit.php will be removed in v1.2, please use rate_limit.php instead.', $message);
|
||||
});
|
||||
|
||||
return $logger;
|
||||
});
|
||||
|
||||
$request = Mockery::mock(RequestInterface::class);
|
||||
$handler = Mockery::mock(RateLimitHandler::class);
|
||||
$aspect = new RateLimitAnnotationAspect(new Config($config = [
|
||||
'rate-limit' => [
|
||||
'create' => 1,
|
||||
'consume' => 1,
|
||||
'capacity' => 2,
|
||||
'limitCallback' => [],
|
||||
'waitTimeout' => 1,
|
||||
],
|
||||
]), $request, $handler);
|
||||
|
||||
$this->assertSame($config['rate-limit'], $aspect->getConfig());
|
||||
}
|
||||
|
||||
protected function getContainer()
|
||||
{
|
||||
$container = Mockery::mock(ContainerInterface::class);
|
||||
ApplicationContext::setContainer($container);
|
||||
|
||||
return $container;
|
||||
}
|
||||
}
|
@ -87,7 +87,7 @@ abstract class AbstractServiceClient
|
||||
{
|
||||
$this->container = $container;
|
||||
$this->loadBalancerManager = $container->get(LoadBalancerManager::class);
|
||||
$protocol = new Protocol($container, $container->get(ProtocolManager::class), $this->protocol);
|
||||
$protocol = new Protocol($container, $container->get(ProtocolManager::class), $this->protocol, $this->getOptions());
|
||||
$loadBalancer = $this->createLoadBalancer(...$this->createNodes());
|
||||
$transporter = $protocol->getTransporter()->setLoadBalancer($loadBalancer);
|
||||
$this->client = make(Client::class)
|
||||
@ -137,6 +137,34 @@ abstract class AbstractServiceClient
|
||||
return $loadBalancer;
|
||||
}
|
||||
|
||||
protected function getOptions(): array
|
||||
{
|
||||
$consumer = $this->getConsumerConfig();
|
||||
|
||||
return $consumer['options'] ?? [];
|
||||
}
|
||||
|
||||
protected function getConsumerConfig(): array
|
||||
{
|
||||
if (! $this->container->has(ConfigInterface::class)) {
|
||||
throw new RuntimeException(sprintf('The object implementation of %s missing.', ConfigInterface::class));
|
||||
}
|
||||
|
||||
$config = $this->container->get(ConfigInterface::class);
|
||||
|
||||
// According to the registry config of the consumer, retrieve the nodes.
|
||||
$consumers = $config->get('services.consumers', []);
|
||||
$config = [];
|
||||
foreach ($consumers as $consumer) {
|
||||
if (isset($consumer['name']) && $consumer['name'] === $this->serviceName) {
|
||||
$config = $consumer;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create nodes the first time.
|
||||
*
|
||||
@ -144,24 +172,11 @@ abstract class AbstractServiceClient
|
||||
*/
|
||||
protected function createNodes(): array
|
||||
{
|
||||
if (! $this->container->has(ConfigInterface::class)) {
|
||||
throw new RuntimeException(sprintf('The object implementation of %s missing.', ConfigInterface::class));
|
||||
}
|
||||
$refreshCallback = null;
|
||||
$config = $this->container->get(ConfigInterface::class);
|
||||
|
||||
// According to the registry config of the consumer, retrieve the nodes.
|
||||
$consumers = $config->get('services.consumers', []);
|
||||
$isMatch = false;
|
||||
foreach ($consumers as $consumer) {
|
||||
if (isset($consumer['name']) && $consumer['name'] === $this->serviceName) {
|
||||
$isMatch = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$consumer = $this->getConsumerConfig();
|
||||
|
||||
// Current $consumer is the config of the specified consumer.
|
||||
if ($isMatch && isset($consumer['registry']['protocol'], $consumer['registry']['address'])) {
|
||||
if (isset($consumer['registry']['protocol'], $consumer['registry']['address'])) {
|
||||
// According to the protocol and address of the registry, retrieve the nodes.
|
||||
switch ($registryProtocol = $consumer['registry']['protocol'] ?? '') {
|
||||
case 'consul':
|
||||
@ -177,6 +192,7 @@ abstract class AbstractServiceClient
|
||||
}
|
||||
return [$nodes, $refreshCallback];
|
||||
}
|
||||
|
||||
// Not exists the registry config, then looking for the 'nodes' property.
|
||||
if (isset($consumer['nodes'])) {
|
||||
$nodes = [];
|
||||
@ -190,6 +206,7 @@ abstract class AbstractServiceClient
|
||||
}
|
||||
return [$nodes, $refreshCallback];
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException('Config of registry or nodes missing.');
|
||||
}
|
||||
|
||||
|
@ -47,9 +47,6 @@ class RpcClientPool extends Pool
|
||||
parent::__construct($container, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
|
@ -41,9 +41,6 @@ class RouteCollector
|
||||
|
||||
/**
|
||||
* Constructs a route collector.
|
||||
*
|
||||
* @param RouteParser $routeParser
|
||||
* @param DataGenerator $dataGenerator
|
||||
*/
|
||||
public function __construct(RouteParser $routeParser, DataGenerator $dataGenerator)
|
||||
{
|
||||
@ -58,9 +55,7 @@ class RouteCollector
|
||||
* The syntax used in the $route string depends on the used route parser.
|
||||
*
|
||||
* @param string|string[] $httpMethod
|
||||
* @param string $route
|
||||
* @param mixed $handler
|
||||
* @param array $options
|
||||
*/
|
||||
public function addRoute(string $route, $handler, array $options = [])
|
||||
{
|
||||
@ -80,7 +75,6 @@ class RouteCollector
|
||||
* All routes created in the passed callback will have the given group prefix prepended.
|
||||
*
|
||||
* @param string $prefix
|
||||
* @param callable $callback
|
||||
*/
|
||||
public function addGroup($prefix, callable $callback, array $options = [])
|
||||
{
|
||||
|
@ -81,13 +81,11 @@ abstract class Server implements OnReceiveInterface, MiddlewareInitializerInterf
|
||||
ContainerInterface $container,
|
||||
DispatcherInterface $dispatcher,
|
||||
ExceptionHandlerDispatcher $exceptionDispatcher,
|
||||
Protocol $protocol,
|
||||
LoggerInterface $logger
|
||||
) {
|
||||
$this->container = $container;
|
||||
$this->dispatcher = $dispatcher;
|
||||
$this->exceptionHandlerDispatcher = $exceptionDispatcher;
|
||||
$this->protocol = $protocol;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
@ -141,18 +139,7 @@ abstract class Server implements OnReceiveInterface, MiddlewareInitializerInterf
|
||||
|
||||
protected function send(SwooleServer $server, int $fd, ResponseInterface $response): void
|
||||
{
|
||||
$eof = $server->setting['package_eof'] ?? '';
|
||||
$serverPort = $server->getClientInfo($fd)['server_port'] ?? null;
|
||||
if ($serverPort) {
|
||||
foreach ($server->ports ?? [] as $port) {
|
||||
if ($port->port === $serverPort) {
|
||||
$eof = $port->setting['package_eof'] ?? $eof;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$server->send($fd, (string) $response->getBody() . $eof);
|
||||
$server->send($fd, (string) $response->getBody());
|
||||
}
|
||||
|
||||
abstract protected function createCoreMiddleware(): CoreMiddlewareInterface;
|
||||
|
@ -35,11 +35,17 @@ class Protocol
|
||||
*/
|
||||
private $name;
|
||||
|
||||
public function __construct(ContainerInterface $container, ProtocolManager $protocolManager, string $name)
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $options;
|
||||
|
||||
public function __construct(ContainerInterface $container, ProtocolManager $protocolManager, string $name, array $options = [])
|
||||
{
|
||||
$this->container = $container;
|
||||
$this->name = $name;
|
||||
$this->protocolManager = $protocolManager;
|
||||
$this->options = $options;
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
@ -53,7 +59,8 @@ class Protocol
|
||||
if (! $this->container->has($packer)) {
|
||||
throw new \InvalidArgumentException("Packer {$packer} for {$this->name} does not exist");
|
||||
}
|
||||
return $this->container->get($packer);
|
||||
|
||||
return make($packer, [$this->options]);
|
||||
}
|
||||
|
||||
public function getTransporter(): TransporterInterface
|
||||
@ -62,7 +69,7 @@ class Protocol
|
||||
if (! $this->container->has($transporter)) {
|
||||
throw new \InvalidArgumentException("Transporter {$transporter} for {$this->name} does not exist");
|
||||
}
|
||||
return make($transporter);
|
||||
return make($transporter, ['config' => $this->options]);
|
||||
}
|
||||
|
||||
public function getPathGenerator(): PathGeneratorInterface
|
||||
|
@ -57,4 +57,20 @@ return [
|
||||
],
|
||||
],
|
||||
],
|
||||
'tags' => [
|
||||
'http_client' => [
|
||||
'http.url' => 'http.url',
|
||||
'http.method' => 'http.method',
|
||||
'http.status_code' => 'http.status_code',
|
||||
],
|
||||
'redis' => [
|
||||
'arguments' => 'arguments',
|
||||
'result' => 'result',
|
||||
],
|
||||
'db' => [
|
||||
'db.query' => 'db.query',
|
||||
'db.statement' => 'db.statement',
|
||||
'db.query_time' => 'db.query_time',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
@ -25,4 +25,9 @@ class Trace extends AbstractAnnotation
|
||||
* @var string
|
||||
*/
|
||||
public $name = '';
|
||||
|
||||
/**
|
||||
* @var array|string
|
||||
*/
|
||||
public $tag = 'source';
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ use Hyperf\Di\Annotation\Aspect;
|
||||
use Hyperf\Di\Aop\AroundInterface;
|
||||
use Hyperf\Di\Aop\ProceedingJoinPoint;
|
||||
use Hyperf\Tracer\SpanStarter;
|
||||
use Hyperf\Tracer\SpanTagManager;
|
||||
use Hyperf\Tracer\SwitchManager;
|
||||
use Hyperf\Utils\Context;
|
||||
use OpenTracing\Tracer;
|
||||
@ -46,10 +47,16 @@ class HttpClientAspect implements AroundInterface
|
||||
*/
|
||||
private $switchManager;
|
||||
|
||||
public function __construct(Tracer $tracer, SwitchManager $switchManager)
|
||||
/**
|
||||
* @var SpanTagManager
|
||||
*/
|
||||
private $spanTagManager;
|
||||
|
||||
public function __construct(Tracer $tracer, SwitchManager $switchManager, SpanTagManager $spanTagManager)
|
||||
{
|
||||
$this->tracer = $tracer;
|
||||
$this->switchManager = $switchManager;
|
||||
$this->spanTagManager = $spanTagManager;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -70,6 +77,12 @@ class HttpClientAspect implements AroundInterface
|
||||
$key = "HTTP Request [{$method}] {$uri}";
|
||||
$span = $this->startSpan($key);
|
||||
$span->setTag('source', $proceedingJoinPoint->className . '::' . $proceedingJoinPoint->methodName);
|
||||
if ($this->spanTagManager->has('http_client', 'http.url')) {
|
||||
$span->setTag($this->spanTagManager->get('http_client', 'http.url'), $uri);
|
||||
}
|
||||
if ($this->spanTagManager->has('http_client', 'http.method')) {
|
||||
$span->setTag($this->spanTagManager->get('http_client', 'http.method'), $method);
|
||||
}
|
||||
$appendHeaders = [];
|
||||
// Injects the context into the wire
|
||||
$this->tracer->inject(
|
||||
@ -81,7 +94,7 @@ class HttpClientAspect implements AroundInterface
|
||||
$proceedingJoinPoint->arguments['keys']['options'] = $options;
|
||||
$result = $proceedingJoinPoint->process();
|
||||
if ($result instanceof ResponseInterface) {
|
||||
$span->setTag('status', $result->getStatusCode());
|
||||
$span->setTag($this->spanTagManager->get('http_client', 'http.status_code'), $result->getStatusCode());
|
||||
}
|
||||
$span->finish();
|
||||
return $result;
|
||||
|
@ -17,6 +17,7 @@ use Hyperf\Di\Aop\AroundInterface;
|
||||
use Hyperf\Di\Aop\ProceedingJoinPoint;
|
||||
use Hyperf\Redis\Redis;
|
||||
use Hyperf\Tracer\SpanStarter;
|
||||
use Hyperf\Tracer\SpanTagManager;
|
||||
use Hyperf\Tracer\SwitchManager;
|
||||
use OpenTracing\Tracer;
|
||||
|
||||
@ -50,10 +51,16 @@ class RedisAspect implements AroundInterface
|
||||
*/
|
||||
private $switchManager;
|
||||
|
||||
public function __construct(Tracer $tracer, SwitchManager $switchManager)
|
||||
/**
|
||||
* @var SpanTagManager
|
||||
*/
|
||||
private $spanTagManager;
|
||||
|
||||
public function __construct(Tracer $tracer, SwitchManager $switchManager, SpanTagManager $spanTagManager)
|
||||
{
|
||||
$this->tracer = $tracer;
|
||||
$this->switchManager = $switchManager;
|
||||
$this->spanTagManager = $spanTagManager;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -67,9 +74,9 @@ class RedisAspect implements AroundInterface
|
||||
|
||||
$arguments = $proceedingJoinPoint->arguments['keys'];
|
||||
$span = $this->startSpan('Redis' . '::' . $arguments['name']);
|
||||
$span->setTag('arguments', json_encode($arguments['arguments']));
|
||||
$span->setTag($this->spanTagManager->get('redis', 'arguments'), json_encode($arguments['arguments']));
|
||||
$result = $proceedingJoinPoint->process();
|
||||
$span->setTag('result', json_encode($result));
|
||||
$span->setTag($this->spanTagManager->get('redis', 'result'), json_encode($result));
|
||||
$span->finish();
|
||||
return $result;
|
||||
}
|
||||
|
@ -52,11 +52,13 @@ class TraceAnnotationAspect implements AroundInterface
|
||||
/** @var Trace $annotation */
|
||||
if ($annotation = $metadata->method[Trace::class] ?? null) {
|
||||
$name = $annotation->name;
|
||||
$tag = $annotation->tag;
|
||||
} else {
|
||||
$name = $source;
|
||||
$tag = 'source';
|
||||
}
|
||||
$span = $this->startSpan($name);
|
||||
$span->setTag('source', $source);
|
||||
$span->setTag($tag, $source);
|
||||
$result = $proceedingJoinPoint->process();
|
||||
$span->finish();
|
||||
return $result;
|
||||
|
@ -24,6 +24,7 @@ class ConfigProvider
|
||||
'dependencies' => [
|
||||
Tracer::class => TracerFactory::class,
|
||||
SwitchManager::class => SwitchManagerFactory::class,
|
||||
SpanTagManager::class => SpanTagManagerFactory::class,
|
||||
Client::class => Client::class,
|
||||
],
|
||||
'listeners' => [
|
||||
|
@ -15,6 +15,7 @@ namespace Hyperf\Tracer\Listener;
|
||||
use Hyperf\Database\Events\QueryExecuted;
|
||||
use Hyperf\Event\Contract\ListenerInterface;
|
||||
use Hyperf\Tracer\SpanStarter;
|
||||
use Hyperf\Tracer\SpanTagManager;
|
||||
use Hyperf\Tracer\SwitchManager;
|
||||
use Hyperf\Utils\Arr;
|
||||
use Hyperf\Utils\Str;
|
||||
@ -34,10 +35,16 @@ class DbQueryExecutedListener implements ListenerInterface
|
||||
*/
|
||||
private $switchManager;
|
||||
|
||||
public function __construct(Tracer $tracer, SwitchManager $switchManager)
|
||||
/**
|
||||
* @var SpanTagManager
|
||||
*/
|
||||
private $spanTagManager;
|
||||
|
||||
public function __construct(Tracer $tracer, SwitchManager $switchManager, SpanTagManager $spanTagManager)
|
||||
{
|
||||
$this->tracer = $tracer;
|
||||
$this->switchManager = $switchManager;
|
||||
$this->spanTagManager = $spanTagManager;
|
||||
}
|
||||
|
||||
public function listen(): array
|
||||
@ -63,11 +70,11 @@ class DbQueryExecutedListener implements ListenerInterface
|
||||
}
|
||||
|
||||
$endTime = microtime(true);
|
||||
$span = $this->startSpan('db.query', [
|
||||
$span = $this->startSpan($this->spanTagManager->get('db', 'db.query'), [
|
||||
'start_time' => (int) (($endTime - $event->time / 1000) * 1000 * 1000),
|
||||
]);
|
||||
$span->setTag('db.sql', $sql);
|
||||
$span->setTag('db.query_time', $event->time . ' ms');
|
||||
$span->setTag($this->spanTagManager->get('db', 'db.statement'), $sql);
|
||||
$span->setTag($this->spanTagManager->get('db', 'db.query_time'), $event->time . ' ms');
|
||||
$span->finish((int) ($endTime * 1000 * 1000));
|
||||
}
|
||||
}
|
||||
|
47
src/tracer/src/SpanTagManager.php
Normal file
47
src/tracer/src/SpanTagManager.php
Normal file
@ -0,0 +1,47 @@
|
||||
<?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/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace Hyperf\Tracer;
|
||||
|
||||
class SpanTagManager
|
||||
{
|
||||
// TODO: The properties will be changed to standard version in v1.2.
|
||||
private $tags = [
|
||||
'http_client' => [
|
||||
'http.status_code' => 'status',
|
||||
],
|
||||
'redis' => [
|
||||
'arguments' => 'arguments',
|
||||
'result' => 'result',
|
||||
],
|
||||
'db' => [
|
||||
'db.query' => 'db.query',
|
||||
'db.statement' => 'db.sql',
|
||||
'db.query_time' => 'db.query_time',
|
||||
],
|
||||
];
|
||||
|
||||
public function apply(array $tags): void
|
||||
{
|
||||
$this->tags = array_replace_recursive($this->tags, $tags);
|
||||
}
|
||||
|
||||
public function get(string $type, string $name): string
|
||||
{
|
||||
return $this->tags[$type][$name];
|
||||
}
|
||||
|
||||
public function has(string $type, string $name): bool
|
||||
{
|
||||
return isset($this->tags[$type][$name]);
|
||||
}
|
||||
}
|
27
src/tracer/src/SpanTagManagerFactory.php
Normal file
27
src/tracer/src/SpanTagManagerFactory.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?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/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace Hyperf\Tracer;
|
||||
|
||||
use Hyperf\Contract\ConfigInterface;
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
||||
class SpanTagManagerFactory
|
||||
{
|
||||
public function __invoke(ContainerInterface $container)
|
||||
{
|
||||
$config = $container->get(ConfigInterface::class);
|
||||
$spanTag = new SpanTagManager();
|
||||
$spanTag->apply($config->get('opentracing.tags', []));
|
||||
return $spanTag;
|
||||
}
|
||||
}
|
@ -1017,8 +1017,9 @@ class Collection implements ArrayAccess, Arrayable, Countable, IteratorAggregate
|
||||
* Get one or a specified number of items randomly from the collection.
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* @return mixed|self
|
||||
*/
|
||||
public function random(int $number = null): self
|
||||
public function random(int $number = null)
|
||||
{
|
||||
if (is_null($number)) {
|
||||
return Arr::random($this->items);
|
||||
@ -1449,9 +1450,10 @@ class Collection implements ArrayAccess, Arrayable, Countable, IteratorAggregate
|
||||
|
||||
/**
|
||||
* Get an operator checker callback.
|
||||
* @param mixed|string $operator
|
||||
* @param null|mixed $value
|
||||
*/
|
||||
protected function operatorForWhere(string $key, string $operator = null, $value = null): \Closure
|
||||
protected function operatorForWhere(string $key, $operator = null, $value = null): \Closure
|
||||
{
|
||||
if (func_num_args() === 1) {
|
||||
$value = true;
|
||||
|
47
src/utils/tests/CollectionTest.php
Normal file
47
src/utils/tests/CollectionTest.php
Normal file
@ -0,0 +1,47 @@
|
||||
<?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/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace HyperfTest\Utils;
|
||||
|
||||
use Hyperf\Utils\Collection;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @coversNothing
|
||||
*/
|
||||
class CollectionTest extends TestCase
|
||||
{
|
||||
public function testOperatorForWhere()
|
||||
{
|
||||
$col = new Collection([['id' => 1, 'name' => 'Hyperf'], ['id' => 2, 'name' => 'HyperfCloud']]);
|
||||
|
||||
$res = $col->where('id', 1);
|
||||
$this->assertSame(1, $res->count());
|
||||
$this->assertSame(['id' => 1, 'name' => 'Hyperf'], $res->shift());
|
||||
|
||||
$res = $col->where('id', '=', 2);
|
||||
$this->assertSame(1, $res->count());
|
||||
$this->assertSame(['id' => 2, 'name' => 'HyperfCloud'], $res->shift());
|
||||
}
|
||||
|
||||
public function testRandom()
|
||||
{
|
||||
$col = new Collection([['id' => 1, 'name' => 'Hyperf'], ['id' => 2, 'name' => 'HyperfCloud']]);
|
||||
|
||||
$res = $col->random();
|
||||
$this->assertTrue(is_array($res));
|
||||
|
||||
$res = $col->random(1);
|
||||
$this->assertTrue($res instanceof Collection);
|
||||
}
|
||||
}
|
@ -161,7 +161,7 @@ trait ValidatesAttributes
|
||||
return false;
|
||||
}
|
||||
|
||||
return preg_match('/^[\pL\pM\pN]+$/u', $value) > 0;
|
||||
return preg_match('/^[\pL\pM\pN]+$/u', (string) $value) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1492,7 +1492,7 @@ trait ValidatesAttributes
|
||||
return $value->getSize() / 1024;
|
||||
}
|
||||
|
||||
return mb_strlen($value);
|
||||
return mb_strlen((string) $value);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -14,7 +14,9 @@ namespace Hyperf\Validation;
|
||||
|
||||
use Hyperf\Contract\ValidatorInterface;
|
||||
use Hyperf\Server\Exception\ServerException;
|
||||
use Hyperf\Utils\ApplicationContext;
|
||||
use Hyperf\Utils\Arr;
|
||||
use Hyperf\Validation\Contract\ValidatorFactoryInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
class ValidationException extends ServerException
|
||||
@ -75,7 +77,9 @@ class ValidationException extends ServerException
|
||||
*/
|
||||
public static function withMessages(array $messages)
|
||||
{
|
||||
return new static(tap(ValidatorFactory::make([], []), function ($validator) use ($messages) {
|
||||
$factory = ApplicationContext::getContainer()->get(ValidatorFactoryInterface::class);
|
||||
|
||||
return new static(tap($factory->make([], []), function ($validator) use ($messages) {
|
||||
foreach ($messages as $key => $value) {
|
||||
foreach (Arr::wrap($value) as $message) {
|
||||
$validator->errors()->add($key, $message);
|
||||
|
20
src/validation/tests/Cases/Stub/ValidatesAttributesStub.php
Normal file
20
src/validation/tests/Cases/Stub/ValidatesAttributesStub.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?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/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace HyperfTest\Validation\Cases\Stub;
|
||||
|
||||
use Hyperf\Validation\Concerns\ValidatesAttributes;
|
||||
|
||||
class ValidatesAttributesStub
|
||||
{
|
||||
use ValidatesAttributes;
|
||||
}
|
35
src/validation/tests/Cases/ValidateAttributesTest.php
Normal file
35
src/validation/tests/Cases/ValidateAttributesTest.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?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/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace HyperfTest\Validation\Cases;
|
||||
|
||||
use HyperfTest\Validation\Cases\Stub\ValidatesAttributesStub;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @coversNothing
|
||||
*/
|
||||
class ValidateAttributesTest extends TestCase
|
||||
{
|
||||
public function testValidateAlphaNum()
|
||||
{
|
||||
$validator = new ValidatesAttributesStub();
|
||||
$this->assertTrue($validator->validateAlphaNum('', 'xxx'));
|
||||
$this->assertTrue($validator->validateAlphaNum('', '123'));
|
||||
$this->assertTrue($validator->validateAlphaNum('', '123f1'));
|
||||
$this->assertTrue($validator->validateAlphaNum('', 123));
|
||||
|
||||
$this->assertFalse($validator->validateAlphaNum('', 123.1));
|
||||
$this->assertFalse($validator->validateAlphaNum('', '123_f1'));
|
||||
}
|
||||
}
|
65
src/validation/tests/Cases/ValidationExceptionTest.php
Normal file
65
src/validation/tests/Cases/ValidationExceptionTest.php
Normal file
@ -0,0 +1,65 @@
|
||||
<?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/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
namespace HyperfTest\Validation\Cases;
|
||||
|
||||
use Hyperf\Contract\TranslatorInterface;
|
||||
use Hyperf\Utils\ApplicationContext;
|
||||
use Hyperf\Utils\MessageBag;
|
||||
use Hyperf\Validation\Contract\ValidatorFactoryInterface;
|
||||
use Hyperf\Validation\ValidationException;
|
||||
use Hyperf\Validation\ValidatorFactory;
|
||||
use Mockery;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @coversNothing
|
||||
*/
|
||||
class ValidationExceptionTest extends TestCase
|
||||
{
|
||||
protected function tearDown()
|
||||
{
|
||||
Mockery::close();
|
||||
}
|
||||
|
||||
public function testWithMessages()
|
||||
{
|
||||
$this->getContainer();
|
||||
$exception = ValidationException::withMessages([
|
||||
'id' => 'id is required.',
|
||||
'name' => ['name is required.'],
|
||||
]);
|
||||
|
||||
$this->assertInstanceOf(ValidationException::class, $exception);
|
||||
$errors = $exception->validator->errors();
|
||||
$this->assertInstanceOf(MessageBag::class, $errors);
|
||||
$this->assertEquals([
|
||||
'id' => ['id is required.'],
|
||||
'name' => ['name is required.'],
|
||||
], $errors->getMessages());
|
||||
}
|
||||
|
||||
protected function getContainer()
|
||||
{
|
||||
$container = Mockery::mock(ContainerInterface::class);
|
||||
ApplicationContext::setContainer($container);
|
||||
|
||||
$container->shouldReceive('get')->with(ValidatorFactoryInterface::class)->andReturnUsing(function () use ($container) {
|
||||
$translator = Mockery::mock(TranslatorInterface::class);
|
||||
return new ValidatorFactory($translator, $container);
|
||||
});
|
||||
|
||||
return $container;
|
||||
}
|
||||
}
|
@ -19,7 +19,6 @@ use Hyperf\Database\Connectors\ConnectionFactory;
|
||||
use Hyperf\Database\Connectors\MySqlConnector;
|
||||
use Hyperf\Database\Model\Register;
|
||||
use Hyperf\Database\Schema\Builder;
|
||||
use Hyperf\DbConnection\ConnectionResolver as DBConnectionResolver;
|
||||
use Hyperf\DbConnection\Model\Model;
|
||||
use Hyperf\Server\Entry\EventDispatcher;
|
||||
use Hyperf\Translation\ArrayLoader;
|
||||
@ -60,7 +59,7 @@ class ValidationExistsRuleTest extends TestCase
|
||||
];
|
||||
$connection = $connector->make($dbConfig);
|
||||
$resolver = new ConnectionResolver(['default' => $connection]);
|
||||
$container->shouldReceive('get')->with(DBConnectionResolver::class)->andReturn($resolver);
|
||||
$container->shouldReceive('get')->with(ConnectionResolverInterface::class)->andReturn($resolver);
|
||||
ApplicationContext::setContainer($container);
|
||||
Register::setConnectionResolver($resolver);
|
||||
$container->shouldReceive('get')->with(EventDispatcherInterface::class)->andReturn(new EventDispatcher());
|
||||
|
@ -1505,6 +1505,12 @@ class ValidationValidatorTest extends TestCase
|
||||
$v = new Validator($trans, ['foo' => '3'], ['foo' => 'Numeric|Size:3']);
|
||||
$this->assertTrue($v->passes());
|
||||
|
||||
$v = new Validator($trans, ['foo' => 123], ['foo' => 'Size:123']);
|
||||
$this->assertFalse($v->passes());
|
||||
|
||||
$v = new Validator($trans, ['foo' => 3], ['foo' => 'Size:1']);
|
||||
$this->assertTrue($v->passes());
|
||||
|
||||
$v = new Validator($trans, ['foo' => [1, 2, 3]], ['foo' => 'Array|Size:3']);
|
||||
$this->assertTrue($v->passes());
|
||||
|
||||
|
@ -58,7 +58,7 @@ class Render implements RenderInterface
|
||||
$this->container = $container;
|
||||
}
|
||||
|
||||
public function render(string $template, array $data)
|
||||
public function render(string $template, array $data = [])
|
||||
{
|
||||
switch ($this->mode) {
|
||||
case Mode::SYNC:
|
||||
|
@ -14,5 +14,5 @@ namespace Hyperf\View;
|
||||
|
||||
interface RenderInterface
|
||||
{
|
||||
public function render(string $template, array $data);
|
||||
public function render(string $template, array $data = []);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user