Merge branch 'master' into pr/1137

This commit is contained in:
李铭昕 2020-01-03 14:40:28 +08:00
commit ab0f17bbf5
100 changed files with 1550 additions and 165 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 部署到了自己的生产环境上并稳定运行。
# 运行环境

View File

@ -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/",

View File

@ -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()

View File

@ -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

View File

@ -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.4TrvisCI 增加 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
## 新增

View File

@ -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` | 父类 |

View File

@ -306,7 +306,7 @@ Hyperf 的长生命周期依赖注入在项目启动时完成。这意味着长
* 构造函数时还不是协程环境,如果注入了可能会触发协程切换的类,就会导致框架启动失败。
* 构造函数中要避免循依赖(比较典型的例子为 `Listener``EventDispatcherInterface`),不然也会启动失败。
* 构造函数中要避免循依赖(比较典型的例子为 `Listener``EventDispatcherInterface`),不然也会启动失败。
目前解决方案是:只在实例中注入 `Psr\Container\ContainerInterface` ,而其他的组件在非构造函数执行时通过 `container` 获取。但 PSR-11 中指出:

View File

@ -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,
];
```

View File

@ -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()

View File

@ -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`,确保对象隔离。

View File

@ -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` 之外,还提供了多种方法来检查请求,下面我们提供一些方法的示例:

View File

@ -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){

View File

@ -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` 值为一个必填参数。

View File

@ -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',
],
]
];
```
### 更换采样器

View File

@ -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

View File

@ -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` | 父類 |

View File

@ -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()

View File

@ -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`,確保對象隔離。

View File

@ -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();

View File

@ -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',
],
]
];
```
### 更換採樣器

View File

@ -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

View File

@ -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` | 父類 |

View File

@ -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()

View File

@ -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`,確保物件隔離。

View File

@ -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();

View File

@ -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',
],
]
];
```
### 更換取樣器

View File

@ -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>

View File

@ -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;
}
}

View File

@ -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

View File

@ -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);

View File

@ -106,6 +106,7 @@ class CoroutineMemoryDriver extends Driver implements KeyCollectorInterface
}
}
$instance->put($collector, $result);
return true;
}
protected function getCollection()

View File

@ -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);
}
}

View File

@ -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'));
}]);
}
}

View File

@ -1175,6 +1175,7 @@ class Connection implements ConnectionInterface
/**
* Fire the given event if possible.
* @param mixed $event
*/
protected function event($event)
{

View File

@ -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);

View File

@ -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();

View File

@ -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' => [

View File

@ -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);
}
}

View File

@ -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)
);
}
}
}

View File

@ -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);
}

View File

@ -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();

View 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
{
}

View 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());
}
}

View File

@ -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' => [

View File

@ -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 /.

View File

@ -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.

View File

@ -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)

View File

@ -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

View File

@ -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.

View File

@ -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.

View File

@ -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,

View 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);
}
}

View 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);
}
}

View File

@ -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.

View File

@ -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

View File

@ -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;

View File

@ -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;
}

View 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));
}
}

View File

@ -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;
}
}

View 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;
}
}

View 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);
}
}

View File

@ -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);

View File

@ -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' => [

View File

@ -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);

View File

@ -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
View File

@ -0,0 +1 @@
/tests export-ignore

View File

@ -40,6 +40,7 @@
},
"autoload-dev": {
"psr-4": {
"HyperfTest\\RateLimit\\": "tests/"
}
},
"config": {

View File

@ -0,0 +1,9 @@
<?php
return [
'create' => 1,
'consume' => 1,
'capacity' => 2,
'limitCallback' => [],
'waitTimeout' => 1,
];

View File

@ -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;
}

View File

@ -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,
];
}
}

View File

@ -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',
],
],
];
}
}

View 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;
}
}

View File

@ -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.');
}

View File

@ -47,9 +47,6 @@ class RpcClientPool extends Pool
parent::__construct($container, $options);
}
/**
* @return string
*/
public function getName(): string
{
return $this->name;

View File

@ -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 = [])
{

View File

@ -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;

View File

@ -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

View File

@ -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',
],
],
];

View File

@ -25,4 +25,9 @@ class Trace extends AbstractAnnotation
* @var string
*/
public $name = '';
/**
* @var array|string
*/
public $tag = 'source';
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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;

View File

@ -24,6 +24,7 @@ class ConfigProvider
'dependencies' => [
Tracer::class => TracerFactory::class,
SwitchManager::class => SwitchManagerFactory::class,
SpanTagManager::class => SpanTagManagerFactory::class,
Client::class => Client::class,
],
'listeners' => [

View File

@ -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));
}
}

View 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]);
}
}

View 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;
}
}

View File

@ -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;

View 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);
}
}

View File

@ -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);
}
/**

View File

@ -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);

View 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;
}

View 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'));
}
}

View 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;
}
}

View File

@ -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());

View File

@ -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());

View File

@ -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:

View File

@ -14,5 +14,5 @@ namespace Hyperf\View;
interface RenderInterface
{
public function render(string $template, array $data);
public function render(string $template, array $data = []);
}