mirror of
https://gitee.com/hyperf/hyperf.git
synced 2024-11-30 02:37:58 +08:00
Merge branch 'master' into 2.1-merge
# Conflicts: # src/guzzle/src/CoroutineHandler.php # src/guzzle/tests/Stub/CoroutineHandlerStub.php
This commit is contained in:
commit
8921c2defc
29
.github/workflows/gitee.yml
vendored
Normal file
29
.github/workflows/gitee.yml
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
name: Mirror to Gitee Repo
|
||||
|
||||
on: [ push, delete, create ]
|
||||
|
||||
jobs:
|
||||
git-mirror:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Configure Private Key
|
||||
if: github.repository_owner == 'hyperf'
|
||||
env:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.GITEE_PRIVATE_KEY }}
|
||||
run: |
|
||||
mkdir -p ~/.ssh
|
||||
echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
|
||||
chmod 600 ~/.ssh/id_rsa
|
||||
echo "StrictHostKeyChecking no" >> ~/.ssh/config
|
||||
|
||||
- name: Push Mirror
|
||||
if: github.repository_owner == 'hyperf'
|
||||
env:
|
||||
SOURCE_REPO: 'https://github.com/hyperf/hyperf.git'
|
||||
DESTINATION_REPO: 'git@gitee.com:hyperf/hyperf.git'
|
||||
run: |
|
||||
git clone --mirror "$SOURCE_REPO" && cd `basename "$SOURCE_REPO"`
|
||||
git remote set-url --push origin "$DESTINATION_REPO"
|
||||
git fetch -p origin
|
||||
git for-each-ref --format 'delete %(refname)' refs/pull | git update-ref --stdin
|
||||
git push --mirror
|
@ -73,7 +73,7 @@ VALUES
|
||||
DROP TABLE IF EXISTS `user_ext`;
|
||||
|
||||
CREATE TABLE `user_ext` (
|
||||
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`id` bigint(20) unsigned NOT NULL,
|
||||
`count` int(10) unsigned NOT NULL DEFAULT '0',
|
||||
`float_num` decimal(10,2) DEFAULT '0.00',
|
||||
`str` varchar(16) DEFAULT NULL,
|
||||
|
@ -1,4 +1,59 @@
|
||||
# v2.0.19 - TBD
|
||||
# v2.0.21 - TBD
|
||||
|
||||
## Added
|
||||
|
||||
- [#2857](https://github.com/hyperf/hyperf/pull/2857) Support Consul ACL Token for Service Governance.
|
||||
|
||||
## Changed
|
||||
|
||||
- [#2851](https://github.com/hyperf/hyperf/pull/2851) Changed default engine of view config.
|
||||
|
||||
## Optimized
|
||||
|
||||
- [#2861](https://github.com/hyperf/hyperf/pull/2861) Optimized guzzle coroutine handler which throw exception when the status code below zero.
|
||||
- [#2868](https://github.com/hyperf/hyperf/pull/2868) Optimized code for guzzle sink, which support resource not only string.
|
||||
|
||||
# v2.0.20 - 2020-11-23
|
||||
|
||||
## Added
|
||||
|
||||
- [#2824](https://github.com/hyperf/hyperf/pull/2824) Added method `simplePaginate()` which return `PaginatorInterface` in `Hyperf\Database\Query\Builder`.
|
||||
|
||||
## Fixed
|
||||
|
||||
- [#2820](https://github.com/hyperf/hyperf/pull/2820) Fixed amqp consumer does not works when using fanout exchange.
|
||||
- [#2831](https://github.com/hyperf/hyperf/pull/2831) Fixed bug that amqp connection always be closed by client.
|
||||
- [#2848](https://github.com/hyperf/hyperf/pull/2848) Fixed database connection has already been bound to another coroutine when used in defer.
|
||||
|
||||
## Changed
|
||||
|
||||
- [#2824](https://github.com/hyperf/hyperf/pull/2824) Changed the result from `PaginatorInterface` to `LengthAwarePaginatorInterface` for method `paginate()` in `Hyperf\Database\Query\Builder`.
|
||||
|
||||
## Optimized
|
||||
|
||||
- [#2766](https://github.com/hyperf/hyperf/pull/2766) Safely finish spans in case of exception for tracer.
|
||||
- [#2805](https://github.com/hyperf/hyperf/pull/2805) Optimized nacos process which can stop safely.
|
||||
- [#2821](https://github.com/hyperf/hyperf/pull/2821) Optimized the exceptions thrown by `Json` and `Xml`.
|
||||
- [#2827](https://github.com/hyperf/hyperf/pull/2827) Optimized `Hyperf\Server\ServerConfig` which return type of `__set` should be void.
|
||||
- [#2839](https://github.com/hyperf/hyperf/pull/2839) Optimized comments for `Hyperf\Database\Schema\ColumnDefinition`.
|
||||
|
||||
# v2.0.19 - 2020-11-17
|
||||
|
||||
## Added
|
||||
|
||||
- [#2794](https://github.com/hyperf/hyperf/pull/2794) [#2802](https://github.com/hyperf/hyperf/pull/2802) Added `options.cookie_lifetime` for `hyperf/session`, you can use it to control the expire time for cookies.
|
||||
|
||||
## Fixed
|
||||
|
||||
- [#2783](https://github.com/hyperf/hyperf/pull/2783) Fixed nsq consumer does not works in coroutine style server.
|
||||
- [#2788](https://github.com/hyperf/hyperf/pull/2788) Fixed call non-static method `__handlePropertyHandler()` statically in class proxy.
|
||||
- [#2790](https://github.com/hyperf/hyperf/pull/2790) Fixed `BootProcessListener` of `config-etcd` does not works in coroutine style server.
|
||||
- [#2803](https://github.com/hyperf/hyperf/pull/2803) Fixed response body does not exists when bad request.
|
||||
- [#2807](https://github.com/hyperf/hyperf/pull/2807) Fixed Middleware does not work as expected when repeatedly configured.
|
||||
|
||||
## Optimized
|
||||
|
||||
- [#2750](https://github.com/hyperf/hyperf/pull/2750) Use elastic `index` instead of `type` for `searchableAs`, when the config of `index` is `null` or the elastic version is more than `7.0.0`.
|
||||
|
||||
# v2.0.18 - 2020-11-09
|
||||
|
||||
|
@ -104,6 +104,7 @@
|
||||
"hyperf/di": "self.version",
|
||||
"hyperf/dispatcher": "self.version",
|
||||
"hyperf/elasticsearch": "self.version",
|
||||
"hyperf/encryption": "self.version",
|
||||
"hyperf/etcd": "self.version",
|
||||
"hyperf/event": "self.version",
|
||||
"hyperf/exception-handler": "self.version",
|
||||
@ -180,6 +181,7 @@
|
||||
"Hyperf\\Di\\": "src/di/src/",
|
||||
"Hyperf\\Dispatcher\\": "src/dispatcher/src/",
|
||||
"Hyperf\\Elasticsearch\\": "src/elasticsearch/src/",
|
||||
"Hyperf\\Encryption\\": "src/encryption/src/",
|
||||
"Hyperf\\Etcd\\": "src/etcd/src/",
|
||||
"Hyperf\\Event\\": "src/event/src/",
|
||||
"Hyperf\\ExceptionHandler\\": "src/exception-handler/src/",
|
||||
@ -262,6 +264,7 @@
|
||||
"HyperfTest\\Di\\": "src/di/tests/",
|
||||
"HyperfTest\\Dispatcher\\": "src/dispatcher/tests/",
|
||||
"HyperfTest\\Elasticsearch\\": "src/elasticsearch/tests/",
|
||||
"HyperfTest\\Encryption\\": "src/encryption/tests/",
|
||||
"HyperfTest\\Etcd\\": "src/etcd/tests/",
|
||||
"HyperfTest\\Event\\": "src/event/tests/",
|
||||
"HyperfTest\\ExceptionHandler\\": "src/exception-handler/tests/",
|
||||
@ -338,6 +341,7 @@
|
||||
"Hyperf\\Devtool\\ConfigProvider",
|
||||
"Hyperf\\Di\\ConfigProvider",
|
||||
"Hyperf\\Dispatcher\\ConfigProvider",
|
||||
"Hyperf\\Encryption\\ConfigProvider",
|
||||
"Hyperf\\Etcd\\ConfigProvider",
|
||||
"Hyperf\\Event\\ConfigProvider",
|
||||
"Hyperf\\ExceptionHandler\\ConfigProvider",
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html lang="zh">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
|
@ -126,6 +126,7 @@
|
||||
- [firstphp/wsdebug](https://github.com/lamplife/wsdebug) 通过 `WebSocket` 实时观测异常错误的开发调试组件
|
||||
- [qbhy/hyperf-multi-env](https://github.com/qbhy/hyperf-multi-env) 支持与 laravel 类似的多 env 配置文件功能,通过 `APP_ENV=testing` 可以加载 `.env.testing` 配置覆盖默认的 `.env`
|
||||
- [qiutuleng/hyperf-dump-server](https://github.com/qiutuleng/hyperf-dump-server) 提供一个 `dump` 函数,可以将程序内的变量或数据打印到另一个命令行窗口中,基于 Symfony 的 `Var-Dump Server` 组件
|
||||
- [leearvin/hyperf-tinker](https://github.com/Arvin-Lee/hyperf-tinker) 基于 PsySH 提供一个交互式的 Hyperf shell 容器
|
||||
|
||||
## 权限认证
|
||||
|
||||
@ -134,6 +135,7 @@
|
||||
- [96qbhy/hyperf-auth](https://github.com/qbhy/hyperf-auth) 参考 laravel 的 auth 组件设计,支持 jwt 和 session 驱动,更轻巧更好用
|
||||
- [hyperf-ext/jwt](https://github.com/hyperf-ext/jwt) JWT 组件,实现了完整用于 JWT 认证的能力
|
||||
- [hyperf-ext/auth](https://github.com/hyperf-ext/auth) 移植自 `illuminate/auth`,基本完整的实现了 Laravel Auth 的功能特性
|
||||
- [donjan-deng/hyperf-casbin](https://github.com/donjan-deng/hyperf-casbin) 适配于 Hyperf 的开源访问控制框架 [Casbin](https://casbin.org/docs/zh-CN/overview)
|
||||
|
||||
## 第三方 SDK
|
||||
|
||||
|
@ -1,5 +1,47 @@
|
||||
# 版本更新记录
|
||||
|
||||
# v2.0.20 - 2020-11-23
|
||||
|
||||
## 新增
|
||||
|
||||
- [#2824](https://github.com/hyperf/hyperf/pull/2824) 为 `Hyperf\Database\Query\Builder` 增加方法 `simplePaginate()`。
|
||||
|
||||
## 修复
|
||||
|
||||
- [#2820](https://github.com/hyperf/hyperf/pull/2820) 修复使用 `fanout` 交换机时,`AMQP` 消费者无法正常工作的问题。
|
||||
- [#2831](https://github.com/hyperf/hyperf/pull/2831) 修复 `AMQP` 连接会被客户端意外关闭的问题。
|
||||
- [#2848](https://github.com/hyperf/hyperf/pull/2848) 修复在 `defer` 中使用数据库组件时,会导致数据库连接会同时被其他协程绑定的问题。
|
||||
|
||||
## 变更
|
||||
|
||||
- [#2824](https://github.com/hyperf/hyperf/pull/2824) 修改 `Hyperf\Database\Query\Builder` 方法 `paginate()` 返回值类型,由 `PaginatorInterface` 变更为 `LengthAwarePaginatorInterface`。
|
||||
|
||||
## 优化
|
||||
|
||||
- [#2766](https://github.com/hyperf/hyperf/pull/2766) 优化 `Tracer` 组件,在抛出异常的情况下,也可以执行 `finish` 方法,记录链路。
|
||||
- [#2805](https://github.com/hyperf/hyperf/pull/2805) 优化 `Nacos` 进程,可以安全停止。
|
||||
- [#2821](https://github.com/hyperf/hyperf/pull/2821) 优化工具类 `Json` 和 `Xml`,使其抛出一致的异常。
|
||||
- [#2827](https://github.com/hyperf/hyperf/pull/2827) 优化 `Hyperf\Server\ServerConfig`,解决方法 `__set` 因返回值不为 `void`,导致不兼容 `PHP8` 的问题。
|
||||
- [#2839](https://github.com/hyperf/hyperf/pull/2839) 优化 `Hyperf\Database\Schema\ColumnDefinition` 的注释。
|
||||
|
||||
# v2.0.19 - 2020-11-17
|
||||
|
||||
## 新增
|
||||
|
||||
- [#2794](https://github.com/hyperf/hyperf/pull/2794) [#2802](https://github.com/hyperf/hyperf/pull/2802) 为 `Session` 组件新增配置项 `options.cookie_lifetime`, 允许用户自己设置 `Cookies` 的超时时间。
|
||||
|
||||
## 修复
|
||||
|
||||
- [#2783](https://github.com/hyperf/hyperf/pull/2783) 修复 `NSQ` 消费者无法在协程风格下正常使用的问题。
|
||||
- [#2788](https://github.com/hyperf/hyperf/pull/2788) 修复非静态方法 `__handlePropertyHandler()` 在代理类中,被静态调用的问题。
|
||||
- [#2790](https://github.com/hyperf/hyperf/pull/2790) 修复 `ETCD` 配置中心,`BootProcessListener` 监听器无法在协程风格下正常使用的问题。
|
||||
- [#2803](https://github.com/hyperf/hyperf/pull/2803) 修复当 `Request` 无法实例化时,`HTTP` 响应数据被清除的问题。
|
||||
- [#2807](https://github.com/hyperf/hyperf/pull/2807) 修复当存在重复的中间件时,中间件的表现会与预期不符的问题。
|
||||
|
||||
## 优化
|
||||
|
||||
- [#2750](https://github.com/hyperf/hyperf/pull/2750) 优化 `Scout` 组件,当没有配置搜索引擎 `index` 或 `Elasticsearch` 版本高于 `7.0` 时,使用 `index` 而非 `type` 作为模型的搜索条件。
|
||||
|
||||
# v2.0.18 - 2020-11-09
|
||||
|
||||
## 新增
|
||||
|
@ -54,7 +54,7 @@ Swoole 协程也是对异步回调的一种解决方案,在 `PHP` 语言下,
|
||||
|
||||
### 协程与普通线程有哪些区别?
|
||||
|
||||
都说协程是一个轻量级的线程,协程和线程都适用于多任务的场景下,从这个角度上来说,协程与线程很相似,都有自己的上下文,可以共享全局变量,但不同之处在于,在同一时间可以有多个线程处于运行状态,但对于 `Swoole` 协程来说只能有一个,其它的协程都会处于暂停的状态。此外,普通线程是抢占式的,那个线程能得到资源由操作系统决定,而协程是协作式的,执行权由用户态自行分配。
|
||||
都说协程是一个轻量级的线程,协程和线程都适用于多任务的场景下,从这个角度上来说,协程与线程很相似,都有自己的上下文,可以共享全局变量,但不同之处在于,在同一时间可以有多个线程处于运行状态,但对于 `Swoole` 协程来说只能有一个,其它的协程都会处于暂停的状态。此外,普通线程是抢占式的,哪个线程能得到资源由操作系统决定,而协程是协作式的,执行权由用户态自行分配。
|
||||
|
||||
## 协程编程注意事项
|
||||
|
||||
|
@ -96,8 +96,8 @@ class DbQueryExecutedListener implements ListenerInterface
|
||||
| updated | 数据更新后 | 否 | |
|
||||
| saving | 数据创建或更新时 | 是 | |
|
||||
| saved | 数据创建或更新后 | 否 | |
|
||||
| restoring | 软删除数据回复时 | 是 | |
|
||||
| restored | 软删除数据回复后 | 否 | |
|
||||
| restoring | 软删除数据恢复时 | 是 | |
|
||||
| restored | 软删除数据恢复后 | 否 | |
|
||||
| deleting | 数据删除时 | 是 | |
|
||||
| deleted | 数据删除后 | 否 | |
|
||||
| forceDeleted | 数据强制删除后 | 否 | |
|
||||
|
37
docs/zh-cn/encryption.md
Normal file
37
docs/zh-cn/encryption.md
Normal file
@ -0,0 +1,37 @@
|
||||
# 加密解密
|
||||
|
||||
[hyperf/encryption](https://github.com/hyperf/encryption) 借鉴于 `Laravel Encryption` 组件,十分感谢 `Laravel` 开发组对 `PHP` 社区的贡献。
|
||||
|
||||
## 简介
|
||||
|
||||
`Encryption` 是基于 `OpenSSL` 实现加密解密组件。
|
||||
|
||||
## 配置
|
||||
|
||||
加密器可以根据实际情况,配置多组 `key` 和 `cipher`。
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
return [
|
||||
'default' => [
|
||||
'key' => 'Hyperf',
|
||||
'cipher' => 'AES-128-CBC',
|
||||
],
|
||||
];
|
||||
|
||||
```
|
||||
|
||||
## 使用
|
||||
|
||||
```php
|
||||
<?php
|
||||
use Hyperf\Utils\ApplicationContext;
|
||||
use Hyperf\Encryption\Contract\EncrypterInterface;
|
||||
|
||||
$input = 'Hello Word.';
|
||||
$container = ApplicationContext::getContainer();
|
||||
$encrypter = $container->get(EncrypterInterface::class);
|
||||
$encrypt = $encrypter->encrypt($input);
|
||||
$raw = $encrypter->decrypt($encrypt);
|
||||
```
|
@ -214,7 +214,7 @@ class FooMiddleware implements MiddlewareInterface
|
||||
[
|
||||
'code' => -1,
|
||||
'data' => [
|
||||
'error' => '中间里验证token无效,阻止继续向下执行',
|
||||
'error' => '中间件验证token无效,阻止继续向下执行',
|
||||
],
|
||||
]
|
||||
);
|
||||
|
@ -145,10 +145,32 @@ $id = $generator->generate($userId);
|
||||
|
||||
```php
|
||||
<?php
|
||||
use Hyperf\Database\Model\Model;
|
||||
use Hyperf\Snowflake\Concern\Snowflake;
|
||||
|
||||
class User extends \Hyperf\Database\Model\Model {
|
||||
use \Hyperf\Snowflake\Concern\Snowflake;
|
||||
class User extends Model {
|
||||
use Snowflake;
|
||||
}
|
||||
```
|
||||
|
||||
上述 User 模型在创建时便会默认使用 Snowflake 算法生成主键。
|
||||
|
||||
因为 Snowflake 中会复写 `creating` 方法,而用户有需要自己设置 `creating` 方法时,就会出现无法生成 `ID` 的问题。这里需要用户按照以下方式自行处理即可
|
||||
|
||||
```php
|
||||
<?php
|
||||
use Hyperf\Database\Model\Model;
|
||||
use Hyperf\Snowflake\Concern\Snowflake;
|
||||
|
||||
class User extends Model {
|
||||
use Snowflake {
|
||||
creating as create;
|
||||
}
|
||||
|
||||
public function creating()
|
||||
{
|
||||
$this->create();
|
||||
// Do something ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
@ -116,7 +116,8 @@
|
||||
* [Watcher](zh-cn/watcher.md)
|
||||
* [开发者工具](zh-cn/devtool.md)
|
||||
* [Swoole Tracker](zh-cn/swoole-tracker.md)
|
||||
|
||||
* [加密解密](zh-cn/encryption.md)
|
||||
|
||||
* 应用部署
|
||||
|
||||
* [Docker Swarm 集群搭建](zh-cn/tutorial/docker-swarm.md)
|
||||
|
@ -73,6 +73,16 @@ return [
|
||||
|
||||
### 安装 Blade 引擎
|
||||
|
||||
```bash
|
||||
composer require hyperf/view-engine
|
||||
```
|
||||
|
||||
详细方式见文档 [视图引擎](zh-cn/view-engine.md)
|
||||
|
||||
或者使用
|
||||
|
||||
> duncan3dc/blade 因为使用了 Laravel 的 Support 库,所以会导致某些函数不兼容,暂时不推荐使用
|
||||
|
||||
```bash
|
||||
composer require duncan3dc/blade
|
||||
```
|
||||
|
@ -25,6 +25,7 @@
|
||||
<directory suffix="Test.php">./src/di/tests</directory>
|
||||
<directory suffix="Test.php">./src/dispatcher/tests</directory>
|
||||
<directory suffix="Test.php">./src/elasticsearch/tests</directory>
|
||||
<directory suffix="Test.php">./src/encryption/tests</directory>
|
||||
<directory suffix="Test.php">./src/etcd/tests</directory>
|
||||
<directory suffix="Test.php">./src/event/tests</directory>
|
||||
<directory suffix="Test.php">./src/exception-handler/tests</directory>
|
||||
@ -80,6 +81,7 @@
|
||||
<directory suffix=".php">./src/di/src</directory>
|
||||
<directory suffix=".php">./src/dispatcher/src</directory>
|
||||
<directory suffix=".php">./src/elasticsearch/src</directory>
|
||||
<directory suffix=".php">./src/encryption/src</directory>
|
||||
<directory suffix=".php">./src/event/src</directory>
|
||||
<directory suffix=".php">./src/grpc-client/src</directory>
|
||||
<directory suffix=".php">./src/guzzle/src</directory>
|
||||
|
@ -85,10 +85,6 @@ class Connection extends BaseConnection implements ConnectionInterface
|
||||
public function getActiveConnection(): AbstractConnection
|
||||
{
|
||||
if ($this->check()) {
|
||||
// The connection is valid, reset the last heartbeat time.
|
||||
$currentTime = microtime(true);
|
||||
$this->lastHeartbeatTime = $currentTime;
|
||||
|
||||
return $this->connection;
|
||||
}
|
||||
|
||||
@ -128,7 +124,14 @@ class Connection extends BaseConnection implements ConnectionInterface
|
||||
|
||||
public function check(): bool
|
||||
{
|
||||
return isset($this->connection) && $this->connection instanceof AbstractConnection && $this->connection->isConnected() && ! $this->isHeartbeatTimeout();
|
||||
$result = isset($this->connection) && $this->connection instanceof AbstractConnection && $this->connection->isConnected() && ! $this->isHeartbeatTimeout();
|
||||
if ($result) {
|
||||
// The connection is valid, reset the last heartbeat time.
|
||||
$currentTime = microtime(true);
|
||||
$this->lastHeartbeatTime = $currentTime;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function close(): bool
|
||||
|
@ -18,6 +18,7 @@ use Hyperf\Amqp\Event\WaitTimeout;
|
||||
use Hyperf\Amqp\Exception\MessageException;
|
||||
use Hyperf\Amqp\Message\ConsumerMessageInterface;
|
||||
use Hyperf\Amqp\Message\MessageInterface;
|
||||
use Hyperf\Amqp\Message\Type;
|
||||
use Hyperf\Amqp\Pool\PoolFactory;
|
||||
use Hyperf\Contract\ConfigInterface;
|
||||
use Hyperf\ExceptionHandler\Formatter\FormatterInterface;
|
||||
@ -132,6 +133,10 @@ class Consumer extends Builder
|
||||
$channel->queue_bind($message->getQueue(), $message->getExchange(), $routingKey);
|
||||
}
|
||||
|
||||
if (empty($routineKeys) && $message->getType() === Type::FANOUT) {
|
||||
$channel->queue_bind($message->getQueue(), $message->getExchange());
|
||||
}
|
||||
|
||||
if (is_array($qos = $message->getQos())) {
|
||||
$size = $qos['prefetch_size'] ?? null;
|
||||
$count = $qos['prefetch_count'] ?? null;
|
||||
|
@ -20,6 +20,7 @@ use Hyperf\Contract\StdoutLoggerInterface;
|
||||
use Hyperf\Event\Contract\ListenerInterface;
|
||||
use Hyperf\Framework\Event\BeforeWorkerStart;
|
||||
use Hyperf\Process\Event\BeforeProcessHandle;
|
||||
use Hyperf\Server\Event\MainCoroutineServerStart;
|
||||
use Hyperf\Utils\Coordinator\Constants;
|
||||
use Hyperf\Utils\Coordinator\CoordinatorManager;
|
||||
use Hyperf\Utils\Coroutine;
|
||||
@ -69,6 +70,7 @@ class BootProcessListener implements ListenerInterface
|
||||
BeforeWorkerStart::class,
|
||||
BeforeProcessHandle::class,
|
||||
BeforeHandle::class,
|
||||
MainCoroutineServerStart::class,
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -11,4 +11,5 @@ declare(strict_types=1);
|
||||
*/
|
||||
return [
|
||||
'uri' => 'http://127.0.0.1:8500',
|
||||
'token' => '',
|
||||
];
|
||||
|
@ -13,6 +13,7 @@ namespace Hyperf\Database\Query;
|
||||
|
||||
use Closure;
|
||||
use DateTimeInterface;
|
||||
use Hyperf\Contract\LengthAwarePaginatorInterface;
|
||||
use Hyperf\Contract\PaginatorInterface;
|
||||
use Hyperf\Database\Concerns\BuildsQueries;
|
||||
use Hyperf\Database\ConnectionInterface;
|
||||
@ -1867,13 +1868,31 @@ class Builder
|
||||
* @param string $pageName
|
||||
* @param null|int $page
|
||||
*/
|
||||
public function paginate($perPage = 15, $columns = ['*'], $pageName = 'page', $page = null): PaginatorInterface
|
||||
public function simplePaginate($perPage = 15, $columns = ['*'], $pageName = 'page', $page = null): PaginatorInterface
|
||||
{
|
||||
$page = $page ?: Paginator::resolveCurrentPage($pageName);
|
||||
|
||||
$this->skip(($page - 1) * $perPage)->take($perPage + 1);
|
||||
|
||||
return $this->paginator($this->get($columns), $perPage, $page, [
|
||||
return $this->simplePaginator($this->get($columns), $perPage, $page, [
|
||||
'path' => Paginator::resolveCurrentPath(),
|
||||
'pageName' => $pageName,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $perPage
|
||||
* @param string[] $columns
|
||||
* @param string $pageName
|
||||
* @param null $page
|
||||
*/
|
||||
public function paginate($perPage = 15, $columns = ['*'], $pageName = 'page', $page = null): LengthAwarePaginatorInterface
|
||||
{
|
||||
$page = $page ?: Paginator::resolveCurrentPage($pageName);
|
||||
$total = $this->getCountForPagination();
|
||||
$results = $total ? $this->forPage($page, $perPage)->get($columns) : collect();
|
||||
|
||||
return $this->paginator($results, $total, $perPage, $page, [
|
||||
'path' => Paginator::resolveCurrentPath(),
|
||||
'pageName' => $pageName,
|
||||
]);
|
||||
@ -2489,18 +2508,6 @@ class Builder
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new simple paginator instance.
|
||||
*/
|
||||
protected function paginator(Collection $items, int $perPage, int $currentPage, array $options)
|
||||
{
|
||||
$container = ApplicationContext::getContainer();
|
||||
if (! method_exists($container, 'make')) {
|
||||
throw new \RuntimeException('The DI container does not support make() method.');
|
||||
}
|
||||
return $container->make(PaginatorInterface::class, compact('items', 'perPage', 'currentPage', 'options'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a subquery and parse it.
|
||||
*
|
||||
@ -2878,4 +2885,28 @@ class Builder
|
||||
return ! $binding instanceof Expression;
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new length-aware paginator instance.
|
||||
*/
|
||||
protected function paginator(Collection $items, int $total, int $perPage, int $currentPage, array $options): LengthAwarePaginatorInterface
|
||||
{
|
||||
$container = ApplicationContext::getContainer();
|
||||
if (! method_exists($container, 'make')) {
|
||||
throw new \RuntimeException('The DI container does not support make() method.');
|
||||
}
|
||||
return $container->make(LengthAwarePaginatorInterface::class, compact('items', 'total', 'perPage', 'currentPage', 'options'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new simple paginator instance.
|
||||
*/
|
||||
protected function simplePaginator(Collection $items, int $perPage, int $currentPage, array $options): PaginatorInterface
|
||||
{
|
||||
$container = ApplicationContext::getContainer();
|
||||
if (! method_exists($container, 'make')) {
|
||||
throw new \RuntimeException('The DI container does not support make() method.');
|
||||
}
|
||||
return $container->make(PaginatorInterface::class, compact('items', 'perPage', 'currentPage', 'options'));
|
||||
}
|
||||
}
|
||||
|
@ -17,21 +17,21 @@ use Hyperf\Utils\Fluent;
|
||||
* @method ColumnDefinition after(string $column) Place the column "after" another column (MySQL)
|
||||
* @method ColumnDefinition always() Used as a modifier for generatedAs() (PostgreSQL)
|
||||
* @method ColumnDefinition autoIncrement() Set INTEGER columns as auto-increment (primary key)
|
||||
* @method ColumnDefinition change() Change the column
|
||||
* @method ColumnDefinition change() Change the column
|
||||
* @method ColumnDefinition charset(string $charset) Specify a character set for the column (MySQL)
|
||||
* @method ColumnDefinition collation(string $collation) Specify a collation for the column (MySQL/SQL Server)
|
||||
* @method ColumnDefinition comment(string $comment) Add a comment to the column (MySQL)
|
||||
* @method ColumnDefinition default(mixed $value) Specify a "default" value for the column
|
||||
* @method ColumnDefinition default(mixed $value) Specify a "default" value for the column
|
||||
* @method ColumnDefinition first() Place the column "first" in the table (MySQL)
|
||||
* @method ColumnDefinition generatedAs(string $expression) Create a SQL compliant identity column (PostgreSQL)
|
||||
* @method ColumnDefinition index() Add an index
|
||||
* @method ColumnDefinition nullable(bool $value = true) Allow NULL values to be inserted into the column
|
||||
* @method ColumnDefinition primary() Add a primary index
|
||||
* @method ColumnDefinition spatialIndex() Add a spatial index
|
||||
* @method ColumnDefinition index(string $name = null) Add an index
|
||||
* @method ColumnDefinition nullable(bool $value = true) Allow NULL values to be inserted into the column
|
||||
* @method ColumnDefinition primary() Add a primary index
|
||||
* @method ColumnDefinition spatialIndex() Add a spatial index
|
||||
* @method ColumnDefinition storedAs(string $expression) Create a stored generated column (MySQL)
|
||||
* @method ColumnDefinition unique() Add a unique index
|
||||
* @method ColumnDefinition unique(string $name = null) Add a unique index
|
||||
* @method ColumnDefinition unsigned() Set the INTEGER column as UNSIGNED (MySQL)
|
||||
* @method ColumnDefinition useCurrent() Set the TIMESTAMP column to use CURRENT_TIMESTAMP as default value
|
||||
* @method ColumnDefinition useCurrent() Set the TIMESTAMP column to use CURRENT_TIMESTAMP as default value
|
||||
* @method ColumnDefinition virtualAs(string $expression) Create a virtual generated column (MySQL)
|
||||
*/
|
||||
class ColumnDefinition extends Fluent
|
||||
|
@ -20,6 +20,7 @@ use Hyperf\Database\Query\Grammars\MySqlGrammar;
|
||||
use Hyperf\Database\Query\Processors\MySqlProcessor;
|
||||
use Hyperf\Database\Query\Processors\Processor;
|
||||
use Hyperf\Di\Container;
|
||||
use Hyperf\Paginator\LengthAwarePaginator;
|
||||
use Hyperf\Paginator\Paginator;
|
||||
use Hyperf\Utils\ApplicationContext;
|
||||
use Hyperf\Utils\Collection;
|
||||
@ -2632,7 +2633,7 @@ class QueryBuilderTest extends TestCase
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
public function testPaginate()
|
||||
public function testSimplePaginate()
|
||||
{
|
||||
$perPage = 16;
|
||||
$columns = ['test'];
|
||||
@ -2659,7 +2660,7 @@ class QueryBuilderTest extends TestCase
|
||||
return Context::get('path');
|
||||
});
|
||||
|
||||
$result = $builder->paginate($perPage, $columns, $pageName, $page);
|
||||
$result = $builder->simplePaginate($perPage, $columns, $pageName, $page);
|
||||
|
||||
$this->assertEquals(new Paginator($results, $perPage, $page, [
|
||||
'path' => $path,
|
||||
@ -2667,7 +2668,44 @@ class QueryBuilderTest extends TestCase
|
||||
]), $result);
|
||||
}
|
||||
|
||||
public function testPaginateWithDefaultArguments()
|
||||
public function testPaginate()
|
||||
{
|
||||
$perPage = 16;
|
||||
$columns = ['test'];
|
||||
$pageName = 'page-name';
|
||||
$page = 1;
|
||||
$total = 10;
|
||||
$builder = $this->getMockQueryBuilder();
|
||||
$path = 'http://foo.bar?page=3';
|
||||
|
||||
$results = collect([['test' => 'foo'], ['test' => 'bar']]);
|
||||
|
||||
$builder->shouldReceive('get')->once()->andReturn($results);
|
||||
$builder->shouldReceive('getCountForPagination')->andReturn($total);
|
||||
|
||||
Context::set('path', $path);
|
||||
if (! defined('BASE_PATH')) {
|
||||
define('BASE_PATH', __DIR__);
|
||||
}
|
||||
|
||||
$container = Mockery::mock(Container::class);
|
||||
$container->shouldReceive('make')->once()->andReturnUsing(function ($interface, $args) {
|
||||
return new LengthAwarePaginator($args['items'], $args['total'], $args['perPage'], $args['currentPage'], $args['options']);
|
||||
});
|
||||
ApplicationContext::setContainer($container);
|
||||
Paginator::currentPathResolver(function () {
|
||||
return Context::get('path');
|
||||
});
|
||||
|
||||
$result = $builder->paginate($perPage, $columns, $pageName, $page);
|
||||
|
||||
$this->assertEquals(new LengthAwarePaginator($results, $total, $perPage, $page, [
|
||||
'path' => $path,
|
||||
'pageName' => $pageName,
|
||||
]), $result);
|
||||
}
|
||||
|
||||
public function testSimplePaginateWithDefaultArguments()
|
||||
{
|
||||
$perPage = 15;
|
||||
$columns = ['*'];
|
||||
@ -2700,7 +2738,7 @@ class QueryBuilderTest extends TestCase
|
||||
return Context::get('path');
|
||||
});
|
||||
|
||||
$result = $builder->paginate();
|
||||
$result = $builder->simplePaginate();
|
||||
|
||||
$this->assertEquals(new Paginator($results, $perPage, $page, [
|
||||
'path' => $path,
|
||||
@ -2708,7 +2746,50 @@ class QueryBuilderTest extends TestCase
|
||||
]), $result);
|
||||
}
|
||||
|
||||
public function testPaginateWhenNoResults()
|
||||
public function testPaginateWithDefaultArguments()
|
||||
{
|
||||
$perPage = 15;
|
||||
$columns = ['*'];
|
||||
$pageName = 'page';
|
||||
$page = 1;
|
||||
$total = 10;
|
||||
$builder = $this->getMockQueryBuilder();
|
||||
$path = 'http://foo.bar?page=3';
|
||||
|
||||
$results = collect([['test' => 'foo'], ['test' => 'bar']]);
|
||||
|
||||
$builder->shouldReceive('get')->once()->andReturn($results);
|
||||
$builder->shouldReceive('getCountForPagination')->andReturn($total);
|
||||
|
||||
Context::set('path', $path);
|
||||
Context::set('page', $page);
|
||||
if (! defined('BASE_PATH')) {
|
||||
define('BASE_PATH', __DIR__);
|
||||
}
|
||||
|
||||
$container = Mockery::mock(Container::class);
|
||||
$container->shouldReceive('make')->once()->andReturnUsing(function ($interface, $args) {
|
||||
return new LengthAwarePaginator($args['items'], $args['total'], $args['perPage'], $args['currentPage'], $args['options']);
|
||||
});
|
||||
ApplicationContext::setContainer($container);
|
||||
|
||||
Paginator::currentPageResolver(function () {
|
||||
return Context::get('page');
|
||||
});
|
||||
|
||||
Paginator::currentPathResolver(function () {
|
||||
return Context::get('path');
|
||||
});
|
||||
|
||||
$result = $builder->paginate();
|
||||
|
||||
$this->assertEquals(new LengthAwarePaginator($results, $total, $perPage, $page, [
|
||||
'path' => $path,
|
||||
'pageName' => $pageName,
|
||||
]), $result);
|
||||
}
|
||||
|
||||
public function testSimplePaginateWhenNoResults()
|
||||
{
|
||||
$perPage = 15;
|
||||
$pageName = 'page';
|
||||
@ -2742,7 +2823,7 @@ class QueryBuilderTest extends TestCase
|
||||
return Context::get('path');
|
||||
});
|
||||
|
||||
$result = $builder->paginate();
|
||||
$result = $builder->simplePaginate();
|
||||
|
||||
$this->assertEquals(new Paginator($results, $perPage, $page, [
|
||||
'path' => $path,
|
||||
@ -2750,6 +2831,50 @@ class QueryBuilderTest extends TestCase
|
||||
]), $result);
|
||||
}
|
||||
|
||||
public function testPaginateWhenNoResults()
|
||||
{
|
||||
$perPage = 15;
|
||||
$pageName = 'page';
|
||||
$page = 1;
|
||||
$total = 10;
|
||||
$builder = $this->getMockQueryBuilder();
|
||||
$path = 'http://foo.bar?page=3';
|
||||
|
||||
$results = collect();
|
||||
|
||||
$builder->shouldReceive('get')->once()->andReturn($results);
|
||||
$builder->shouldReceive('getCountForPagination')->andReturn($total);
|
||||
|
||||
Context::set('path', $path);
|
||||
Context::set('page', $page);
|
||||
if (! defined('BASE_PATH')) {
|
||||
define('BASE_PATH', __DIR__);
|
||||
}
|
||||
|
||||
$container = Mockery::mock(Container::class);
|
||||
$container->shouldReceive('make')->once()->andReturnUsing(function ($interface, $args) {
|
||||
/** @var Collection $items */
|
||||
$items = $args['items'];
|
||||
return new LengthAwarePaginator(collect($items), $args['total'], $args['perPage'], $args['currentPage'], $args['options']);
|
||||
});
|
||||
ApplicationContext::setContainer($container);
|
||||
|
||||
Paginator::currentPageResolver(function () {
|
||||
return Context::get('page');
|
||||
});
|
||||
|
||||
Paginator::currentPathResolver(function () {
|
||||
return Context::get('path');
|
||||
});
|
||||
|
||||
$result = $builder->paginate();
|
||||
|
||||
$this->assertEquals(new LengthAwarePaginator($results, $total, $perPage, $page, [
|
||||
'path' => $path,
|
||||
'pageName' => $pageName,
|
||||
]), $result);
|
||||
}
|
||||
|
||||
public function testWhereRowValues()
|
||||
{
|
||||
$builder = $this->getBuilder();
|
||||
|
@ -71,7 +71,8 @@ class ConnectionResolver implements ConnectionResolverInterface
|
||||
Context::set($id, $connection);
|
||||
} finally {
|
||||
if (Coroutine::inCoroutine()) {
|
||||
defer(function () use ($connection) {
|
||||
defer(function () use ($connection, $id) {
|
||||
Context::set($id, null);
|
||||
$connection->release();
|
||||
});
|
||||
}
|
||||
|
@ -169,4 +169,18 @@ class ConnectionTest extends TestCase
|
||||
$this->assertSame('mysql:host=192.168.1.1;dbname=hyperf', $pdo->dsn);
|
||||
}]);
|
||||
}
|
||||
|
||||
public function testDbConnectionUseInDefer()
|
||||
{
|
||||
$container = ContainerStub::mockReadWriteContainer();
|
||||
|
||||
parallel([function () use ($container) {
|
||||
$resolver = $container->get(ConnectionResolverInterface::class);
|
||||
|
||||
defer(function () {
|
||||
$this->assertFalse(Context::has('database.connection.default'));
|
||||
});
|
||||
$resolver->connection();
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
@ -65,12 +65,12 @@ class PropertyHandlerVisitor extends NodeVisitorAbstract
|
||||
if ($this->visitorMetadata->hasExtends) {
|
||||
$constructor->stmts[] = $this->buildCallParentConstructorStatement();
|
||||
}
|
||||
$constructor->stmts[] = $this->buildStaticCallStatement();
|
||||
$constructor->stmts[] = $this->buildMethodCallStatement();
|
||||
$node->stmts = array_merge([$this->buildProxyTraitUseStatement()], [$constructor], $node->stmts);
|
||||
$this->visitorMetadata->hasConstructor = true;
|
||||
} else {
|
||||
if ($node instanceof Node\Stmt\ClassMethod && $node->name->toString() === '__construct') {
|
||||
$node->stmts = array_merge([$this->buildStaticCallStatement()], $node->stmts);
|
||||
$node->stmts = array_merge([$this->buildMethodCallStatement()], $node->stmts);
|
||||
}
|
||||
if ($node instanceof Node\Stmt\Class_ && ! $node->isAnonymous()) {
|
||||
$node->stmts = array_merge([$this->buildProxyTraitUseStatement()], $node->stmts);
|
||||
@ -115,9 +115,9 @@ class PropertyHandlerVisitor extends NodeVisitorAbstract
|
||||
]);
|
||||
}
|
||||
|
||||
protected function buildStaticCallStatement(): Node\Stmt\Expression
|
||||
protected function buildMethodCallStatement(): Node\Stmt\Expression
|
||||
{
|
||||
return new Node\Stmt\Expression(new Node\Expr\StaticCall(new Name('self'), '__handlePropertyHandler', [
|
||||
return new Node\Stmt\Expression(new Node\Expr\MethodCall(new Node\Expr\Variable('this'), '__handlePropertyHandler', [
|
||||
new Node\Arg(new Node\Scalar\MagicConst\Class_()),
|
||||
]));
|
||||
}
|
||||
|
@ -63,7 +63,7 @@ class Foo
|
||||
use \Hyperf\Di\Aop\PropertyHandlerTrait;
|
||||
function __construct()
|
||||
{
|
||||
self::__handlePropertyHandler(__CLASS__);
|
||||
$this->__handlePropertyHandler(__CLASS__);
|
||||
}
|
||||
}', $code);
|
||||
}
|
||||
@ -83,7 +83,7 @@ class Bar2 extends Bar
|
||||
use \Hyperf\Di\Aop\PropertyHandlerTrait;
|
||||
public function __construct(int $id)
|
||||
{
|
||||
self::__handlePropertyHandler(__CLASS__);
|
||||
$this->__handlePropertyHandler(__CLASS__);
|
||||
parent::__construct($id);
|
||||
}
|
||||
public static function build()
|
||||
@ -112,7 +112,7 @@ class Bar5
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
self::__handlePropertyHandler(__CLASS__);
|
||||
$this->__handlePropertyHandler(__CLASS__);
|
||||
$this->id = 9501;
|
||||
}
|
||||
};
|
||||
@ -141,7 +141,7 @@ class Bar4
|
||||
use \Hyperf\Di\Aop\PropertyHandlerTrait;
|
||||
function __construct()
|
||||
{
|
||||
self::__handlePropertyHandler(__CLASS__);
|
||||
$this->__handlePropertyHandler(__CLASS__);
|
||||
}
|
||||
public function toMethodString() : string
|
||||
{
|
||||
@ -185,7 +185,7 @@ class Bar3 extends Bar
|
||||
if (method_exists(parent::class, \'__construct\')) {
|
||||
parent::__construct(...func_get_args());
|
||||
}
|
||||
self::__handlePropertyHandler(__CLASS__);
|
||||
$this->__handlePropertyHandler(__CLASS__);
|
||||
}
|
||||
public function getId() : int
|
||||
{
|
||||
|
@ -46,7 +46,7 @@ abstract class AbstractRequestHandler
|
||||
*/
|
||||
public function __construct(array $middlewares, $coreHandler, ContainerInterface $container)
|
||||
{
|
||||
$this->middlewares = array_unique($middlewares);
|
||||
$this->middlewares = array_values(array_unique($middlewares));
|
||||
$this->coreHandler = $coreHandler;
|
||||
$this->container = $container;
|
||||
}
|
||||
|
2
src/encryption/.gitattributes
vendored
Normal file
2
src/encryption/.gitattributes
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/tests export-ignore
|
||||
/.github export-ignore
|
25
src/encryption/.github/workflows/release.yml
vendored
Normal file
25
src/encryption/.github/workflows/release.yml
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
on:
|
||||
push:
|
||||
# Sequence of patterns matched against refs/tags
|
||||
tags:
|
||||
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
|
||||
|
||||
name: Release
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ github.ref }}
|
||||
release_name: Release ${{ github.ref }}
|
||||
draft: false
|
||||
prerelease: false
|
22
src/encryption/LICENSE
Normal file
22
src/encryption/LICENSE
Normal file
@ -0,0 +1,22 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) Taylor Otwell
|
||||
Copyright (c) Hyperf
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
36
src/encryption/composer.json
Normal file
36
src/encryption/composer.json
Normal file
@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "hyperf/encryption",
|
||||
"type": "library",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"php",
|
||||
"hyperf",
|
||||
"encryption"
|
||||
],
|
||||
"description": "",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Hyperf\\Encryption\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"HyperfTest\\Encryption\\": "tests/"
|
||||
}
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.2",
|
||||
"ext-json": "*",
|
||||
"ext-openssl": "*",
|
||||
"hyperf/contract": "~2.0.0",
|
||||
"psr/container": "^1.0"
|
||||
},
|
||||
"config": {
|
||||
"sort-packages": true
|
||||
},
|
||||
"extra": {
|
||||
"hyperf": {
|
||||
"config": "Hyperf\\Encryption\\ConfigProvider"
|
||||
}
|
||||
}
|
||||
}
|
17
src/encryption/publish/encryption.php
Normal file
17
src/encryption/publish/encryption.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
return [
|
||||
'default' => [
|
||||
'key' => 'Hyperf',
|
||||
'cipher' => 'AES-128-CBC',
|
||||
],
|
||||
];
|
34
src/encryption/src/ConfigProvider.php
Normal file
34
src/encryption/src/ConfigProvider.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
namespace Hyperf\Encryption;
|
||||
|
||||
use Hyperf\Encryption\Contract\EncrypterInterface;
|
||||
|
||||
class ConfigProvider
|
||||
{
|
||||
public function __invoke(): array
|
||||
{
|
||||
return [
|
||||
'dependencies' => [
|
||||
EncrypterInterface::class => EncrypterInvoker::class,
|
||||
],
|
||||
'publish' => [
|
||||
[
|
||||
'id' => 'config',
|
||||
'description' => 'The config for encryption.',
|
||||
'source' => __DIR__ . '/../publish/encryption.php',
|
||||
'destination' => BASE_PATH . '/config/autoload/encryption.php',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
33
src/encryption/src/Contract/EncrypterInterface.php
Normal file
33
src/encryption/src/Contract/EncrypterInterface.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
namespace Hyperf\Encryption\Contract;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
interface EncrypterInterface
|
||||
{
|
||||
/**
|
||||
* Encrypt the given value.
|
||||
*
|
||||
* @param mixed $value
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function encrypt($value, bool $serialize = true): string;
|
||||
|
||||
/**
|
||||
* Decrypt the given value.
|
||||
*
|
||||
* @throws RuntimeException
|
||||
* @return mixed
|
||||
*/
|
||||
public function decrypt(string $payload, bool $unserialize = true);
|
||||
}
|
229
src/encryption/src/Encrypter.php
Normal file
229
src/encryption/src/Encrypter.php
Normal file
@ -0,0 +1,229 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
namespace Hyperf\Encryption;
|
||||
|
||||
use Hyperf\Encryption\Contract\EncrypterInterface;
|
||||
use Hyperf\Encryption\Exception\DecryptException;
|
||||
use Hyperf\Encryption\Exception\EncryptException;
|
||||
use Hyperf\Encryption\Exception\InvalidArgumentException;
|
||||
|
||||
class Encrypter implements EncrypterInterface
|
||||
{
|
||||
/**
|
||||
* The encryption key.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $key;
|
||||
|
||||
/**
|
||||
* The algorithm used for encryption.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $cipher;
|
||||
|
||||
public function __construct(string $key, string $cipher = 'AES-128-CBC')
|
||||
{
|
||||
if (empty($key)) {
|
||||
throw new InvalidArgumentException('Encrypter key Not Set');
|
||||
}
|
||||
|
||||
if (! in_array(strtolower($cipher), openssl_get_cipher_methods())) {
|
||||
throw new InvalidArgumentException('Encrypter ciphers Not supported');
|
||||
}
|
||||
|
||||
$this->key = $key;
|
||||
$this->cipher = $cipher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt the given value.
|
||||
*
|
||||
* @param mixed $value
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function encrypt($value, bool $serialize = true): string
|
||||
{
|
||||
$iv = random_bytes(openssl_cipher_iv_length($this->cipher));
|
||||
|
||||
// First we will encrypt the value using OpenSSL. After this is encrypted we
|
||||
// will proceed to calculating a MAC for the encrypted value so that this
|
||||
// value can be verified later as not having been changed by the users.
|
||||
$value = \openssl_encrypt(
|
||||
$serialize ? serialize($value) : $value,
|
||||
$this->cipher,
|
||||
$this->key,
|
||||
0,
|
||||
$iv
|
||||
);
|
||||
|
||||
if ($value === false) {
|
||||
throw new EncryptException('Could not encrypt the data.');
|
||||
}
|
||||
|
||||
// Once we get the encrypted value we'll go ahead and base64_encode the input
|
||||
// vector and create the MAC for the encrypted value so we can then verify
|
||||
// its authenticity. Then, we'll JSON the data into the "payload" array.
|
||||
$mac = $this->hash($iv = base64_encode($iv), $value);
|
||||
|
||||
$json = json_encode(compact('iv', 'value', 'mac'), JSON_UNESCAPED_SLASHES);
|
||||
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
throw new EncryptException('Could not encrypt the data.');
|
||||
}
|
||||
|
||||
return base64_encode($json);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt a string without serialization.
|
||||
*
|
||||
* @param string $value
|
||||
* @throws \Exception
|
||||
* @return string
|
||||
*/
|
||||
public function encryptString($value)
|
||||
{
|
||||
return $this->encrypt($value, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt the given value.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function decrypt(string $payload, bool $unserialize = true)
|
||||
{
|
||||
$payload = $this->getJsonPayload($payload);
|
||||
|
||||
$iv = base64_decode($payload['iv']);
|
||||
|
||||
// Here we will decrypt the value. If we are able to successfully decrypt it
|
||||
// we will then unserialize it and return it out to the caller. If we are
|
||||
// unable to decrypt this value we will throw out an exception message.
|
||||
$decrypted = \openssl_decrypt(
|
||||
$payload['value'],
|
||||
$this->cipher,
|
||||
$this->key,
|
||||
0,
|
||||
$iv
|
||||
);
|
||||
|
||||
if ($decrypted === false) {
|
||||
throw new DecryptException('Could not decrypt the data.');
|
||||
}
|
||||
|
||||
return $unserialize ? unserialize($decrypted) : $decrypted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt the given string without unserialization.
|
||||
*
|
||||
* @param string $payload
|
||||
* @return string
|
||||
*/
|
||||
public function decryptString($payload)
|
||||
{
|
||||
return $this->decrypt($payload, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the encryption key.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getKey()
|
||||
{
|
||||
return $this->key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a MAC for the given value.
|
||||
*
|
||||
* @param string $iv
|
||||
* @param mixed $value
|
||||
* @return string
|
||||
*/
|
||||
protected function hash($iv, $value)
|
||||
{
|
||||
return hash_hmac('sha256', $iv . $value, $this->key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the JSON array from the given payload.
|
||||
*
|
||||
* @param string $payload
|
||||
* @return array
|
||||
*/
|
||||
protected function getJsonPayload($payload)
|
||||
{
|
||||
$payload = json_decode(base64_decode($payload), true);
|
||||
|
||||
// If the payload is not valid JSON or does not have the proper keys set we will
|
||||
// assume it is invalid and bail out of the routine since we will not be able
|
||||
// to decrypt the given value. We'll also check the MAC for this encryption.
|
||||
if (! $this->validPayload($payload)) {
|
||||
throw new DecryptException('The payload is invalid.');
|
||||
}
|
||||
|
||||
if (! $this->validMac($payload)) {
|
||||
throw new DecryptException('The MAC is invalid.');
|
||||
}
|
||||
|
||||
return $payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the encryption payload is valid.
|
||||
*
|
||||
* @param mixed $payload
|
||||
* @return bool
|
||||
*/
|
||||
protected function validPayload($payload)
|
||||
{
|
||||
return is_array($payload) && isset($payload['iv'], $payload['value'], $payload['mac']) &&
|
||||
strlen(base64_decode($payload['iv'], true)) === openssl_cipher_iv_length($this->cipher);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the MAC for the given payload is valid.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function validMac(array $payload)
|
||||
{
|
||||
$calculated = $this->calculateMac($payload, $bytes = random_bytes(16));
|
||||
|
||||
return hash_equals(
|
||||
hash_hmac('sha256', $payload['mac'], $bytes, true),
|
||||
$calculated
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the hash of the given payload.
|
||||
*
|
||||
* @param array $payload
|
||||
* @param string $bytes
|
||||
* @return string
|
||||
*/
|
||||
protected function calculateMac($payload, $bytes)
|
||||
{
|
||||
return hash_hmac(
|
||||
'sha256',
|
||||
$this->hash($payload['iv'], $payload['value']),
|
||||
$bytes,
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
38
src/encryption/src/EncrypterFactory.php
Normal file
38
src/encryption/src/EncrypterFactory.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
namespace Hyperf\Encryption;
|
||||
|
||||
use Hyperf\Contract\ConfigInterface;
|
||||
use Hyperf\Encryption\Contract\EncrypterInterface;
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
||||
class EncrypterFactory
|
||||
{
|
||||
/**
|
||||
* @var ConfigInterface
|
||||
*/
|
||||
protected $config;
|
||||
|
||||
public function __construct(ContainerInterface $container)
|
||||
{
|
||||
$this->config = $container->get(ConfigInterface::class);
|
||||
}
|
||||
|
||||
public function get(string $name): EncrypterInterface
|
||||
{
|
||||
$encryption = $this->config->get('encryption.' . $name, []);
|
||||
$key = $encryption['key'] ?? '';
|
||||
$cipher = $encryption['cipher'] ?? 'AES-128-CBC';
|
||||
|
||||
return new Encrypter($key, $cipher);
|
||||
}
|
||||
}
|
24
src/encryption/src/EncrypterInvoker.php
Normal file
24
src/encryption/src/EncrypterInvoker.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://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
namespace Hyperf\Encryption;
|
||||
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
||||
class EncrypterInvoker
|
||||
{
|
||||
public function __invoke(ContainerInterface $container)
|
||||
{
|
||||
$factory = $container->get(EncrypterFactory::class);
|
||||
|
||||
return $factory->get('default');
|
||||
}
|
||||
}
|
16
src/encryption/src/Exception/DecryptException.php
Normal file
16
src/encryption/src/Exception/DecryptException.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
namespace Hyperf\Encryption\Exception;
|
||||
|
||||
class DecryptException extends \RuntimeException
|
||||
{
|
||||
}
|
16
src/encryption/src/Exception/EncryptException.php
Normal file
16
src/encryption/src/Exception/EncryptException.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
namespace Hyperf\Encryption\Exception;
|
||||
|
||||
class EncryptException extends \RuntimeException
|
||||
{
|
||||
}
|
16
src/encryption/src/Exception/InvalidArgumentException.php
Normal file
16
src/encryption/src/Exception/InvalidArgumentException.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
namespace Hyperf\Encryption\Exception;
|
||||
|
||||
class InvalidArgumentException extends \InvalidArgumentException
|
||||
{
|
||||
}
|
74
src/encryption/tests/EncrypterTest.php
Normal file
74
src/encryption/tests/EncrypterTest.php
Normal file
@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
namespace HyperfTest\Encryption;
|
||||
|
||||
use Hyperf\Config\Config;
|
||||
use Hyperf\Contract\ConfigInterface;
|
||||
use Hyperf\Encryption\Contract\EncrypterInterface;
|
||||
use Hyperf\Encryption\EncrypterFactory;
|
||||
use Hyperf\Encryption\EncrypterInvoker;
|
||||
use Hyperf\Utils\ApplicationContext;
|
||||
use Mockery;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @coversNothing
|
||||
*/
|
||||
class EncrypterTest extends TestCase
|
||||
{
|
||||
protected function tearDown(): void
|
||||
{
|
||||
Mockery::close();
|
||||
}
|
||||
|
||||
public function mockContainer(): ContainerInterface
|
||||
{
|
||||
$container = Mockery::mock(ContainerInterface::class);
|
||||
ApplicationContext::setContainer($container);
|
||||
$config = new Config([
|
||||
'encryption' => [
|
||||
'default' => [
|
||||
'key' => '123456',
|
||||
'cipher' => 'AES-256-CBC',
|
||||
],
|
||||
],
|
||||
]);
|
||||
$container->shouldReceive('get')->with(ConfigInterface::class)->andReturn($config);
|
||||
$container->shouldReceive('get')->with(EncrypterFactory::class)->andReturnUsing(function () use ($container) {
|
||||
return new EncrypterFactory($container);
|
||||
});
|
||||
$container->shouldReceive('get')->with(EncrypterInterface::class)->andReturnUsing(function () use ($container) {
|
||||
return (new EncrypterInvoker())($container);
|
||||
});
|
||||
return $container;
|
||||
}
|
||||
|
||||
public function testEncodeString()
|
||||
{
|
||||
$input = 'Hello Word.';
|
||||
$container = $this->mockContainer();
|
||||
$encrypter = $container->get(EncrypterInterface::class);
|
||||
$encrypt = $encrypter->encryptString($input);
|
||||
$this->assertSame($input, $encrypter->decryptString($encrypt));
|
||||
}
|
||||
|
||||
public function testEncodeObject()
|
||||
{
|
||||
$input = range(1, 3);
|
||||
$container = $this->mockContainer();
|
||||
$encrypter = $container->get(EncrypterInterface::class);
|
||||
$encrypt = $encrypter->encrypt($input);
|
||||
$this->assertSame($input, $encrypter->decrypt($encrypt));
|
||||
}
|
||||
}
|
@ -180,8 +180,9 @@ class CoroutineHandler
|
||||
protected function getResponse(RawResponse $raw, RequestInterface $request, array $options, float $transferTime)
|
||||
{
|
||||
$body = $raw->body;
|
||||
if (isset($options['sink']) && is_string($options['sink'])) {
|
||||
$body = $this->createSink($raw->body, $options['sink']);
|
||||
$sink = $options['sink'] ?? null;
|
||||
if (isset($sink) && (is_string($sink) || is_resource($sink))) {
|
||||
$body = $this->createSink($body, $sink);
|
||||
}
|
||||
|
||||
$response = new Psr7\Response(
|
||||
@ -210,16 +211,16 @@ class CoroutineHandler
|
||||
return Utils::streamFor($body);
|
||||
}
|
||||
|
||||
protected function createSink(string $body, string $sink)
|
||||
/**
|
||||
* @param resource|string $stream
|
||||
*/
|
||||
protected function createSink(string $body, $stream)
|
||||
{
|
||||
if (! empty($options['stream'])) {
|
||||
return $body;
|
||||
if (is_string($stream)) {
|
||||
$stream = fopen($stream, 'w+');
|
||||
}
|
||||
|
||||
$stream = fopen($sink, 'w+');
|
||||
if ($body !== '') {
|
||||
fwrite($stream, $body);
|
||||
fseek($stream, 0);
|
||||
}
|
||||
|
||||
return $stream;
|
||||
|
@ -289,8 +289,30 @@ class CoroutineHandlerTest extends TestCase
|
||||
@mkdir($dir, 0755, true);
|
||||
|
||||
$handler = new CoroutineHandlerStub();
|
||||
$handler->createSink($body = uniqid(), $sink = $dir . uniqid());
|
||||
$stream = $handler->createSink($body = uniqid(), $sink = $dir . uniqid());
|
||||
$this->assertSame($body, file_get_contents($sink));
|
||||
$this->assertSame('', stream_get_contents($stream));
|
||||
|
||||
$stream = $handler->createSink($body = uniqid(), $sink);
|
||||
$this->assertSame($body, file_get_contents($sink));
|
||||
$this->assertSame('', stream_get_contents($stream));
|
||||
fseek($stream, 0);
|
||||
$this->assertSame($body, stream_get_contents($stream));
|
||||
}
|
||||
|
||||
public function testResourceSink()
|
||||
{
|
||||
$dir = BASE_PATH . '/runtime/guzzle/';
|
||||
@mkdir($dir, 0755, true);
|
||||
$sink = fopen($file = $dir . uniqid(), 'w+');
|
||||
$handler = new CoroutineHandlerStub();
|
||||
$stream = $handler->createSink($body1 = uniqid(), $sink);
|
||||
$this->assertSame('', stream_get_contents($stream));
|
||||
$stream = $handler->createSink($body2 = uniqid(), $sink);
|
||||
$this->assertSame('', stream_get_contents($stream));
|
||||
$this->assertSame($body1 . $body2, file_get_contents($file));
|
||||
fseek($sink, 0);
|
||||
$this->assertSame($body1 . $body2, stream_get_contents($stream));
|
||||
}
|
||||
|
||||
public function testExpect100Continue()
|
||||
|
@ -26,7 +26,7 @@ class CoroutineHandlerStub extends CoroutineHandler
|
||||
$this->statusCode = $statusCode;
|
||||
}
|
||||
|
||||
public function createSink(string $body, string $sink)
|
||||
public function createSink(string $body, $sink)
|
||||
{
|
||||
return parent::createSink($body, $sink);
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ namespace Hyperf\HttpMessage\Server\Request;
|
||||
use Hyperf\HttpMessage\Exception\BadRequestHttpException;
|
||||
use Hyperf\HttpMessage\Server\RequestParserInterface;
|
||||
use Hyperf\Utils\Codec\Json;
|
||||
use Hyperf\Utils\Exception\InvalidArgumentException;
|
||||
use InvalidArgumentException;
|
||||
|
||||
class JsonParser implements RequestParserInterface
|
||||
{
|
||||
|
@ -14,7 +14,7 @@ namespace Hyperf\HttpMessage\Server\Request;
|
||||
use Hyperf\HttpMessage\Exception\BadRequestHttpException;
|
||||
use Hyperf\HttpMessage\Server\RequestParserInterface;
|
||||
use Hyperf\Utils\Codec\Xml;
|
||||
use Hyperf\Utils\Exception\InvalidArgumentException;
|
||||
use InvalidArgumentException;
|
||||
|
||||
class XmlParser implements RequestParserInterface
|
||||
{
|
||||
|
38
src/http-message/tests/RequestParserTest.php
Normal file
38
src/http-message/tests/RequestParserTest.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* This file is part of Hyperf.
|
||||
*
|
||||
* @link https://www.hyperf.io
|
||||
* @document https://hyperf.wiki
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
namespace HyperfTest\HttpMessage;
|
||||
|
||||
use Hyperf\HttpMessage\Exception\BadRequestHttpException;
|
||||
use Hyperf\HttpMessage\Server\Request\JsonParser;
|
||||
use Hyperf\HttpMessage\Server\Request\XmlParser;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @coversNothing
|
||||
*/
|
||||
class RequestParserTest extends TestCase
|
||||
{
|
||||
public function testJsonParserFailed()
|
||||
{
|
||||
$this->expectException(BadRequestHttpException::class);
|
||||
$parser = new JsonParser();
|
||||
$parser->parse('{"hy"', '');
|
||||
}
|
||||
|
||||
public function testXmlParserFailed()
|
||||
{
|
||||
$this->expectException(BadRequestHttpException::class);
|
||||
$parser = new XmlParser();
|
||||
$parser->parse('{"hy"', '');
|
||||
}
|
||||
}
|
@ -122,7 +122,7 @@ class Server implements OnRequestInterface, MiddlewareInitializerInterface
|
||||
if (! isset($psr7Response)) {
|
||||
return;
|
||||
}
|
||||
if (! isset($psr7Request) || $psr7Request->getMethod() === 'HEAD') {
|
||||
if (isset($psr7Request) && $psr7Request->getMethod() === 'HEAD') {
|
||||
$this->responseEmitter->emit($psr7Response, $response, false);
|
||||
} else {
|
||||
$this->responseEmitter->emit($psr7Response, $response, true);
|
||||
|
@ -16,6 +16,7 @@ use Hyperf\Contract\StdoutLoggerInterface;
|
||||
use Hyperf\Nacos\Client;
|
||||
use Hyperf\Process\AbstractProcess;
|
||||
use Hyperf\Process\ProcessCollector;
|
||||
use Hyperf\Process\ProcessManager;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Swoole\Coroutine\Server as CoServer;
|
||||
use Swoole\Server;
|
||||
@ -55,7 +56,7 @@ class FetchConfigProcess extends AbstractProcess
|
||||
$cache = [];
|
||||
$config = $this->container->get(ConfigInterface::class);
|
||||
$client = $this->container->get(Client::class);
|
||||
while (true) {
|
||||
while (ProcessManager::isRunning()) {
|
||||
$remoteConfig = $client->pull();
|
||||
if ($remoteConfig != $cache) {
|
||||
$pipeMessage = new PipeMessage($remoteConfig);
|
||||
|
@ -22,6 +22,7 @@ use Hyperf\Nacos\Constants;
|
||||
use Hyperf\Nacos\Exception\RuntimeException;
|
||||
use Hyperf\Nacos\Instance;
|
||||
use Hyperf\Nacos\Service;
|
||||
use Hyperf\Server\Event\CoroutineServerStart;
|
||||
use Hyperf\Utils\Arr;
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
||||
@ -47,6 +48,7 @@ class MainWorkerStartListener implements ListenerInterface
|
||||
{
|
||||
return [
|
||||
MainWorkerStart::class,
|
||||
CoroutineServerStart::class,
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,7 @@ use Hyperf\Framework\Event\OnShutdown;
|
||||
use Hyperf\Nacos\Api\NacosInstance;
|
||||
use Hyperf\Nacos\Contract\LoggerInterface;
|
||||
use Hyperf\Nacos\Instance;
|
||||
use Hyperf\Server\Event\CoroutineServerStop;
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
||||
class OnShutdownListener implements ListenerInterface
|
||||
@ -26,6 +27,11 @@ class OnShutdownListener implements ListenerInterface
|
||||
*/
|
||||
protected $container;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $processed = false;
|
||||
|
||||
public function __construct(ContainerInterface $container)
|
||||
{
|
||||
$this->container = $container;
|
||||
@ -35,11 +41,17 @@ class OnShutdownListener implements ListenerInterface
|
||||
{
|
||||
return [
|
||||
OnShutdown::class,
|
||||
CoroutineServerStop::class,
|
||||
];
|
||||
}
|
||||
|
||||
public function process(object $event)
|
||||
{
|
||||
if ($this->processed) {
|
||||
return;
|
||||
}
|
||||
$this->processed = true;
|
||||
|
||||
$config = $this->container->get(ConfigInterface::class);
|
||||
if (! $config->get('nacos.enable', true)) {
|
||||
return;
|
||||
|
@ -17,6 +17,7 @@ use Hyperf\Nacos\Contract\LoggerInterface;
|
||||
use Hyperf\Nacos\Instance;
|
||||
use Hyperf\Nacos\Service;
|
||||
use Hyperf\Process\AbstractProcess;
|
||||
use Hyperf\Process\ProcessManager;
|
||||
|
||||
class InstanceBeatProcess extends AbstractProcess
|
||||
{
|
||||
@ -33,7 +34,7 @@ class InstanceBeatProcess extends AbstractProcess
|
||||
|
||||
$config = $this->container->get(ConfigInterface::class);
|
||||
$logger = $this->container->get(LoggerInterface::class);
|
||||
while (true) {
|
||||
while (ProcessManager::isRunning()) {
|
||||
sleep($config->get('nacos.client.beat_interval', 5));
|
||||
$send = $nacosInstance->beat($service, $instance);
|
||||
if ($send) {
|
||||
|
@ -14,6 +14,7 @@ namespace Hyperf\Nsq\Listener;
|
||||
use Hyperf\Event\Contract\ListenerInterface;
|
||||
use Hyperf\Framework\Event\BeforeMainServerStart;
|
||||
use Hyperf\Nsq\ConsumerManager;
|
||||
use Hyperf\Server\Event\MainCoroutineServerStart;
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
||||
/**
|
||||
@ -38,6 +39,7 @@ class BeforeMainServerStartListener implements ListenerInterface
|
||||
{
|
||||
return [
|
||||
BeforeMainServerStart::class,
|
||||
MainCoroutineServerStart::class,
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,7 @@ return [
|
||||
'concurrency' => 100,
|
||||
'engine' => [
|
||||
'elasticsearch' => [
|
||||
'driver' => \Hyperf\Scout\Provider\ElasticsearchProvider::class,
|
||||
'driver' => Hyperf\Scout\Provider\ElasticsearchProvider::class,
|
||||
'index' => env('ELASTICSEARCH_INDEX', 'hyperf'),
|
||||
'hosts' => [
|
||||
env('ELASTICSEARCH_HOST', 'http://localhost'),
|
||||
|
@ -22,10 +22,17 @@ use Hyperf\Utils\Collection as BaseCollection;
|
||||
class ElasticsearchEngine extends Engine
|
||||
{
|
||||
/**
|
||||
* Index where the models will be saved.
|
||||
* Elastic server version.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $version;
|
||||
|
||||
/**
|
||||
* Index where the models will be saved.
|
||||
*
|
||||
* @var null|string
|
||||
*/
|
||||
protected $index;
|
||||
|
||||
/**
|
||||
@ -37,13 +44,13 @@ class ElasticsearchEngine extends Engine
|
||||
|
||||
/**
|
||||
* Create a new engine instance.
|
||||
*
|
||||
* @param $index
|
||||
*/
|
||||
public function __construct(Client $client, $index)
|
||||
public function __construct(Client $client, ?string $index = null)
|
||||
{
|
||||
$this->elastic = $client;
|
||||
$this->index = $index;
|
||||
if ($index) {
|
||||
$this->index = $this->initIndex($client, $index);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -55,13 +62,19 @@ class ElasticsearchEngine extends Engine
|
||||
{
|
||||
$params['body'] = [];
|
||||
$models->each(function ($model) use (&$params) {
|
||||
$params['body'][] = [
|
||||
'update' => [
|
||||
if ($this->index) {
|
||||
$update = [
|
||||
'_id' => $model->getKey(),
|
||||
'_index' => $this->index,
|
||||
'_type' => $model->searchableAs(),
|
||||
],
|
||||
];
|
||||
];
|
||||
} else {
|
||||
$update = [
|
||||
'_id' => $model->getKey(),
|
||||
'_index' => $model->searchableAs(),
|
||||
];
|
||||
}
|
||||
$params['body'][] = ['update' => $update];
|
||||
$params['body'][] = [
|
||||
'doc' => $model->toSearchableArray(),
|
||||
'doc_as_upsert' => true,
|
||||
@ -79,13 +92,19 @@ class ElasticsearchEngine extends Engine
|
||||
{
|
||||
$params['body'] = [];
|
||||
$models->each(function ($model) use (&$params) {
|
||||
$params['body'][] = [
|
||||
'delete' => [
|
||||
if ($this->index) {
|
||||
$delete = [
|
||||
'_id' => $model->getKey(),
|
||||
'_index' => $this->index,
|
||||
'_type' => $model->searchableAs(),
|
||||
],
|
||||
];
|
||||
];
|
||||
} else {
|
||||
$delete = [
|
||||
'_id' => $model->getKey(),
|
||||
'_index' => $model->searchableAs(),
|
||||
];
|
||||
}
|
||||
$params['body'][] = ['delete' => $delete];
|
||||
});
|
||||
$this->elastic->bulk($params);
|
||||
}
|
||||
@ -171,6 +190,24 @@ class ElasticsearchEngine extends Engine
|
||||
->unsearchable();
|
||||
}
|
||||
|
||||
protected function initIndex(Client $client, string $index): ?string
|
||||
{
|
||||
if (! static::$version) {
|
||||
try {
|
||||
static::$version = $client->info()['version']['number'];
|
||||
} catch (\Throwable $exception) {
|
||||
static::$version = '0.0.0';
|
||||
}
|
||||
}
|
||||
|
||||
// When the version of elasticsearch is more than 7.0.0, it does not support type, so set `null` to `$index`.
|
||||
if (version_compare(static::$version, '7.0.0', '<')) {
|
||||
return $index;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the given search on the engine.
|
||||
*
|
||||
@ -189,6 +226,10 @@ class ElasticsearchEngine extends Engine
|
||||
],
|
||||
],
|
||||
];
|
||||
if (! $this->index) {
|
||||
unset($params['type']);
|
||||
$params['index'] = $builder->index ?: $builder->model->searchableAs();
|
||||
}
|
||||
if ($sort = $this->sort($builder)) {
|
||||
$params['body']['sort'] = $sort;
|
||||
}
|
||||
|
@ -56,13 +56,9 @@ class ServerConfig implements Arrayable
|
||||
->setCallbacks($config['callbacks'] ?? []);
|
||||
}
|
||||
|
||||
public function __set($name, $value): self
|
||||
public function __set($name, $value)
|
||||
{
|
||||
if (! $this->isAvailableProperty($name)) {
|
||||
throw new \InvalidArgumentException(sprintf('Invalid property %s', $name));
|
||||
}
|
||||
$this->config[$name] = $value;
|
||||
return $this;
|
||||
$this->set($name, $value);
|
||||
}
|
||||
|
||||
public function __get($name)
|
||||
@ -81,7 +77,7 @@ class ServerConfig implements Arrayable
|
||||
if (! $this->isAvailableProperty($propertyName)) {
|
||||
throw new \InvalidArgumentException(sprintf('Invalid property %s', $propertyName));
|
||||
}
|
||||
return $prefix === 'set' ? $this->__set($propertyName, ...$arguments) : $this->__get($propertyName);
|
||||
return $prefix === 'set' ? $this->set($propertyName, ...$arguments) : $this->__get($propertyName);
|
||||
}
|
||||
}
|
||||
|
||||
@ -96,6 +92,15 @@ class ServerConfig implements Arrayable
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
protected function set($name, $value): self
|
||||
{
|
||||
if (! $this->isAvailableProperty($name)) {
|
||||
throw new \InvalidArgumentException(sprintf('Invalid property %s', $name));
|
||||
}
|
||||
$this->config[$name] = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function isAvailableProperty(string $name)
|
||||
{
|
||||
return in_array($name, [
|
||||
|
@ -22,10 +22,19 @@ class ConsulAgentFactory
|
||||
{
|
||||
return new Agent(function () use ($container) {
|
||||
$config = $container->get(ConfigInterface::class);
|
||||
return $container->get(ClientFactory::class)->create([
|
||||
$token = $config->get('consul.token', '');
|
||||
$options = [
|
||||
'timeout' => 2,
|
||||
'base_uri' => $config->get('consul.uri', Agent::DEFAULT_URI),
|
||||
]);
|
||||
];
|
||||
|
||||
if (! empty($token)) {
|
||||
$options['headers'] = [
|
||||
'X-Consul-Token' => $token,
|
||||
];
|
||||
}
|
||||
|
||||
return $container->get(ClientFactory::class)->create($options);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -19,5 +19,6 @@ return [
|
||||
'gc_maxlifetime' => 1200,
|
||||
'session_name' => 'HYPERF_SESSION_ID',
|
||||
'domain' => null,
|
||||
'cookie_lifetime' => 5 * 60 * 60,
|
||||
],
|
||||
];
|
||||
|
@ -95,7 +95,8 @@ class SessionMiddleware implements MiddlewareInterface
|
||||
if ($this->config->get('session.options.expire_on_close')) {
|
||||
$expirationDate = 0;
|
||||
} else {
|
||||
$expirationDate = Carbon::now()->addMinutes(5 * 60)->getTimestamp();
|
||||
$expireSeconds = $this->config->get('session.options.cookie_lifetime', 5 * 60 * 60);
|
||||
$expirationDate = Carbon::now()->addSeconds($expireSeconds)->getTimestamp();
|
||||
}
|
||||
return $expirationDate;
|
||||
}
|
||||
|
@ -64,6 +64,7 @@ class SessionMiddlewareTest extends TestCase
|
||||
$config->shouldReceive('get')->with('session.options.expire_on_close')->andReturn(1);
|
||||
$config->shouldReceive('get')->with('session.options.session_name', 'HYPERF_SESSION_ID')->andReturn('HYPERF_SESSION_ID');
|
||||
$config->shouldReceive('get')->with('session.options.domain')->andReturn(null);
|
||||
$config->shouldReceive('get')->with('session.options.cookie_lifetime', 5 * 60)->andReturn(5 * 60);
|
||||
|
||||
$sessionManager = new SessionManager($container, $config);
|
||||
$middleware = new SessionMiddleware($sessionManager, $config);
|
||||
@ -109,6 +110,7 @@ class SessionMiddlewareTest extends TestCase
|
||||
$config->shouldReceive('get')->with('session.options.expire_on_close')->andReturn(0);
|
||||
$config->shouldReceive('get')->with('session.options.session_name', 'HYPERF_SESSION_ID')->andReturn('HYPERF_SESSION_ID');
|
||||
$config->shouldReceive('get')->with('session.options.domain')->andReturn(null);
|
||||
$config->shouldReceive('get')->with('session.options.cookie_lifetime', 5 * 60 * 60)->andReturn(10 * 60 * 60);
|
||||
|
||||
$sessionManager = new SessionManager($container, $config);
|
||||
$middleware = new SessionMiddleware($sessionManager, $config);
|
||||
@ -119,7 +121,7 @@ class SessionMiddlewareTest extends TestCase
|
||||
|
||||
/** @var Cookie $cookie */
|
||||
$cookie = $response->getCookies()['']['/'][$session->getName()];
|
||||
$this->assertSame($time + (5 * 60 * 60), $cookie->getExpiresTime());
|
||||
$this->assertSame($time + (10 * 60 * 60), $cookie->getExpiresTime());
|
||||
}
|
||||
|
||||
public function testSessionOptionsDomain()
|
||||
@ -135,6 +137,7 @@ class SessionMiddlewareTest extends TestCase
|
||||
'path' => BASE_PATH . '/runtime/session',
|
||||
'gc_maxlifetime' => 1200,
|
||||
'session_name' => 'HYPERF_SESSION_ID',
|
||||
'cookie_lifetime' => 5 * 60 * 60,
|
||||
],
|
||||
],
|
||||
]);
|
||||
@ -160,6 +163,7 @@ class SessionMiddlewareTest extends TestCase
|
||||
'gc_maxlifetime' => 1200,
|
||||
'session_name' => 'HYPERF_SESSION_ID',
|
||||
'domain' => null,
|
||||
'cookie_lifetime' => 5 * 60 * 60,
|
||||
],
|
||||
],
|
||||
]);
|
||||
@ -185,6 +189,7 @@ class SessionMiddlewareTest extends TestCase
|
||||
'gc_maxlifetime' => 1200,
|
||||
'session_name' => 'HYPERF_SESSION_ID',
|
||||
'domain' => 'hyperf.wiki',
|
||||
'cookie_lifetime' => 5 * 60 * 60,
|
||||
],
|
||||
],
|
||||
]);
|
||||
@ -235,6 +240,7 @@ class SessionMiddlewareTest extends TestCase
|
||||
$config = Mockery::mock(ConfigInterface::class);
|
||||
$config->shouldReceive('get')->with('session.options.expire_on_close')->andReturn(0);
|
||||
$config->shouldReceive('get')->with('session.options.domain')->andReturn(null);
|
||||
$config->shouldReceive('get')->with('session.options.cookie_lifetime', 5 * 60 * 60)->andReturn(5 * 60);
|
||||
$middleware = new SessionMiddleware(Mockery::mock(SessionManager::class), $config);
|
||||
$ref = new ReflectionClass($middleware);
|
||||
$method = $ref->getMethod('addCookieToResponse');
|
||||
|
@ -91,11 +91,15 @@ class HttpClientAspect implements AroundInterface
|
||||
);
|
||||
$options['headers'] = array_replace($options['headers'] ?? [], $appendHeaders);
|
||||
$proceedingJoinPoint->arguments['keys']['options'] = $options;
|
||||
$result = $proceedingJoinPoint->process();
|
||||
if ($result instanceof ResponseInterface) {
|
||||
$span->setTag($this->spanTagManager->get('http_client', 'http.status_code'), $result->getStatusCode());
|
||||
|
||||
try {
|
||||
$result = $proceedingJoinPoint->process();
|
||||
if ($result instanceof ResponseInterface) {
|
||||
$span->setTag($this->spanTagManager->get('http_client', 'http.status_code'), $result->getStatusCode());
|
||||
}
|
||||
} finally {
|
||||
$span->finish();
|
||||
}
|
||||
$span->finish();
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
@ -90,13 +90,16 @@ class JsonRpcAspect implements AroundInterface
|
||||
}
|
||||
|
||||
if ($proceedingJoinPoint->methodName === 'send') {
|
||||
$result = $proceedingJoinPoint->process();
|
||||
/** @var Span $span */
|
||||
if ($span = CT::get('tracer.span.' . static::class)) {
|
||||
if ($this->spanTagManager->has('rpc', 'status')) {
|
||||
$span->setTag($this->spanTagManager->get('rpc', 'status'), isset($result['result']) ? 'OK' : 'Failed');
|
||||
try {
|
||||
$result = $proceedingJoinPoint->process();
|
||||
} finally {
|
||||
/** @var Span $span */
|
||||
if ($span = CT::get('tracer.span.' . static::class)) {
|
||||
if ($this->spanTagManager->has('rpc', 'status')) {
|
||||
$span->setTag($this->spanTagManager->get('rpc', 'status'), isset($result['result']) ? 'OK' : 'Failed');
|
||||
}
|
||||
$span->finish();
|
||||
}
|
||||
$span->finish();
|
||||
}
|
||||
|
||||
return $result;
|
||||
|
@ -58,8 +58,11 @@ class MethodAspect extends AbstractAspect
|
||||
|
||||
$key = $proceedingJoinPoint->className . '::' . $proceedingJoinPoint->methodName;
|
||||
$span = $this->startSpan($key);
|
||||
$result = $proceedingJoinPoint->process();
|
||||
$span->finish();
|
||||
try {
|
||||
$result = $proceedingJoinPoint->process();
|
||||
} finally {
|
||||
$span->finish();
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
@ -74,9 +74,12 @@ class RedisAspect implements AroundInterface
|
||||
$arguments = $proceedingJoinPoint->arguments['keys'];
|
||||
$span = $this->startSpan('Redis' . '::' . $arguments['name']);
|
||||
$span->setTag($this->spanTagManager->get('redis', 'arguments'), json_encode($arguments['arguments']));
|
||||
$result = $proceedingJoinPoint->process();
|
||||
$span->setTag($this->spanTagManager->get('redis', 'result'), json_encode($result));
|
||||
$span->finish();
|
||||
try {
|
||||
$result = $proceedingJoinPoint->process();
|
||||
$span->setTag($this->spanTagManager->get('redis', 'result'), json_encode($result));
|
||||
} finally {
|
||||
$span->finish();
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
@ -58,8 +58,11 @@ class TraceAnnotationAspect implements AroundInterface
|
||||
}
|
||||
$span = $this->startSpan($name);
|
||||
$span->setTag($tag, $source);
|
||||
$result = $proceedingJoinPoint->process();
|
||||
$span->finish();
|
||||
try {
|
||||
$result = $proceedingJoinPoint->process();
|
||||
} finally {
|
||||
$span->finish();
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
@ -36,7 +36,12 @@ trait SpanStarter
|
||||
/** @var ServerRequestInterface $request */
|
||||
$request = Context::get(ServerRequestInterface::class);
|
||||
if (! $request instanceof ServerRequestInterface) {
|
||||
throw new \RuntimeException('ServerRequest object missing.');
|
||||
// If the request object is absent, we are probably in a commandline context.
|
||||
// Throwing an exception is unnecessary.
|
||||
$root = $this->tracer->startSpan($name, $option);
|
||||
$root->setTag(SPAN_KIND, $kind);
|
||||
Context::set('tracer.root', $root);
|
||||
return $root;
|
||||
}
|
||||
$carrier = array_map(function ($header) {
|
||||
return $header[0];
|
||||
|
@ -13,7 +13,7 @@ namespace Hyperf\Utils\Codec;
|
||||
|
||||
use Hyperf\Utils\Contracts\Arrayable;
|
||||
use Hyperf\Utils\Contracts\Jsonable;
|
||||
use InvalidArgumentException;
|
||||
use Hyperf\Utils\Exception\InvalidArgumentException;
|
||||
|
||||
class Json
|
||||
{
|
||||
|
@ -13,6 +13,7 @@ namespace HyperfTest\Utils;
|
||||
|
||||
use HyperfTest\Utils\Exception\RetryException;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Swoole\Coroutine\Channel;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@ -116,4 +117,34 @@ class FunctionTest extends TestCase
|
||||
{
|
||||
$this->assertSame(SWOOLE_HOOK_ALL, swoole_hook_flags());
|
||||
}
|
||||
|
||||
public function testDefer()
|
||||
{
|
||||
$channel = new Channel(10);
|
||||
parallel([function () use ($channel) {
|
||||
defer(function () use ($channel) {
|
||||
$channel->push(0);
|
||||
});
|
||||
defer(function () use ($channel) {
|
||||
$channel->push(1);
|
||||
defer(function () use ($channel) {
|
||||
$channel->push(2);
|
||||
});
|
||||
defer(function () use ($channel) {
|
||||
$channel->push(3);
|
||||
});
|
||||
});
|
||||
defer(function () use ($channel) {
|
||||
$channel->push(4);
|
||||
});
|
||||
$channel->push(5);
|
||||
}]);
|
||||
|
||||
$this->assertSame(5, $channel->pop(0.001));
|
||||
$this->assertSame(4, $channel->pop(0.001));
|
||||
$this->assertSame(1, $channel->pop(0.001));
|
||||
$this->assertSame(3, $channel->pop(0.001));
|
||||
$this->assertSame(2, $channel->pop(0.001));
|
||||
$this->assertSame(0, $channel->pop(0.001));
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ declare(strict_types=1);
|
||||
namespace HyperfTest\Utils;
|
||||
|
||||
use Hyperf\Utils\Codec\Json;
|
||||
use Hyperf\Utils\Exception\InvalidArgumentException;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
@ -55,4 +56,10 @@ class JsonTest extends TestCase
|
||||
|
||||
$this->assertSame([1, 2, 3], $result);
|
||||
}
|
||||
|
||||
public function testJsonFailed()
|
||||
{
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
Json::decode('{"hype');
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ declare(strict_types=1);
|
||||
namespace HyperfTest\Utils;
|
||||
|
||||
use Hyperf\Utils\Codec\Xml;
|
||||
use Hyperf\Utils\Exception\InvalidArgumentException;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
@ -51,4 +52,10 @@ class XmlTest extends TestCase
|
||||
];
|
||||
$this->assertSame(Xml::toXml(Xml::toArray($xml), null, 'xml'), Xml::toXml($data, null, 'xml'));
|
||||
}
|
||||
|
||||
public function testXmlFailed()
|
||||
{
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
Xml::toArray('{"hype');
|
||||
}
|
||||
}
|
||||
|
@ -9,12 +9,12 @@ declare(strict_types=1);
|
||||
* @contact group@hyperf.io
|
||||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
|
||||
*/
|
||||
use Hyperf\View\Engine\BladeEngine;
|
||||
use Hyperf\View\Mode;
|
||||
use Hyperf\ViewEngine\HyperfViewEngine;
|
||||
|
||||
return [
|
||||
'engine' => BladeEngine::class,
|
||||
'mode' => Mode::TASK,
|
||||
'engine' => HyperfViewEngine::class,
|
||||
'mode' => Mode::SYNC,
|
||||
'config' => [
|
||||
'view_path' => BASE_PATH . '/storage/view/',
|
||||
'cache_path' => BASE_PATH . '/runtime/view/',
|
||||
|
Loading…
Reference in New Issue
Block a user