Merge branch 'master' into 3.0-merge

# Conflicts:
#	.github/workflows/test.yml
#	composer.json
#	phpunit.xml
#	src/config-apollo/src/Client.php
#	src/http-server/src/Request.php
#	src/redis/src/RedisConnection.php
#	src/utils/composer.json
This commit is contained in:
李铭昕 2022-02-21 10:03:25 +08:00
commit 4135ea0a0e
46 changed files with 3514 additions and 30 deletions

View File

@ -6,7 +6,7 @@ on:
schedule:
- cron: '0 2 * * *'
env:
SW_VERSION: 'v4.8.6'
SW_VERSION: 'v4.8.7'
jobs:
database:
name: Test for Database

View File

@ -14,7 +14,7 @@ jobs:
matrix:
os: [ ubuntu-latest ]
php-version: [ '8.0', '8.1' ]
sw-version: [ 'v4.5.11', 'v4.6.7', 'v4.7.1', 'v4.8.6', 'master' ]
sw-version: [ 'v4.5.11', 'v4.6.7', 'v4.7.1', 'v4.8.7', 'master' ]
exclude:
- php-version: '8.1'
sw-version: 'v4.5.11'

View File

@ -1,4 +1,20 @@
# v2.2.26 - TBD
# v2.2.27 - TBD
# v2.2.26 - 2022-02-21
## Fixed
- [#4536](https://github.com/hyperf/hyperf/pull/4536) Fixed bug that response header `content-type` will be set more than once sometimes when using json-rpc.
## Added
- [#4527](https://github.com/hyperf/hyperf/pull/4527) Added some useful methods for `Hyperf\Database\Schema\Blueprint`.
## Optimized
- [#4514](https://github.com/hyperf/hyperf/pull/4514) Improved some performance by using lowercase headers.
- [#4521](https://github.com/hyperf/hyperf/pull/4521) Try to connect to another one when connected redis sentinel failed.
- [#4529](https://github.com/hyperf/hyperf/pull/4529) Split `hyperf/context` from `hyperf/utils`.
# v2.2.25 - 2022-01-30

View File

@ -102,6 +102,7 @@
"hyperf/config-zookeeper": "*",
"hyperf/constants": "*",
"hyperf/consul": "*",
"hyperf/context": "*",
"hyperf/contract": "*",
"hyperf/coordinator": "*",
"hyperf/crontab": "*",
@ -204,6 +205,7 @@
"Hyperf\\Config\\": "src/config/src/",
"Hyperf\\Constants\\": "src/constants/src/",
"Hyperf\\Consul\\": "src/consul/src/",
"Hyperf\\Context\\": "src/context/src/",
"Hyperf\\Contract\\": "src/contract/src/",
"Hyperf\\Coordinator\\": "src/coordinator/src/",
"Hyperf\\Crontab\\": "src/crontab/src/",
@ -298,6 +300,7 @@
"HyperfTest\\Config\\": "src/config/tests/",
"HyperfTest\\Constants\\": "src/constants/tests/",
"HyperfTest\\Consul\\": "src/consul/tests/",
"HyperfTest\\Context\\": "src/context/tests/",
"HyperfTest\\Coordinator\\": "src/coordinator/tests/",
"HyperfTest\\Crontab\\": "src/crontab/tests/",
"HyperfTest\\DB\\": "src/db/tests/",

View File

@ -0,0 +1,68 @@
# 协程风格服务
Hyperf 默认使用的是 [Swoole 异步风格](https://wiki.swoole.com/#/server/init),此类型为多进程模型,自定义进程为单独进程运行。
> 此类型在使用 SWOOLE_BASE 且不使用自定义进程时,会以单进程模型来跑,具体可查看 Swoole 官方文档。
Hyperf 还提供了协程风格服务,此类型为单进程模型,所有的自定义进程会全部以协程模式来跑,不会创建单独的进程。
此两种风格,可以按需选择,**但不推荐将已经在正常使用的服务,进行无脑切换**。
## 配置
修改 `autoload/server.php` 配置文件,设置 `type``Hyperf\Server\CoroutineServer::class` 即可启动协程风格。
```php
<?php
declare(strict_types=1);
use Hyperf\Server\Event;
use Hyperf\Server\Server;
return [
'type' => Hyperf\Server\CoroutineServer::class,
'servers' => [
[
'name' => 'http',
'type' => Server::SERVER_HTTP,
'host' => '0.0.0.0',
'port' => 9501,
'sock_type' => SWOOLE_SOCK_TCP,
'callbacks' => [
Event::ON_REQUEST => [Hyperf\HttpServer\Server::class, 'onRequest'],
],
],
],
];
```
## WebSocket
1. 因为协程风格和异步风格,在对应的回调上存在差异,所以需要按需使用
例如 `onReceive` 回调,异步风格是 `Swoole\Server`,协程风格是 `Swoole\Coroutine\Server\Connection`
```php
<?php
declare(strict_types=1);
namespace Hyperf\Contract;
use Swoole\Coroutine\Server\Connection;
use Swoole\Server as SwooleServer;
interface OnReceiveInterface
{
/**
* @param Connection|SwooleServer $server
*/
public function onReceive($server, int $fd, int $reactorId, string $data): void;
}
```
2. 中间件所在协程只有在 `onClose` 时,才会结束
因为 `Hyperf` 的数据库实例,是在协程销毁时,返还给连接池,所以如果在 `WebSocket` 的中间件中使用 `Database` 就会导致连接池内的连接无法正常归还。

145
docs/en/dag.md Normal file

File diff suppressed because one or more lines are too long

310
docs/en/db/gen.md Normal file
View File

@ -0,0 +1,310 @@
# 模型创建脚本
Hyperf 提供了创建模型的命令,您可以很方便的根据数据表创建对应模型。命令通过 `AST` 生成模型,所以当您增加了某些方法后,也可以使用脚本方便的重置模型。
```bash
php bin/hyperf.php gen:model table_name
```
## 创建模型
可选参数如下:
| 参数 | 类型 | 默认值 | 备注 |
| :----------------: | :----: | :-------------------------------: | :-----------------------------------------------: |
| --pool | string | `default` | 连接池,脚本会根据当前连接池配置创建 |
| --path | string | `app/Model` | 模型路径 |
| --force-casts | bool | `false` | 是否强制重置 `casts` 参数 |
| --prefix | string | 空字符串 | 表前缀 |
| --inheritance | string | `Model` | 父类 |
| --uses | string | `Hyperf\DbConnection\Model\Model` | 配合 `inheritance` 使用 |
| --refresh-fillable | bool | `false` | 是否刷新 `fillable` 参数 |
| --table-mapping | array | `[]` | 为表名 -> 模型增加映射关系 比如 ['users:Account'] |
| --ignore-tables | array | `[]` | 不需要生成模型的表名 比如 ['users'] |
| --with-comments | bool | `false` | 是否增加字段注释 |
| --property-case | int | `0` | 字段类型 0 蛇形 1 驼峰 |
当使用 `--property-case` 将字段类型转化为驼峰时,还需要手动在模型中加入 `Hyperf\Database\Model\Concerns\CamelCase`
对应配置也可以配置到 `databases.{pool}.commands.gen:model` 中,如下
> 中划线都需要转化为下划线
```php
<?php
declare(strict_types=1);
use Hyperf\Database\Commands\ModelOption;
return [
'default' => [
// 忽略其他配置
'commands' => [
'gen:model' => [
'path' => 'app/Model',
'force_casts' => true,
'inheritance' => 'Model',
'uses' => '',
'refresh_fillable' => true,
'table_mapping' => [],
'with_comments' => true,
'property_case' => ModelOption::PROPERTY_SNAKE_CASE,
],
],
],
];
```
创建的模型如下
```php
<?php
declare(strict_types=1);
namespace App\Model;
use Hyperf\DbConnection\Model\Model;
/**
* @property $id
* @property $name
* @property $gender
* @property $created_at
* @property $updated_at
*/
class User extends Model
{
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'user';
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = ['id', 'name', 'gender', 'created_at', 'updated_at'];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = ['id' => 'integer', 'gender' => 'integer'];
}
```
## Visitors
框架提供了几个 `Visitors`,方便用户对脚本能力进行扩展。使用方法很简单,只需要在 `visitors` 配置中,添加对应的 `Visitor` 即可。
```php
<?php
declare(strict_types=1);
return [
'default' => [
// 忽略其他配置
'commands' => [
'gen:model' => [
'visitors' => [
Hyperf\Database\Commands\Ast\ModelRewriteKeyInfoVisitor::class
],
],
],
],
];
```
### 可选 Visitors
- Hyperf\Database\Commands\Ast\ModelRewriteKeyInfoVisitor
`Visitor` 可以根据数据库中主键,生成对应的 `$incrementing` `$primaryKey``$keyType`
- Hyperf\Database\Commands\Ast\ModelRewriteSoftDeletesVisitor
`Visitor` 可以根据 `DELETED_AT` 常量判断该模型是否含有软删除字段,如果存在,则添加对应的 Trait `SoftDeletes`
- Hyperf\Database\Commands\Ast\ModelRewriteTimestampsVisitor
`Visitor` 可以根据 `created_at``updated_at` 自动判断,是否启用默认记录 `创建和修改时间` 的功能。
- Hyperf\Database\Commands\Ast\ModelRewriteGetterSetterVisitor
`Visitor` 可以根据数据库字段生成对应的 `getter``setter`
## 覆盖 Visitor
Hyperf 框架中,当使用 `gen:model` 时,默认会将 `decimal` 转化成为 `float`。如下:
```php
<?php
declare(strict_types=1);
namespace App\Model;
/**
* @property int $id
* @property int $count
* @property float $float_num // decimal
* @property string $str
* @property string $json
* @property \Carbon\Carbon $created_at
* @property \Carbon\Carbon $updated_at
*/
class UserExt extends Model
{
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'user_ext';
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = ['id', 'count', 'float_num', 'str', 'json', 'created_at', 'updated_at'];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = ['id' => 'integer', 'count' => 'integer', 'float_num' => 'float', 'created_at' => 'datetime', 'updated_at' => 'datetime'];
}
```
这时候,我们就可以通过重写 `ModelUpdateVisitor`,修改这一特性。
```php
<?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 App\Kernel\Visitor;
use Hyperf\Database\Commands\Ast\ModelUpdateVisitor as Visitor;
use Hyperf\Utils\Str;
class ModelUpdateVisitor extends Visitor
{
protected function formatDatabaseType(string $type): ?string
{
switch ($type) {
case 'tinyint':
case 'smallint':
case 'mediumint':
case 'int':
case 'bigint':
return 'integer';
case 'decimal':
// 设置为 decimal并设置对应精度
return 'decimal:2';
case 'float':
case 'double':
case 'real':
return 'float';
case 'bool':
case 'boolean':
return 'boolean';
default:
return null;
}
}
protected function formatPropertyType(string $type, ?string $cast): ?string
{
if (! isset($cast)) {
$cast = $this->formatDatabaseType($type) ?? 'string';
}
switch ($cast) {
case 'integer':
return 'int';
case 'date':
case 'datetime':
return '\Carbon\Carbon';
case 'json':
return 'array';
}
if (Str::startsWith($cast, 'decimal')) {
// 如果 cast 为 decimal@property 改为 string
return 'string';
}
return $cast;
}
}
```
配置映射关系 `dependencies.php`
```php
<?php
return [
Hyperf\Database\Commands\Ast\ModelUpdateVisitor::class => App\Kernel\Visitor\ModelUpdateVisitor::class,
];
```
重新执行 `gen:model` 后,对应模型如下:
```php
<?php
declare (strict_types=1);
namespace App\Model;
/**
* @property int $id
* @property int $count
* @property string $float_num
* @property string $str
* @property string $json
* @property \Carbon\Carbon $created_at
* @property \Carbon\Carbon $updated_at
*/
class UserExt extends Model
{
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'user_ext';
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = ['id', 'count', 'float_num', 'str', 'json', 'created_at', 'updated_at'];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = ['id' => 'integer', 'count' => 'integer', 'float_num' => 'decimal:2', 'created_at' => 'datetime', 'updated_at' => 'datetime'];
}
```

944
docs/en/db/resource.md Normal file
View File

@ -0,0 +1,944 @@
# API 资源构造器
> 支持返回 Grpc 响应的资源扩展
## 简介
当构建 API 时,你往往需要一个转换层来联结你的 Model 模型和实际返回给用户的 JSON 响应。资源类能够让你以更直观简便的方式将模型和模型集合转化成 JSON。
## 安装
```
composer require hyperf/resource
```
## 生成资源
你可以使用 `gen:resource` 命令来生成一个资源类。默认情况下生成的资源都会被放置在应用程序的 `app/Resource` 文件夹下。资源继承自 `Hyperf\Resource\Json\JsonResource` 类:
```bash
php bin/hyperf.php gen:resource User
```
### 资源集合
除了生成资源转换单个模型外,你还可以生成资源集合用来转换模型的集合。这允许你在响应中包含与给定资源相关的链接与其他元信息。
你需要在生成资源时添加 `--collection` 标志以生成一个资源集合。或者,你也可以直接在资源的名称中包含 `Collection` 表示应该生成一个资源集合。资源集合继承自 `Hyperf\Resource\Json\ResourceCollection` 类:
```bash
php bin/hyperf.php gen:resource Users --collection
php bin/hyperf.php gen:resource UserCollection
```
## gRPC 资源
> 需要额外安装 `hyperf/resource-grpc`
```
composer require hyperf/resource-grpc
```
```bash
php bin/hyperf.php gen:resource User --grpc
```
gRPC 资源需要设置 `message` 类. 通过重写该资源类的 `expect()` 方法来实现.
gRPC 服务返回时, 必须调用 `toMessage()`. 该方法会返回一个实例化的 `message` 类.
```php
<?php
namespace HyperfTest\ResourceGrpc\Stubs\Resources;
use Hyperf\ResourceGrpc\GrpcResource;
use HyperfTest\ResourceGrpc\Stubs\Grpc\HiReply;
class HiReplyResource extends GrpcResource
{
public function toArray(): array
{
return [
'message' => $this->message,
'user' => HiUserResource::make($this->user),
];
}
public function expect(): string
{
return HiReply::class;
}
}
```
默认生成的资源集合, 可通过继承 `Hyperf\ResourceGrpc\GrpcResource` 接口来使其支持 gRPC 返回.
## 概念综述
> 这是对资源和资源集合的高度概述。强烈建议你阅读本文档的其他部分,以深入了解如何更好地自定义和使用资源。
在深入了解如何定制化编写你的资源之前,让我们先来看看在框架中如何使用资源。一个资源类表示一个单一模型需要被转换成 JSON 格式。例如,现在我们有一个简单的 `User` 资源类:
```php
<?php
namespace App\Resource;
use Hyperf\Resource\Json\JsonResource;
class User extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array
*/
public function toArray(): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
];
}
}
```
每一个资源类都定义了一个 `toArray` 方法,在发送响应时它会返回应该被转化成 JSON 的属性数组。注意在这里我们可以直接使用 `$this` 变量来访问模型属性。这是因为资源类将自动代理属性和方法到底层模型以方便访问。你可以在控制器中返回已定义的资源:
```php
<?php
namespace App\Controller;
use App\Resource\User as UserResource;
use App\Model\User;
class IndexController extends AbstractController
{
public function index()
{
return (new UserResource(User::first()))->toResponse();
}
}
```
### 资源集合
你可以在控制器中使用 `collection` 方法来创建资源实例,以返回多个资源的集合或分页响应:
```php
namespace App\Controller;
use App\Resource\User as UserResource;
use App\Model\User;
class IndexController extends AbstractController
{
public function index()
{
return UserResource::collection(User::all())->toResponse();
}
}
```
当然了,使用如上方法你将不能添加任何附加的元数据和集合一起返回。如果你需要自定义资源集合响应,你需要创建一个专用的资源来表示集合:
```bash
php bin/hyperf.php gen:resource UserCollection
```
你可以轻松的在已生成的资源集合类中定义任何你想在响应中返回的元数据:
```php
<?php
namespace App\Resource;
use Hyperf\Resource\Json\ResourceCollection;
class UserCollection extends ResourceCollection
{
/**
* Transform the resource collection into an array.
*
* @return array
*/
public function toArray() :array
{
return [
'data' => $this->collection,
'links' => [
'self' => 'link-value',
],
];
}
}
```
你可以在控制器中返回已定义的资源集合:
```php
<?php
namespace App\Controller;
use App\Model\User;
use App\Resource\UserCollection;
class IndexController extends AbstractController
{
public function index()
{
return (new UserCollection(User::all()))->toResponse();
}
}
```
### 保护集合的键
当从路由返回资源集合时,将重置集合的键,使它们以简单的数字顺序。但是,可以将 `preserveKeys` 属性添加到资源类中,指示是否应保留集合键:
```php
<?php
namespace App\Resource;
use Hyperf\Resource\Json\JsonResource;
class User extends JsonResource
{
/**
* 指示是否应保留资源的集合键。
*
* @var bool
*/
public $preserveKeys = true;
/**
* Transform the resource into an array.
*
* @return array
*/
public function toArray(): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
];
}
}
```
`preserveKeys` 属性被设置为 `true`,集合的键将会被保护:
```php
<?php
namespace App\Controller;
use App\Model\User;
use App\Resource\User as UserResource;
class IndexController extends AbstractController
{
public function index()
{
return UserResource::collection(User::all()->keyBy->id)->toResponse();
}
}
```
### 自定义基础资源类
通常,资源集合的 `$this->collection` 属性会自动填充,结果是将集合的每个项映射到其单个资源类。假定单一资源类是集合的类名,但结尾没有 `Collection` 字符串。
例如,`UserCollection` 将给定的用户实例映射到 `User` 资源中。若要自定义此行为,你可以重写资源集合的 `$collects` 属性:
```php
<?php
namespace App\Resource;
use Hyperf\Resource\Json\ResourceCollection;
class UserCollection extends ResourceCollection
{
/**
* collects 属性定义了资源类。
*
* @var string
*/
public $collects = 'App\Resource\Member';
/**
* Transform the resource collection into an array.
*
* @return array
*/
public function toArray(): array
{
return [
'data' => $this->collection,
'links' => [
'self' => 'link-value',
],
];
}
}
```
## 编写资源
> 如果你还没有阅读 [概念综述](#概念综述),那么在继续阅读本文档前,强烈建议你去阅读一下。
从本质上来说,资源的作用很简单。它们只需要将一个给定的模型转换成一个数组。所以每一个资源都包含一个 `toArray` 方法用来将你的模型属性转换成一个可以返回给用户的 API 友好数组:
```php
<?php
namespace App\Resource;
use Hyperf\Resource\Json\JsonResource;
class User extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array
*/
public function toArray(): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
];
}
}
```
你可以在控制器中返回已经定义的资源:
```php
<?php
namespace App\Controller;
use App\Model\User;
use App\Resource\User as UserResource;
class IndexController extends AbstractController
{
public function index()
{
return (new UserResource(User::find(1)))->toResponse();
}
}
```
### 关联
如果你希望在响应中包含关联资源,你只需要将它们添加到 `toArray` 方法返回的数组中。在下面这个例子里,我们将使用 `Post` 资源的 `collection` 方法将用户的文章添加到资源响应中:
```php
<?php
namespace App\Resource;
use Hyperf\Resource\Json\JsonResource;
class User extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array
*/
public function toArray(): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'posts' => PostResource::collection($this->posts),
];
}
}
```
> 如果你只想在关联已经加载时才添加关联资源,请查看相关文档。
### 资源集合
资源是将单个模型转换成数组,而资源集合是将多个模型的集合转换成数组。所有的资源都提供了一个 `collection` 方法来生成一个 「临时」 资源集合,所以你没有必要为每一个模型类型都编写一个资源集合类:
```php
<?php
namespace App\Controller;
use App\Resource\User as UserResource;
use App\Model\User;
class IndexController extends AbstractController
{
public function index()
{
return UserResource::collection(User::all())->toResponse();
}
}
```
要自定义返回集合的元数据,则仍需要定义一个资源集合:
```php
<?php
namespace App\Resource;
use Hyperf\Resource\Json\ResourceCollection;
class UserCollection extends ResourceCollection
{
/**
* Transform the resource collection into an array.
*
* @return array
*/
public function toArray(): array
{
return [
'data' => $this->collection,
'links' => [
'self' => 'link-value',
],
];
}
}
```
和单个资源一样,你可以在控制器中直接返回资源集合:
```php
<?php
namespace App\Controller;
use App\Model\User;
use App\Resource\UserCollection;
class IndexController extends AbstractController
{
public function index()
{
return (new UserCollection(User::all()))->toResponse();
}
}
```
### 数据包裹
默认情况下,当资源响应被转换成 JSON 时,顶层资源将会被包裹在 `data` 键中。因此一个典型的资源集合响应如下所示:
```json
{
"data": [
{
"id": 1,
"name": "Eladio Schroeder Sr.",
"email": "therese28@example.com"
},
{
"id": 2,
"name": "Liliana Mayert",
"email": "evandervort@example.com"
}
]
}
```
你可以使用资源基类的 `withoutWrapping` 方法来禁用顶层资源的包裹。
```php
<?php
namespace App\Controller;
use App\Model\User;
use App\Resource\UserCollection;
class IndexController extends AbstractController
{
public function index()
{
return (new UserCollection(User::all()))->withoutWrapping()->toResponse();
}
}
```
> withoutWrapping 方法只会禁用顶层资源的包裹,不会删除你手动添加到资源集合中的 data 键。而且只会在当前的资源或资源集合中生效,不影响全局。
#### 包裹嵌套资源
你可以完全自由地决定资源关联如何被包裹。如果你希望无论怎样嵌套,都将所有资源集合包裹在 `data` 键中,那么你需要为每个资源都定义一个资源集合类,并将返回的集合包裹在 `data` 键中。
当然,你可能会担心这样顶层资源将会被包裹在两个 `data `键中。请放心, 组件将永远不会让你的资源被双层包裹,因此你不必担心被转换的资源集合会被多重嵌套:
```php
<?php
namespace App\Resource;
use Hyperf\Resource\Json\ResourceCollection;
class UserCollection extends ResourceCollection
{
/**
* Transform the resource collection into an array.
*
* @return array
*/
public function toArray(): array
{
return [
'data' => $this->collection,
];
}
}
```
#### 分页
当在资源响应中返回分页集合时,即使你调用了 `withoutWrapping` 方法, 组件也会将你的资源数据包裹在 `data` 键中。这是因为分页响应中总会有 `meta``links` 键包含着分页状态信息:
```json
{
"data": [
{
"id": 1,
"name": "Eladio Schroeder Sr.",
"email": "therese28@example.com"
},
{
"id": 2,
"name": "Liliana Mayert",
"email": "evandervort@example.com"
}
],
"links":{
"first": "/pagination?page=1",
"last": "/pagination?page=1",
"prev": null,
"next": null
},
"meta":{
"current_page": 1,
"from": 1,
"last_page": 1,
"path": "/pagination",
"per_page": 15,
"to": 10,
"total": 10
}
}
```
你可以将分页实例传递给资源的 `collection` 方法或者自定义的资源集合:
```php
<?php
namespace App\Controller;
use App\Model\User;
use App\Resource\UserCollection;
class IndexController extends AbstractController
{
public function index()
{
return (new UserCollection(User::paginate()))->toResponse();
}
}
```
分页响应中总有 `meta``links` 键包含着分页状态信息:
```json
{
"data": [
{
"id": 1,
"name": "Eladio Schroeder Sr.",
"email": "therese28@example.com"
},
{
"id": 2,
"name": "Liliana Mayert",
"email": "evandervort@example.com"
}
],
"links":{
"first": "/pagination?page=1",
"last": "/pagination?page=1",
"prev": null,
"next": null
},
"meta":{
"current_page": 1,
"from": 1,
"last_page": 1,
"path": "/pagination",
"per_page": 15,
"to": 10,
"total": 10
}
}
```
### 条件属性
有些时候,你可能希望在给定条件满足时添加属性到资源响应里。例如,你可能希望如果当前用户是 「管理员」 时添加某个值到资源响应中。在这种情况下组件提供了一些辅助方法来帮助你解决问题。 `when` 方法可以被用来有条件地向资源响应添加属性:
```php
<?php
namespace App\Resource;
use Hyperf\Resource\Json\JsonResource;
class User extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array
*/
public function toArray(): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'secret' => $this->when(Auth::user()->isAdmin(), 'secret-value'),
];
}
}
```
在上面这个例子中,只有当 `isAdmin` 方法返回 `true` 时, `secret` 键才会最终在资源响应中被返回。如果该方法返回 `false` ,则 `secret` 键将会在资源响应被发送给客户端之前被删除。 `when` 方法可以使你避免使用条件语句拼接数组,转而用更优雅的方式来编写你的资源。
`when` 方法也接受闭包作为其第二个参数,只有在给定条件为 `true` 时,才从闭包中计算返回的值:
```php
<?php
namespace App\Resource;
use Hyperf\Resource\Json\JsonResource;
class User extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array
*/
public function toArray(): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'secret' => $this->when(Auth::user()->isAdmin(), function () {
return 'secret-value';
}),
];
}
}
```
#### 有条件的合并数据
有些时候,你可能希望在给定条件满足时添加多个属性到资源响应里。在这种情况下,你可以使用 `mergeWhen` 方法在给定的条件为 `true` 时将多个属性添加到响应中:
```php
<?php
namespace App\Resource;
use Hyperf\Resource\Json\JsonResource;
class User extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array
*/
public function toArray(): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
$this->mergeWhen(Auth::user()->isAdmin(), [
'first-secret' => 'value',
'second-secret' => 'value',
]),
];
}
}
```
同理,如果给定的条件为 `false` 时,则这些属性将会在资源响应被发送给客户端之前被移除。
> `mergeWhen` 方法不应该被使用在混合字符串和数字键的数组中。此外,它也不应该被使用在不按顺序排列的数字键的数组中。
### 条件关联
除了有条件地添加属性之外,你还可以根据模型关联是否已加载来有条件地在你的资源响应中包含关联。这允许你在控制器中决定加载哪些模型关联,这样你的资源可以在模型关联被加载后才添加它们。
这样做可以避免在你的资源中出现 「N+1」 查询问题。你应该使用 `whenLoaded` 方法来有条件的加载关联。为了避免加载不必要的关联,此方法接受关联的名称而不是关联本身作为其参数:
```php
<?php
namespace App\Resource;
use Hyperf\Resource\Json\JsonResource;
class User extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array
*/
public function toArray(): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'posts' => PostResource::collection($this->whenLoaded('posts')),
];
}
}
```
在上面这个例子中,如果关联没有被加载,则 `posts` 键将会在资源响应被发送给客户端之前被删除。
#### 条件中间表信息
除了在你的资源响应中有条件地包含关联外,你还可以使用 `whenPivotLoaded` 方法有条件地从多对多关联的中间表中添加数据。 `whenPivotLoaded` 方法接受的第一个参数为中间表的名称。第二个参数是一个闭包,它定义了在模型上如果中间表信息可用时要返回的值:
```php
<?php
namespace App\Resource;
use Hyperf\Resource\Json\JsonResource;
class User extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array
*/
public function toArray(): array
{
return [
'id' => $this->id,
'name' => $this->name,
'expires_at' => $this->whenPivotLoaded('role_user', function () {
return $this->pivot->expires_at;
}),
];
}
}
```
如果你的中间表使用的是 `pivot` 以外的访问器,你可以使用 `whenPivotLoadedAs`方法:
```php
<?php
namespace App\Resource;
use Hyperf\Resource\Json\JsonResource;
class User extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array
*/
public function toArray(): array
{
return [
'id' => $this->id,
'name' => $this->name,
'expires_at' => $this->whenPivotLoadedAs('subscription', 'role_user', function () {
return $this->subscription->expires_at;
}),
];
}
}
```
### 添加元数据
一些 JSON API 标准需要你在资源和资源集合响应中添加元数据。这通常包括资源或相关资源的 `links` ,或一些关于资源本身的元数据。如果你需要返回有关资源的其他元数据,只需要将它们包含在 `toArray` 方法中即可。例如在转换资源集合时你可能需要添加 `links` 信息:
```php
<?php
namespace App\Resource;
use Hyperf\Resource\Json\ResourceCollection;
class UserCollection extends ResourceCollection
{
/**
* Transform the resource collection into an array.
*
* @return array
*/
public function toArray(): array
{
return [
'data' => $this->collection,
'links' => [
'self' => 'link-value',
],
];
}
}
```
当添加额外的元数据到你的资源中时,你不必担心会覆盖在返回分页响应时自动添加的 `links``meta` 键。你添加的任何其他 `links` 会与分页响应添加的 `links` 相合并。
#### 顶层元数据
有时候你可能希望当资源被作为顶层资源返回时添加某些元数据到资源响应中。这通常包括整个响应的元信息。你可以在资源类中添加 `with` 方法来定义元数据。此方法应返回一个元数据数组,当资源被作为顶层资源渲染时,这个数组将会被包含在资源响应中:
```php
<?php
namespace App\Resource;
use Hyperf\Resource\Json\ResourceCollection;
class UserCollection extends ResourceCollection
{
/**
* Transform the resource collection into an array.
*
* @return array
*/
public function toArray(): array
{
return [
'data' => $this->collection,
'links' => [
'self' => 'link-value',
],
];
}
public function with() : array
{
return [
'meta' => [
'key' => 'value',
],
];
}
}
```
#### 构造资源时添加元数据
你还可以在控制器中构造资源实例时添加顶层数据。所有资源都可以使用 `additional` 方法来接受应该被添加到资源响应中的数据数组:
```php
<?php
namespace App\Controller;
use App\Model\User;
use App\Resource\UserCollection;
class IndexController extends AbstractController
{
public function index()
{
return (new UserCollection(User::all()->load('roles')))
->additional(['meta' => [
'key' => 'value',
]])->toResponse();
}
}
```
## 响应资源
就像你知道的那样,资源可以直接在控制器中被返回:
```php
<?php
namespace App\Controller;
use App\Model\User;
use App\Resource\User as UserResource;
class IndexController extends AbstractController
{
public function index()
{
return (new UserResource(User::find(1)))->toResponse();
}
public function info()
{
return new UserResource(User::find(1));
}
}
```
如你想设置响应头信息, 状态码等, 通过调用 `toResponse()` 方法获取到响应对象进行设置.

View File

@ -1,3 +1,31 @@
# ETCD
`hyperf/etcd` component will release in `hyperf 1.1`Please notice to [Project Version 1.1](https://github.com/hyperf/hyperf/projects/2) to know the specific progress.
## 安装
```
composer require hyperf/etcd
```
## 添加配置文件 `etcd.php`
```php
<?php
return [
'uri' => 'http://192.168.1.200:2379',
'version' => 'v3beta',
'options' => [
'timeout' => 10,
],
];
```
## 使用
```php
<?php
use Hyperf\Utils\ApplicationContext;
use Hyperf\Etcd\KVInterface;
$client = ApplicationContext::getContainer()->get(KVInterface::class);
```

61
docs/en/nacos.md Normal file
View File

@ -0,0 +1,61 @@
# Nacos
一个 `Nacos``PHP` 协程客户端,与 `Hyperf` 的配置中心、微服务治理完美结合。
## 安装
```shell
composer require hyperf/nacos
```
### 发布配置文件
```shell
php bin/hyperf.php vendor:publish hyperf/nacos
```
```php
<?php
declare(strict_types=1);
return [
// 无法使用 IP 端口形式的开发者,直接配置 url 即可
// 'url' => '',
'host' => '127.0.0.1',
'port' => 8848,
'username' => null,
'password' => null,
'guzzle' => [
'config' => null,
],
];
```
## 服务与实例
当前组件仍然保留了之前提供的服务注册功能。
只需要安装 `hyperf/service-governance-nacos` 组件,然后配置以下监听器和自定义进程即可。
`Hyperf\ServiceGovernanceNacos\Listener\MainWorkerStartListener`
`Hyperf\ServiceGovernanceNacos\Listener\OnShutdownListener`
`Hyperf\ServiceGovernanceNacos\Process\InstanceBeatProcess`
然后增加如下配置,以监听 `Shutdown` 事件
- config/autoload/server.php
```php
<?php
use Hyperf\Server\Event;
return [
// ...other
'callbacks' => [
// ...other
Event::ON_SHUTDOWN => [Hyperf\Framework\Bootstrap\ShutdownCallback::class, 'onShutdown']
]
];
```

101
docs/en/phar.md Normal file
View File

@ -0,0 +1,101 @@
# Phar 打包器
## 安装
```bash
composer require hyperf/phar
```
## 使用
- 默认打包
```shell
php bin/hyperf.php phar:build
```
- 指定包名
```shell
php bin/hyperf.php phar:build --name=your_project.phar
```
- 指定包版本
```shell
php bin/hyperf.php phar:build --phar-version=1.0.1
```
- 指定启动文件
```shell
php bin/hyperf.php phar:build --bin=bin/hyperf.php
```
- 指定打包目录
```shell
php bin/hyperf.php phar:build --path=BASE_PATH
```
- 映射外部文件
> 需要 hyperf/phar 版本 >= v2.1.7
下述命令,可以允许 `phar` 包读取同目录的 `.env` 文件,方便 `phar` 分发到各个环境当中
```shell
php bin/hyperf.php phar:build -M .env
```
## 运行
```shell
php your_project.phar start
```
## 注意事项
打包后是以 `phar` 包的形式运行,不同与源代码模式运行,`phar` 包中的 `runtime` 目录是不可写的,
所以我们需要重写部分可写的目录位置。
> 根据实际情况酌情修改
- pid_file
修改 `server.php` 配置。
```php
<?php
return [
'settings' => [
'pid_file' => '/tmp/runtime/hyperf.pid',
],
];
```
- logger
修改 `logger.php` 配置
```php
<?php
return [
'default' => [
'handler' => [
'class' => Monolog\Handler\StreamHandler::class,
'constructor' => [
'stream' => '/tmp/runtime/logs/hyperf.log',
'level' => Monolog\Logger::INFO,
],
],
],
];
```
- scan_cacheable
Phar 打包器会将 `config.php` 配置中的 `scan_cacheable` 主动设置为 `true`
当然,主动修改此配置为 `true`,也是可以的。

127
docs/en/rpc-multiplex.md Normal file
View File

@ -0,0 +1,127 @@
# 基于多路复用的 RPC 组件
本组件基于 `TCP` 协议,多路复用的设计借鉴于 `AMQP` 组件。
## 安装
```
composer require hyperf/rpc-multiplex
```
## Server 配置
修改 `config/autoload/server.php` 配置文件,以下配置删除了不相干的配置。
`settings` 设置中,分包规则不允许修改,只可以修改 `package_max_length`,此配置需要 `Server``Client` 保持一致。
```php
<?php
declare(strict_types=1);
use Hyperf\Server\Event;
use Hyperf\Server\Server;
return [
'servers' => [
[
'name' => 'rpc',
'type' => Server::SERVER_BASE,
'host' => '0.0.0.0',
'port' => 9502,
'sock_type' => SWOOLE_SOCK_TCP,
'callbacks' => [
Event::ON_RECEIVE => [Hyperf\RpcMultiplex\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,
],
],
],
];
```
创建 `RpcService`
```php
<?php
namespace App\RPC;
use App\JsonRpc\CalculatorServiceInterface;
use Hyperf\RpcMultiplex\Constant;
use Hyperf\RpcServer\Annotation\RpcService;
/**
* @RpcService(name="CalculatorService", server="rpc", protocol=Constant::PROTOCOL_DEFAULT)
*/
class CalculatorService implements CalculatorServiceInterface
{
}
```
## 客户端配置
修改 `config/autoload/services.php` 配置文件
```php
<?php
declare(strict_types=1);
return [
'consumers' => [
[
'name' => 'CalculatorService',
'service' => App\JsonRpc\CalculatorServiceInterface::class,
'id' => App\JsonRpc\CalculatorServiceInterface::class,
'protocol' => Hyperf\RpcMultiplex\Constant::PROTOCOL_DEFAULT,
'load_balancer' => 'random',
// 这个消费者要从哪个服务中心获取节点信息,如不配置则不会从服务中心获取节点信息
'registry' => [
'protocol' => 'consul',
'address' => 'http://127.0.0.1:8500',
],
'nodes' => [
['host' => '127.0.0.1', 'port' => 9502],
],
'options' => [
'connect_timeout' => 5.0,
'recv_timeout' => 5.0,
'settings' => [
// 包体最大值,若小于 Server 返回的数据大小,则会抛出异常,故尽量控制包体大小
'package_max_length' => 1024 * 1024 * 2,
],
// 重试次数,默认值为 2
'retry_count' => 2,
// 重试间隔,毫秒
'retry_interval' => 100,
// 多路复用客户端数量
'client_count' => 4,
// 心跳间隔 非 numeric 表示不开启心跳
'heartbeat' => 30,
],
],
],
];
```
### 注册中心
如果需要使用注册中心,则需要手动添加以下监听器
```php
<?php
return [
Hyperf\RpcMultiplex\Listener\RegisterServiceListener::class,
];
```

95
docs/en/rpn.md Normal file
View File

@ -0,0 +1,95 @@
# RPN - 逆波兰表示法
![PHPUnit](https://github.com/hyperf/rpn-incubator/workflows/PHPUnit/badge.svg)
`RPN` 是一种是由波兰数学家扬·武卡谢维奇 1920 年引入的数学表达式方式,在逆波兰记法中,所有操作符置于操作数的后面,因此也被称为后缀表示法。逆波兰记法不需要括号来标识操作符的优先级。
```
composer require hyperf/rpn
```
## RPN 逻辑
基本逻辑
- while 有输入
- 读入下一个符号 X
- IF X 是一个操作数
- 入栈
- ELSE IF X 是一个操作符
- 有一个先验的表格给出该操作符需要 n 个参数
- IF 堆栈中少于 n 个操作数
- (错误)用户没有输入足够的操作数
- Elsen 个操作数出栈
- 计算操作符。
- 将计算所得的值入栈
- IF 栈内只有一个值
- 这个值就是整个计算式的结果
- ELSE 多于一个值
- (错误)用户输入了多余的操作数
实例
中缀表达式 `5 + ((1 + 2) * 4) - 3` 写作
`5 1 2 + 4 * + 3 -`
下表给出了该逆波兰表达式从左至右求值的过程,堆栈栏给出了中间值,用于跟踪算法。
| 输入 | 操作 | 堆栈 | 注释 |
| ---- | -------- | ------- | -------------------------- |
| 5 | 入栈 | 5 | |
| 1 | 入栈 | 5, 1 | |
| 2 | 入栈 | 5, 1, 2 | |
| + | 加法运算 | 5, 3 | 1, 2 出栈,将结果 3 入栈 |
| 4 | 入栈 | 5, 3, 4 | |
| * | 乘法运算 | 5, 12 | 3, 4 出栈,将结果 12 入栈 |
| + | 加法运算 | 17 | 5, 12 出栈,将结果 17 入栈 |
| 3 | 入栈 | 17, 3 | |
| - | 减法运算 | 14 | 17, 3 出栈,将结果 14 入栈 |
计算完成时栈内只有一个操作数这就是表达式的结果14
## 使用
直接计算 RPN 表达式
```php
<?php
use Hyperf\Rpn\Calculator;
$calculator = new Calculator();
$calculator->calculate('5 1 2 + 4 * + 3 -', []); // '14'
```
设置计算精度
```php
<?php
use Hyperf\Rpn\Calculator;
$calculator = new Calculator();
$calculator->calculate('5 1 2 + 4 * + 3 -', [], 2); // '14.00'
```
设置变量
```php
<?php
use Hyperf\Rpn\Calculator;
$calculator = new Calculator();
$calculator->calculate('[0] 1 2 + 4 * + [1] -', [5, 10]); // '7'
```
### 中缀表达式转化为后缀表达式
> 暂时不支持使用变量
```php
<?php
use Hyperf\Rpn\Calculator;
$calculator = new Calculator();
$calculator->toRPNExpression('4 - 2 * ( 5 + 5 ) - 10'); // 4 2 5 5 + * - 10 -
```

322
docs/en/scout.md Normal file
View File

@ -0,0 +1,322 @@
# 模型全文检索
## 前言
> [hyperf/scout](https://github.com/hyperf/scout) 衍生于 [laravel/scout](https://github.com/laravel/scout),我们对它进行了一些协程化改造,但保持了相同的 API。在这里感谢一下 Laravel 开发组,实现了如此强大好用的组件。本文档部分节选自 Laravel China 社区组织翻译的 Laravel 官方文档。
Hyperf/Scout 为模型的全文搜索提供了一个简单的、基于驱动程序的解决方案。使用模型观察员Scout 会自动同步你的搜索索引和模型记录。
目前Scout 自带了一个 Elasticsearch 驱动;而编写自定义驱动程序很简单,你可以自由地使用自己的搜索实现来扩展 Scout。
## 安装
### 引入组件包和 Elasticsearch 驱动
```bash
composer require hyperf/scout
composer require hyperf/elasticsearch
```
Scout 安装完成后,使用 vendor:publish 命令来生成 Scout 配置文件。这个命令将在你的 config 目录下生成一个 scout.php 配置文件。
```bash
php bin/hyperf.php vendor:publish hyperf/scout
```
最后,在你要做搜索的模型中添加 Hyperf\Scout\Searchable trait。这个 trait 会注册一个模型观察者来保持模型和所有驱动的同步:
```php
<?php
namespace App;
use Hyperf\Database\Model\Model;
use Hyperf\Scout\Searchable;
class Post extends Model
{
use Searchable;
}
```
## 配置
### 配置文件
生成配置文件
```
php bin/hyperf.php vendor:publish hyperf/scout
```
配置文件
```php
<?php
declare(strict_types=1);
return [
'default' => env('SCOUT_ENGINE', 'elasticsearch'),
'chunk' => [
'searchable' => 500,
'unsearchable' => 500,
],
'prefix' => env('SCOUT_PREFIX', ''),
'soft_delete' => false,
'concurrency' => 100,
'engine' => [
'elasticsearch' => [
'driver' => Hyperf\Scout\Provider\ElasticsearchProvider::class,
// 如果 index 设置为 null则每个模型会对应一个索引反之每个模型对应一个类型
'index' => null,
'hosts' => [
env('ELASTICSEARCH_HOST', 'http://127.0.0.1:9200'),
],
],
],
];
```
### 配置模型索引
每个模型与给定的搜索「索引」同步,这个「索引」包含该模型的所有可搜索记录。换句话说,你可以把每一个「索引」设想为一张 MySQL 数据表。默认情况下,每个模型都会被持久化到与模型的「表」名(通常是模型名称的复数形式)相匹配的索引。你也可以通过覆盖模型上的 `searchableAs` 方法来自定义模型的索引:
<?php
namespace App;
use Hyperf\Scout\Searchable;
use Hyperf\Database\Model\Model;
class Post extends Model
{
use Searchable;
/**
* Get the index name for the model.
*
* @return string
*/
public function searchableAs()
{
return 'posts_index';
}
}
<a name="configuring-searchable-data"></a>
### 配置可搜索的数据
默认情况下,「索引」会从模型的 `toArray` 方法中读取数据来做持久化。如果要自定义同步到搜索索引的数据,可以覆盖模型上的 `toSearchableArray` 方法:
<?php
namespace App;
use Hyperf\Scout\Searchable;
use Hyperf\Database\Model\Model;
class Post extends Model
{
use Searchable;
/**
* Get the indexable data array for the model.
*
* @return array
*/
public function toSearchableArray()
{
$array = $this->toArray();
// Customize array...
return $array;
}
}
<a name="indexing"></a>
## 索引
<a name="batch-import"></a>
### 批量导入
如果你想要将 Scout 安装到现有的项目中,你可能已经有了想要导入搜索驱动的数据库记录。使用 Scout 提供的命令 `import` 把所有现有记录导入到搜索索引里:
php bin/hyperf.php scout:import "App\Post"
<a name="adding-records"></a>
### 添加记录
当你将 Trait `Hyperf\Scout\Searchable` 添加到模型中,你需要做的是 `save` 一个模型实例,它就会自动添加到你的搜索索引。更新索引操作将会在协程结束时进行,不会堵塞请求。
$order = new App\Order;
// ...
$order->save();
#### 批量添加
如果你想通过模型查询构造器将模型集合添加到搜索索引中,你也可以在模型查询构造器上链式调用 `searchable` 方法。`searchable` 会把构造器的查询结果分块并且将记录添加到你的搜索索引里。
// 使用模型查询构造器增加...
App\Order::where('price', '>', 100)->searchable();
// 使用模型关系增加记录...
$user->orders()->searchable();
// 使用集合增加记录...
$orders->searchable();
`searchable` 方法可以被看做是「更新插入」的操作。换句话说,如果模型记录已经在你的索引里了,它就会被更新。如果搜索索引中不存在,则将其添加到索引中。
<a name="updating-records"></a>
### 更新记录
要更新可搜索的模型,只需要更新模型实例的属性并将模型 `save` 到数据库。Scout 会自动将更新同步到你的搜索索引中:
$order = App\Order::find(1);
// 更新 order...
$order->save();
你也可以在模型查询语句上使用 `searchable` 方法来更新一个模型的集合。如果这个模型不存在你检索的索引里,就会被创建:
// 使用模型查询语句更新...
App\Order::where('price', '>', 100)->searchable();
// 你也可以使用模型关系更新...
$user->orders()->searchable();
// 你也可以使用集合更新...
$orders->searchable();
<a name="removing-records"></a>
### 删除记录
简单地使用 `delete` 从数据库中删除该模型就可以移除索引里的记录。这种删除形式甚至与软删除的模型兼容:
$order = App\Order::find(1);
$order->delete();
如果你不想在删除记录之前检索模型,可以在模型查询实例或集合上使用 `unsearchable` 方法:
// 通过模型查询删除...
App\Order::where('price', '>', 100)->unsearchable();
// 通过模型关系删除...
$user->orders()->unsearchable();
// 通过集合删除...
$orders->unsearchable();
<a name="pausing-indexing"></a>
### 暂停索引
你可能需要在执行一批模型操作的时候,不同步模型数据到搜索索引。此时你可以使用协程安全的 `withoutSyncingToSearch` 方法来执行此操作。这个方法接受一个立即执行的回调。该回调中所有的操作都不会同步到模型的索引:
App\Order::withoutSyncingToSearch(function () {
// 执行模型动作...
});
<a name="searching"></a>
## 搜索
你可以使用 `search` 方法来搜索模型。`search` 方法接受一个用于搜索模型的字符串。你还需在搜索查询上链式调用 `get` 方法,才能用给定的搜索语句查询与之匹配的模型模型:
$orders = App\Order::search('Star Trek')->get();
Scout 搜索返回模型模型的集合,因此你可以直接从路由或控制器返回结果,它们会被自动转换成 JSON 格式:
Route::get('/search', function () {
return App\Order::search([])->get();
});
如果你想在它们返回模型模型前得到原结果,你应该使用`raw` 方法:
$orders = App\Order::search('Star Trek')->raw();
搜索查询通常会在模型的 [`searchableAs`](#configuring-model-indexes) 方法指定的索引上执行。当然,你也可以使用 `within` 方法指定应该搜索的自定义索引:
$orders = App\Order::search('Star Trek')
->within('tv_shows_popularity_desc')
->get();
<a name="where-clauses"></a>
### Where 语句
Scout 允许你在搜索查询中增加简单的「where」语句。目前这些语句只支持基本的数值等式检查并且主要是用于根据拥有者的 ID 进行的范围搜索查询。由于搜索索引不是关系型数据库因此当前不支持更高级的「where」语句
$orders = App\Order::search('Star Trek')->where('user_id', 1)->get();
<a name="pagination"></a>
### 分页
除了检索模型的集合,你也可以使用 `paginate` 方法对搜索结果进行分页。这个方法会返回一个就像 [传统的模型查询分页](/zh-cn/db/paginator) 一样的 `Paginator` 实例:
$orders = App\Order::search('Star Trek')->paginate();
你可以通过将数量作为第一个参数传递给 `paginate` 方法来指定每页检索多少个模型:
$orders = App\Order::search('Star Trek')->paginate(15);
获取到检索结果后,就可以使用喜欢的模板引擎来渲染分页链接从而显示结果,就像传统的模型查询分页一样:
<div class="container">
@foreach ($orders as $order)
{{ $order->price }}
@endforeach
</div>
{{ $orders->links() }}
<a name="custom-engines"></a>
## 自定义引擎
#### 写引擎
如果内置的 Scout 搜索引擎不能满足你的需求,你可以写自定义的引擎并且将它注册到 Scout。你的引擎需要继承 `Hyperf\Scout\Engine\Engine` 抽象类,这个抽象类包含了你自定义的引擎必须要实现的五种方法:
use Hyperf\Scout\Builder;
abstract public function update($models);
abstract public function delete($models);
abstract public function search(Builder $builder);
abstract public function paginate(Builder $builder, $perPage, $page);
abstract public function map($results, $model);
`Hyperf\Scout\Engine\ElasticsearchEngine` 类里查看这些方法会对你有较大的帮助。这个类会为你在学习如何在自定义引擎中实现这些方法提供一个好的起点。
#### 注册引擎
一旦你写好了自定义引擎,您就可以在配置文件中指定引擎了。举个例子,如果你写好了一个 `MySqlSearchEngine`,您就可以在配置文件中这样写:
```php
<?php
return [
'default' => 'mysql',
'engine' => [
'mysql' => [
'driver' => MySqlSearchEngine::class,
],
'elasticsearch' => [
'driver' => \Hyperf\Scout\Provider\ElasticsearchProvider::class,
],
],
];
```
## 与 laravel/scout 不同之处
- Hyperf/Scout 是使用协程来高效同步搜索索引和模型记录的,无需依赖队列机制。
- Hyperf/Scout 默认提供的是开源的 Elasticsearch 引擎,而不是闭源的 Algolia。

View File

@ -42,6 +42,7 @@
* [Command](en/command.md)
* [Testing](en/testing.md)
* [View](en/view.md)
* [View Engine](en/view-engine.md)
* [Translation](en/translation.md)
* [Validaton](en/validation.md)
* [Session](en/session.md)
@ -52,6 +53,7 @@
* [Quick Start](en/db/quick-start.md)
* [Query Builder](en/db/querybuilder.md)
* [Model](en/db/model.md)
* [Generator](en/db/gen.md)
* [Relationship](en/db/relationship.md)
* [Paginator](en/db/paginator.md)
* [Model Event](en/db/event.md)
@ -59,19 +61,22 @@
* [Migration](en/db/migration.md)
* [Mutator](en/db/mutators.md)
* [Simple DB](en/db/db.md)
* [API Resource](en/db/resource.md)
* [Scout](en/scout.md)
* Microservice
* [Concept](en/microservice.md)
* [JSON RPC](en/json-rpc.md)
* [gRPC](en/grpc.md)
* [Multiplex RPC](en/rpc-multiplex.md)
* [Service Register](en/service-register.md)
* [Retry](en/retry.md)
* [Service Circuit Breaker](en/circuit-breaker.md)
* [Rate Limit](en/rate-limit.md)
* [Configuration Center](en/config-center.md)
* [Tracing](en/tracer.md)
* [Metric](en/metric.md)
* [Retry](en/retry.md)
* [Snowflake](en/snowflake.md)
* Server
@ -79,6 +84,7 @@
* [Tcp Server](en/tcp-server.md)
* [WebSocket Server](en/websocket-server.md)
* [Socket.io](en/socketio-server.md)
* [Coroutine Style Server](en/coroutine-server.md)
* Message Queue
@ -113,6 +119,9 @@
* [Watcher](en/watcher.md)
* [Dev Tool](en/devtool.md)
* [Swoole Tracker](en/swoole-tracker.md)
* [Phar](en/phar.md)
* [DAG](en/dag.md)
* [RPN](en/rpn.md)
* Deployment
@ -134,3 +143,5 @@
* [1.1 Upgrade Guide](en/upgrade/1.1.md)
* [2.0 Upgrade Guide](en/upgrade/2.0.md)
* [2.1 Upgrade Guide](en/upgrade/2.1.md)
* [2.2 Upgrade Guide](en/upgrade/2.2.md)

156
docs/en/upgrade/1.1.md Normal file
View File

@ -0,0 +1,156 @@
# 1.1 升级指南
1.1 版新增了很多的功能,但一些改动也涉及到了对 Skeleton 骨架的调整,以及配置项的结构调整,如果您已经投入了业务使用的项目且是基于官方提供的 Skeleton 项目创建的 1.0 应用项目,那么可以根据下面的内容点来调整您的骨架项目,如果您是一个新的项目,按照文档通过 `composer create-project hyperf/hyperf-skeleton` 命令创建新的项目即可使用新的 skeleton 结构。
## 升级 Swoole 到 4.4+
1.1 版将最低的 Swoole 版本要求从 4.3+ 提升到了 4.4+这两个版本之间有一些使用上的细节问题Hyperf 已经在较早的版本便已适配了,对于 Hyperf 的用户而言无需理会这之间的差异,我们提升最低 Swoole 版本要求主要是为了减少我们的历史负担,而 Swoole 4.4 作为 Swoole 的 LTS(长期支持版本) 也意味着更加的稳定可靠。
Hyperf 在启动时会进行 Swoole 版本检测,但为了更好的统一各处对 Swoole 版本的依赖约束,我们建议您将 `composer.json` 内对 Swoole 的依赖条件改为 `"ext-swoole": ">=4.4"`
## 增加 SWOOLE_HOOK_FLAGS 常量
在应用的入口文件 `bin/hyperf.php` 以及单测的入口文件 `test/bootstrap.php` 里增加一行常量定义如下:
```php
! defined('SWOOLE_HOOK_FLAGS') && define('SWOOLE_HOOK_FLAGS', SWOOLE_HOOK_ALL);
```
参考:[入口文件参考](https://github.com/hyperf/hyperf-skeleton/blob/70062b7bbf29e23cda2f30680e02aa3b26ebd6f7/bin/hyperf.php#L11) [单测入口文件参考](https://github.com/hyperf/hyperf-skeleton/blob/70062b7bbf29e23cda2f30680e02aa3b26ebd6f7/test/bootstrap.php#L20)
## 移动 config/dependencies.php 文件并调整文件结构
移动 `config/dependencies.php``config/autoload/dependencies.php`,并去除配置文件中的第一层 `dependencies`,如下:
1.0 的文件结构:
```php
<?php
// config/dependencies.php 文件
return [
'dependencies' => [
FooInterface::class => Foo::class
],
];
```
1.1 的文件结构:
```php
<?php
// config/autoload/dependencies.php 文件
return [
FooInterface::class => Foo::class
];
```
## 调整 config/container.php 文件的内容
由于 1.1 版本调整了 `dependencies.php` 文件的位置和结构,所处我们还需要调整一下 `config/container.php` 文件,以便依赖注入容器能够正确的运行,与此同时,我们也为 `config/container.php` 提供了更加简便的写法,`DefinitionSourceFactory` 将很多默认的行为聚合了起来,您只需将 `config/container.php` 文件的内容更换成下面的内容即可:
> 默认开启注解扫描缓存功能,可修改 `DefinitionSourceFactory` 入参的第一个参数来关闭此功能
```php
<?php
/**
* Initial a dependency injection container that implemented PSR-11 and return the container.
*/
declare(strict_types=1);
use Hyperf\Di\Container;
use Hyperf\Di\Definition\DefinitionSourceFactory;
use Hyperf\Utils\ApplicationContext;
use Psr\Container\ContainerInterface;
$container = new Container((new DefinitionSourceFactory(true))());
if (! $container instanceof ContainerInterface) {
throw new RuntimeException('The dependency injection container is invalid.');
}
return ApplicationContext::setContainer($container);
```
## 调整 WebSocket 控制器
由于 1.1 版本调整了 `onMessage``onOpen` 的入参约束,所以需要手动修改其为 `Swoole\WebSocket\Server`,具体代码如下
```php
<?php
declare(strict_types=1);
namespace App\Controller;
use Hyperf\Contract\OnMessageInterface;
use Hyperf\Contract\OnOpenInterface;
use Swoole\Http\Request;
use Swoole\Websocket\Frame;
use Swoole\WebSocket\Server as WebSocketServer;
class WebSocketController implements OnMessageInterface, OnOpenInterface
{
public function onMessage(WebSocketServer $server, Frame $frame): void
{
}
public function onOpen(WebSocketServer $server, Request $request): void
{
}
}
```
## 调整自定义组件的 ConfigProvider
1.0 版本中 `scan.path` 在 1.1 版本中调整为 `annotations.scan.path`,您需要修改所有自定义组件的 ConfigProvider 类来适配此变更,如您的自定义组件不涉及到注解扫描的功能配置,则可忽略此调整,如下所示:
1.0 的 ConfigProvider 文件结构:
```php
class ConfigProvider
{
public function __invoke(): array
{
return [
'scan' => [
'paths' => [
__DIR__,
],
],
];
}
}
```
1.1 的 ConfigProvider 文件结构:
```php
class ConfigProvider
{
public function __invoke(): array
{
return [
'annotations' => [
'scan' => [
'paths' => [
__DIR__,
],
],
],
];
}
}
```
## 调整默认的本地化语言
如果您在之前有使用 [hyperf/translation](https://github.com/hyperf/translation) 组件,那么您需要检查一下 `config/autoload/translation.php` 文件内的 `locale` 配置项,如为 `zh-CN`,则需要改为 `zh_CN`,在 1.1 版本,我们统一了这个配置的值。
## 调整 composer.json 的依赖
由于要升级到 1.1 版本的组件,而原来 skeleton 项目默认情况下是依赖 1.0.x 版本的组件的,所以我们需要对依赖的约束条件进行一些调整,将原来所有 Hyperf 组件的依赖 `~1.0.0` 修改为 `~1.1.0`,修改完后需运行 `composer update` 来将依赖项升级到 1.1 版本。
必须将所有 Hyperf 依赖都升级到 1.1 版本才可用,因为 1.1 调整了组件适配的 ConfigProvider 机制。
## 完成升级
至此1.1 升级即已完成,但由于 Hyperf 的各个底层文件都是可以通过 DI 来实现重写的,如您重写了某些本次升级调整到了的框架内部文件,您仍需再根据您的实际情况进行一定的调整。
如您在升级上或升级后遇到任何的问题,请前往 [Github Issue](https://github.com/hyperf/hyperf/issues) 提交您的 issue说明您遇到的问题我们会尽快帮助您解决。

258
docs/en/upgrade/2.0.md Normal file
View File

@ -0,0 +1,258 @@
# 2.0 升级指南
2.0 版本新增了不少强大的功能,如果您已经投入了业务使用的项目且是基于官方提供的 Skeleton 项目创建的 1.1 版本的应用项目,那么可以根据下面的内容点来调整您的 Skeleton 项目,如果您是一个新的项目,按照文档通过 `composer create-project hyperf/hyperf-skeleton` 命令创建新的项目即可使用新的 2.0 版本的 skeleton 代码,如果您当前使用的版本是低于 1.1 的版本,那么需要您先升级到 1.1 后再根据此升级指南升级到 2.0 版本。
## 升级 Swoole 到 4.5+
2.0 版本将最低的 Swoole 版本要求从 4.4+ 提升到了 4.5+这两个版本之间有一些使用上的细节差异Hyperf 在较早的版本便已适配了这里版本差异,您可无需理会这里的差异细节,提升 Swoole 版本到 4.5+ 主要是减少历史包袱对 Hyperf 造成的长期影响。您可通过执行 `php --ri swoole` 来查看当前环境中的 Swoole 版本,您可根据 [Swoole 文档](https://wiki.swoole.com/#/environment) 的指引来完成对 Swoole 的升级。
## 入口文件添加 ClassLoader 初始化
2.0 改变了 AOP 的底层逻辑,所以需要您在框架入口文件 `bin/hyperf.php` 中添加一行初始化的代码,您需要在入口匿名函数内的第一行添加代码 `Hyperf\Di\ClassLoader::init();`,如下所示:
```php
<?php
ini_set('display_errors', 'on');
ini_set('display_startup_errors', 'on');
error_reporting(E_ALL);
date_default_timezone_set('Asia/Shanghai');
! defined('BASE_PATH') && define('BASE_PATH', dirname(__DIR__, 1));
! defined('SWOOLE_HOOK_FLAGS') && define('SWOOLE_HOOK_FLAGS', SWOOLE_HOOK_ALL);
require BASE_PATH . '/vendor/autoload.php';
// Self-called anonymous function that creates its own scope and keep the global namespace clean.
(function () {
Hyperf\Di\ClassLoader::init();
/** @var \Psr\Container\ContainerInterface $container */
$container = require BASE_PATH . '/config/container.php';
$application = $container->get(\Hyperf\Contract\ApplicationInterface::class);
$application->run();
})();
```
与此同时PHPUnit 的入口文件也许做同样的处理,文件位于 `tests/bootstrap.php`,如下所示:
```php
<?php
declare(strict_types=1);
error_reporting(E_ALL);
date_default_timezone_set('Asia/Shanghai');
! defined('BASE_PATH') && define('BASE_PATH', dirname(__DIR__, 1));
! defined('SWOOLE_HOOK_FLAGS') && define('SWOOLE_HOOK_FLAGS', SWOOLE_HOOK_ALL);
Swoole\Runtime::enableCoroutine(true);
require BASE_PATH . '/vendor/autoload.php';
Hyperf\Di\ClassLoader::init();
$container = require BASE_PATH . '/config/container.php';
$container->get(Hyperf\Contract\ApplicationInterface::class);
```
## 调整 `composer.json`
因为 2.0 版本 AOP 底层逻辑的调整,故移除了 `init-proxy.sh` 脚本,所以需要您从 `composer.json` 中去掉 `scripts.post-autoload-dump` 内的 `"init-proxy.sh"` 执行语句,并修改 `post-autoload-dump` 内的命令为 `rm -rf runtime/container` 语句。
```json
{
"scripts": {
"post-root-package-install": [
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
],
"post-autoload-dump": [
"rm -rf runtime/container"
],
"analyse": "phpstan analyse --memory-limit 300M -l 0 -c phpstan.neon ./app ./config",
"cs-fix": "php-cs-fixer fix $1",
"start": "php ./bin/hyperf.php start",
"test": "co-phpunit -c phpunit.xml --colors=always"
}
}
```
## 调整 composer.json 的依赖版本
由于要升级到 2.0 版本的组件,而原来 skeleton 项目默认情况下是依赖 1.1.x 版本的组件的,所以我们需要对依赖的约束条件进行一些调整,将原来所有 Hyperf 组件的依赖 `~1.1.0` 修改为 `~2.0.0`,同时您还需要将 `phpstan/phpstan` 版本依赖修改为 `^0.12`,修改完后需运行 `composer update` 来将依赖项升级到 2.0 版本。
## 调整 Dockerfile
在 Docker 镜像的打包过程中,主动执行 `php bin/hyperf.php` 命令会帮助提前创建所有需要生成的代理类和注解扫描缓存,这样在生产环境运行启动时便无需再次的扫描,这样可以极大的优化生产环境启动的时间和内存使用量。以下示例不包含未修改的 Dockerfile 代码。
```dockerfile
ENV TIMEZONE=${timezone:-"Asia/Shanghai"} \
APP_ENV=prod \
SCAN_CACHEABLE=(true)
COPY . /opt/www
RUN composer install --no-dev -o && php bin/hyperf.php
EXPOSE 9501
ENTRYPOINT ["php", "/opt/www/bin/hyperf.php", "start"]
```
`Docker` 部署的用户,需要注意的是,在重新启动服务之前,最好先执行一次 `php bin/hyperf.php` 后再重新启动服务,以减少重新启动时的耗时。
## 调整 config/config.php 配置文件
您需要在 `config/config.php` 配置中添加 `app_env``scan_cacheable` 两个配置项,下面的代码示例不包含其它无关的配置内容,如下所示:
```php
<?php
return [
// 生产环境使用 prod 值
'app_env' => env('APP_ENV', 'dev'),
// 是否使用注解扫描缓存
'scan_cacheable' => env('SCAN_CACHEABLE', false),
];
```
`scan_cacheable` 配置用于控制应用启动时是否使用注解扫描缓存,以上 `Dockerfile``config/config.php` 中都有相关的修改。当这个配置的值为 `true` 时,项目启动时则会认为所有类都已经完成了扫描并正确生成了对应的缓存和代理,则会跳过扫描阶段以便优化启动时间和减少内存开销。
## 修改 `config/autoload/logger.php`
因为 2.0 版本提高了对 Monolog 依赖的版本,在高版本的 Monolog 中,默认的日志格式发生了变化,如果对于日志的格式有要求,比如需要根据日志格式与日志系统对接等,可修改 `config/autoload/logger.php` 配置文件的 `dateFormat` 配置项,以保持与之前版本的一致。
```php
<?php
declare(strict_types=1);
return [
'default' => [
'handler' => [
'class' => Monolog\Handler\StreamHandler::class,
'constructor' => [
'stream' => BASE_PATH . '/runtime/logs/hyperf.log',
'level' => Monolog\Logger::DEBUG,
],
],
'formatter' => [
'class' => Monolog\Formatter\LineFormatter::class,
'constructor' => [
'format' => null,
'dateFormat' => 'Y-m-d H:i:s',
'allowInlineLineBreaks' => true,
],
],
'processors' => [
],
],
];
```
## 修改 `config/autoload/exceptions.php`
2.0 版本对 路由找不到(404)、请求方法不匹配(405) 等 HTTP 路由异常行为的处理逻辑进行了调整,统一改为抛出 `Hyperf\HttpMessage\Exception\HttpException` 的子异常类,然后通过 ExceptionHandler 来统一管理这些异常并做对应的响应处理,这样用户也可通过抛出对应的异常以获得一致的响应返回体验,但鉴于 ExceptionHandler 是一个由用户管理的机制,而在 1.1 版本下默认的 Skeleton 配置了一个 `App\Exception\Handler\AppExceptionHandler` 类来对异常进行托底处理,并统一以 500 状态码返回给客户端,故您需要将 2.0 版本提供的用来处理 HttpException 的 `Hyperf\HttpServer\Exception\Handler\HttpExceptionHandler` 配置到 `config/autoload/exceptions.php` 配置文件中,并确保位于 `App\Exception\Handler\AppExceptionHandler` 配置的前面,以下配置示例省略了无关的配置,如下所示:
```php
<?php
return [
'handler' => [
'http' => [
Hyperf\HttpServer\Exception\Handler\HttpExceptionHandler::class,
],
],
];
```
当您完成了 ExceptionHandler 的配置后,可以通过直接访问一个不存在的路由,如果响应的状态码为 `404` 即可理解为完成了此项配置的修改。
## 修改 gRPC 客户端
为了避免和 gRPC 实际业务的命名冲突2.0 版本对 gRPC 客户端的基类函数命名进行了调整。
* `simpleRequest` -> `_simpleRequest`
* `clientStreamRequest` -> `_clientStreamRequest`
* `getGrpcClient` -> `_getGrpcClient`
除此之外,一些不应该暴露的方法变成了私有方法,如您的 gRPC 客户端涉及到以上方法的调用,请进行命名上的调整。
## 移除 DI 懒加载监听器
如果您的项目中有使用到 DI 组件的懒加载功能,此前您需要注册一个 `Hyperf\Di\Listener\LazyLoaderBootApplicationListener` 监听器,而在 2.0 版本,这一监听器被移除了,您可以直接使用该功能,故如果您此前有使用到该功能,您需要在 `config/autoload/listeners.php` 中移除该监听器的注册;
## 绑定 NormalizerInterface 关系
当您使用了 JSONRPC 功能并使用了 `symfony/serializer` 库来提供序列化功能时,由于 2.0 版本不再自动映射 `Hyperf\Contract\NormalizerInterface` 的实现类,所以您需要手动添加该映射关系,如下:
```php
use Hyperf\Utils\Serializer\SerializerFactory;
use Hyperf\Utils\Serializer\Serializer;
return [
Hyperf\Contract\NormalizerInterface::class => new SerializerFactory(Serializer::class),
];
```
## 调整 Hyperf\Contract\ProcessInterface 的实现类
`Hyperf\Contract\ProcessInterface` 中的 `isEnable` 方法增加了一个 `$server` 参数,即 `isEnable($server): bool`,所有 ProcessInterface 的实现类都需要您对该方法进行一些调整。
## 检查 config/autoload/aspects.php 文件
如果您此前对 Skeleton 进行过一些精简操作,需要检查 `config/autoload/aspects.php` 文件是否存在,如不存在需要增加一个文件并返回一个空数组.
> 这个问题在 2.0.1 会被修正,后续可无需做此检查。
## 检查自定义注解的收集器
如果您使用了自定义注解且使用了自定义收集器 `Collector` 来收集注解元数据,则需要将对应的 `Collector` 配置到 `annotations.scan.collectors` 中,因为开发模式下,会根据文件的修改时间判断文件是否修改,然后决定是否重新收集对应的注解元数据。所以,当没有配置 `annotations.scan.collectors` 时,就会导致注解只在首次启动 `server` 时可以生效。
如在应用层,该配置位于 `config/autoload/annotations.php` 文件,如下:
```php
<?php
return [
'scan' => [
'collectors' => [
CustomCollector::class,
],
],
];
```
如在组件,该配置则由 ConfigProvider 提供,如下:
```php
<?php
return [
'annotations' => [
'scan' => [
'collectors' => [
CustomCollector::class,
],
],
]
];
```
## 启动服务并测试访问接口
使用 Swoole 4.5 版本和 view 组件如果出现接口 404 的问题,可以尝试删除 `config/autoload/server.php` 文件中的 `static_handler_locations` 配置项。
此配置下的路径都会被认为是静态文件路由,所以如果配置了`/`,就会导致所有接口都会被认为是文件路径,导致接口 404。
## 完成升级
至此2.0 版本升级即已完成,但由于 Hyperf 的各个底层文件都是可以通过 DI 来实现重写的,如您重写了某些本次升级调整到了的框架内部文件,您仍需再根据您的实际情况进行一定的调整。
如您在升级上或升级后遇到任何的问题,请前往 [Github Issue](https://github.com/hyperf/hyperf/issues) 提交您的 issue说明您遇到的问题我们会尽快帮助您解决。

131
docs/en/upgrade/2.1.md Normal file
View File

@ -0,0 +1,131 @@
# 2.1 升级指南
- 2.1 版本主要增加了底层驱动 [hyperf/engine](https://github.com/hyperf/engine),允许 `Hyperf` 框架跑在 `Swoole``Swow` 之上。
- PHP 最低版本由 7.2 修改为 7.3
> Swow 暂为预览版本,请谨慎使用。
## 修改 Hyperf 组件版本
直接将 `composer.json` 中的 `hyperf/*` 统一修改为 `2.1.*` 即可。
```json
{
"require": {
"php": ">=7.3",
"ext-json": "*",
"ext-openssl": "*",
"ext-pdo": "*",
"ext-pdo_mysql": "*",
"ext-redis": "*",
"ext-swoole": ">=4.5",
"hyperf/async-queue": "2.1.*",
"hyperf/cache": "2.1.*",
"hyperf/command": "2.1.*",
"hyperf/config": "2.1.*",
"hyperf/constants": "2.1.*",
"hyperf/contract": "2.1.*",
"hyperf/database": "2.1.*",
"hyperf/db-connection": "2.1.*",
"hyperf/di": "2.1.*",
"hyperf/dispatcher": "2.1.*",
"hyperf/event": "2.1.*",
"hyperf/exception-handler": "2.1.*",
"hyperf/framework": "2.1.*",
"hyperf/guzzle": "2.1.*",
"hyperf/http-server": "2.1.*",
"hyperf/logger": "2.1.*",
"hyperf/model-cache": "2.1.*",
"hyperf/pool": "2.1.*",
"hyperf/process": "2.1.*",
"hyperf/redis": "2.1.*",
"hyperf/server": "2.1.*",
"hyperf/utils": "2.1.*"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.14",
"hyperf/devtool": "2.1.*",
"hyperf/testing": "2.1.*",
"mockery/mockery": "^1.0",
"phpstan/phpstan": "^0.12.18",
"swoole/ide-helper": "dev-master",
"symfony/var-dumper": "^5.1"
}
}
```
后面只需要执行 `composer update -o`,就可以正常完成升级了。
## 增加 optimize-autoloader 配置
因为 `Hyperf` 自从 `2.0` 开始,需要使用到 `composer` 生成的 `class_map`,这就要求用户每次更新依赖都要使用 `-o` 进行优化,但很多用户从来没有这个习惯。
于是我们建议在 `composer.json` 中增加对应配置,以满足这个需要。
```json
{
"config": {
"optimize-autoloader": true,
"sort-packages": true
}
}
```
## 修改 SwooleEvent
`Hyperf\Server\SwooleEvent``2.1` 中已经更名为 `Hyperf\Server\Event`,所以我们需要在 `server.php` 配置中修改对应代码。
> SwooleEvent 会在 3.0 中正式被移除,请尽快修改为 Event
```php
<?php
declare(strict_types=1);
use Hyperf\Server\Event;
use Hyperf\Server\Server;
return [
'mode' => SWOOLE_BASE,
'servers' => [
[
'name' => 'http',
'type' => Server::SERVER_HTTP,
'host' => '0.0.0.0',
'port' => 9501,
'sock_type' => SWOOLE_SOCK_TCP,
'callbacks' => [
Event::ON_REQUEST => [Hyperf\HttpServer\Server::class, 'onRequest'],
],
],
],
'callbacks' => [
Event::ON_BEFORE_START => [Hyperf\Framework\Bootstrap\ServerStartCallback::class, 'beforeStart'],
Event::ON_WORKER_START => [Hyperf\Framework\Bootstrap\WorkerStartCallback::class, 'onWorkerStart'],
Event::ON_PIPE_MESSAGE => [Hyperf\Framework\Bootstrap\PipeMessageCallback::class, 'onPipeMessage'],
Event::ON_WORKER_EXIT => [Hyperf\Framework\Bootstrap\WorkerExitCallback::class, 'onWorkerExit'],
],
];
```
## 分页器
因为组件 `hyperf/paginator` 已从 `hyperf/database` 依赖中移除。所以在 database 中使用到分页器的同学,还需要额外引入 `hyperf/paginator` 组件。
## 修改 DBAL 版本
倘若使用了 `doctrine/dbal` 组件,则需要升级到 `^3.0` 版本。
## 移除组件 doctrine/common
`doctrine/common` 组件与 `hyperf/utils` 存在依赖冲突。故需要从 `composer.json` 中移除此组件。
```bash
# 移除组件
composer remove doctrine/common
# 更新
composer update "hyperf/*" -o
```
## 注意事项
- 尽量不要将老项目的引擎修改为 Swow如果想要使用 Swow请尽量在新项目中尝试。因为 Swow 并不是 Swoole 的替代品,所以并不是所有 Swoole 的场景,都能在 Swow 中找到对应的替代方案。

212
docs/en/upgrade/2.2.md Normal file
View File

@ -0,0 +1,212 @@
# 2.2 升级指南
2.2 版本主要增加了 `PHP 8` 的适配,支持原生注解。
## 修改 Hyperf 组件版本
直接将 `composer.json` 中的 `hyperf/*` 统一修改为 `2.2.*` 即可。
> hyperf/engine 不跟随框架版本号,故不需要修改
另外,我们可以执行 `composer require "hyperf/ide-helper:2.2.*" --dev` 安装 `hyperf/ide-helper`,此组件可以帮助我们在使用原生注解时,提示注解可以设置的参数。
后面只需要执行 `composer update -o`,就可以正常完成升级了。
## 修改单测脚本
增加选项 `--prepend test/bootstrap.php`
```json
{
"scripts": {
"test": "co-phpunit --prepend test/bootstrap.php -c phpunit.xml --colors=always"
}
}
```
## 安装 pcntl 扩展
新版本的注解扫描使用了 `pcntl` 扩展,所以请先确保您的 `PHP` 安装了此扩展。
```shell
php --ri pcntl
pcntl
pcntl support => enabled
```
> 当开启 `grpc` 的时候,需要添加 `grpc.enable_fork_support= 1;``php.ini` 中,以支持开启子进程。
## AMQP
> 使用到 AMQP 的用户请注意,没有的可忽略此小节。
因为 `AMQP` 组件全线升级,支持多路复用,所以配置上也有一定更改。请按照以下最新的配置,酌情修改。
```php
<?php
return [
'default' => [
'host' => env('AMQP_HOST', 'localhost'),
'port' => (int) env('AMQP_PORT', 5672),
'user' => env('AMQP_USER', 'guest'),
'password' => env('AMQP_PASSWORD', 'guest'),
'vhost' => env('AMQP_VHOST', '/'),
'concurrent' => [
'limit' => 1,
],
'pool' => [
// 同时开启的连接数
// 因为新版本连接是支持多路复用的,所以可以用极少的连接数达到很高的并发
'connections' => 2,
],
'params' => [
'insist' => false,
'login_method' => 'AMQPLAIN',
'login_response' => null,
'locale' => 'en_US',
'connection_timeout' => 3,
'read_write_timeout' => 6,
'context' => null,
'keepalive' => true,
'heartbeat' => 3,
'channel_rpc_timeout' => 0.0,
'close_on_destruct' => false,
// 多路复用中闲置 Channel 的最大值,超过这个数量后,会关闭多余的限制 Channel
'max_idle_channels' => 10,
],
],
];
```
## 配置中心
> 使用到 配置中心 的用户请注意,没有的可忽略此小节。
配置中心在该版本进行了完全的重构,请务必仔细重新阅读对应的文档。
统一都需要引入 `hyperf/config-center` 组件,命令如下:
```shell
composer require "hyperf/config-center:~2.2.0"
```
并根据使用的驱动引入对应的驱动依赖组件,如使用到 `Apollo` 则需要引入 `hyperf/config-apollo` 组件,其余驱动类似。
同时配置中心相关的所有配置信息已全部集合到了 `config/autoload/config_center.php` 中,请根据新的配置结构进行对应的配置,没有该文件可以通过执行 `php bin/hyperf.php vendor:publish hyperf/config-center` 命令来创建。
## 服务中心
使用 `hyperf/service-gonvernace` 组件的用户,因 `consul` 适配器已经从此组件中剥离,新版本下需额外引入 `hyperf/service-governance-consul` 组件,命令如下:
```shell
composer require "hyperf/service-governance-consul:~2.2.0"
```
使用到 `nacos` 作为服务中心驱动的用户则需要引入 `hyperf/service-governance-nacos` 组件,命令如下:
```shell
composer require "hyperf/service-governance-nacos:~2.2.0"
```
## php-cs-fixer
如果不需要升级 `php-cs-fixer``3.0` 版本,则可以忽略此小节
1. 修改版本号
```
"friendsofphp/php-cs-fixer": "^3.0"
```
2. 重名命 .php_cs 文件
重名命为 `.php-cs-fixer.php` 并根据以下变更记录,修改对应代码
```diff
- return PhpCsFixer\Config::create()
+ return (new PhpCsFixer\Config())
- 'commentType' => 'PHPDoc',
+ 'comment_type' => 'PHPDoc',
```
## PHP 7.3 版本对 DI 的兼容性有所下降
> 使用高版本 PHP 的用户可以忽略此小节。
`2.0` - `2.1` 版本时,为了实现 `AOP` 作用于非 `DI` 管理的对象(如 `new` 关键词实例化的对象时),底层实现采用了 `BetterReflection` 组件来实现相关功能,带来新的编程体验的同时,也带来了一些很难攻克的问题,如下:
- 无扫描缓存时项目启动很慢
- 特殊场景下 `Inject``Value` 不生效
- `BetterReflection` 尚未支持 PHP 8 (截止 2.2 发版时)
在新的版本里,弃用了 `BetterReflection` 的应用,采用了 `子进程扫描` 的方式来解决以上这些痛点,但在低版本的 `PHP` 中也有一些不兼容的情况:
使用 `PHP 7.3` 启动应用后遇到类似如下错误:
```bash
PHP Fatal error: Interface 'Hyperf\Signal\SignalHandlerInterface' not found in vendor/hyperf/process/src/Handler/ProcessStopHandler.php on line 17
PHP Fatal error: Interface 'Symfony\Component\Serializer\SerializerInterface' not found in vendor/hyperf/utils/src/Serializer/Serializer.php on line 46
```
此问题是由于在 `PHP 7.3` 中通过 `子进程扫描` 的方式去获取反射,在某个类中实现了一个不存在的 `Interface` ,就会导致抛出 `Interface not found` 的异常,而高版本的 `PHP` 则不会。
解决方法为创建对应的 `Interface` 并正常引入。上文中的报错解决方法为安装对应所依赖的组件即可。
> 当然,最好还是可以升级到 7.4 或者 8.0 版本
```bash
composer require hyperf/signal
composer require symfony/serializer
```
## 文件系统
> 使用到 文件系统 的用户请注意,没有的可忽略此小节。
Hyperf 从 v2.2 版本开始,将同时支持使用 `League\Flysystem` 组件 `v1.0``v2.0` 版本。
如果您使用了 `League\Flysystem``v2.0` 版本,对应的适配器也需要按照下述列表进行调整,具体文档请参考 `文件系统` 章节。
- 阿里云 OSS 适配器
```shell
composer require hyperf/flysystem-oss
```
- S3 适配器
```shell
composer require "league/flysystem-aws-s3-v3:^2.0"
composer require hyperf/guzzle
```
- 七牛适配器
```shell
composer require "overtrue/flysystem-qiniu:^2.0"
```
- 内存适配器
```shell
composer require "league/flysystem-memory:^2.0"
```
- 腾讯云 COS 适配器
> 本适配器基础配置与 `overtrue/flysystem-cos` 组件 `v2.0` 版本略有不同,请根据最新版本配置进行适当修改。
```shell
composer require "overtrue/flysystem-cos:^4.0"
```
## 其他可能导致 BC 的修改
[CHANGED](https://github.com/hyperf/hyperf/blob/2.2/CHANGELOG-2.2.md#changed)

View File

@ -1,5 +1,21 @@
# 版本更新记录
# v2.2.26 - 2022-02-21
## 修复
- [#4536](https://github.com/hyperf/hyperf/pull/4536) 修复使用 `JsonRPC` 时,会设置多次 `content-type` 的问题。
## 新增
- [#4527](https://github.com/hyperf/hyperf/pull/4527) 为 `Hyperf\Database\Schema\Blueprint` 增加了一些比较有用的方法。
## 优化
- [#4514](https://github.com/hyperf/hyperf/pull/4514) 通过使用小写 `key` 获取 `HTTP``Header` 信息,提升一部分性能。
- [#4521](https://github.com/hyperf/hyperf/pull/4521) 在使用 Redis 的哨兵模式时,如果第一个哨兵节点连接失败,则尝试连接其余哨兵节点。
- [#4529](https://github.com/hyperf/hyperf/pull/4529) 将组件 `hyperf/context` 从主键 `hyperf/utils` 中分离出来。
# v2.2.25 - 2022-01-30
## 修复

View File

@ -0,0 +1,5 @@
# 企业服务
我们提供了付费的企业技术支持服务,以保障您在商业环境中更顺畅更轻松的使用 Hyperf 应用于您的业务上,有相关的技术支持服务需求,请添加下面的微信联系我们。
![wechat](imgs/wechat.jpg ':size=375')

View File

@ -4,6 +4,7 @@
* [线上交流](zh-cn/communication.md)
* [捐献及赞助](zh-cn/donate.md)
* [参与开发](zh-cn/contribute.md)
* [企业服务](zh-cn/enterprise-service.md)
* 版本管理

View File

@ -1,5 +1,21 @@
# 版本更新記錄
# v2.2.26 - 2022-02-21
## 修復
- [#4536](https://github.com/hyperf/hyperf/pull/4536) 修復使用 `JsonRPC` 時,會設置多次 `content-type` 的問題。
## 新增
- [#4527](https://github.com/hyperf/hyperf/pull/4527) 為 `Hyperf\Database\Schema\Blueprint` 增加了一些比較有用的方法。
## 優化
- [#4514](https://github.com/hyperf/hyperf/pull/4514) 通過使用小寫 `key` 獲取 `HTTP``Header` 信息,提升一部分性能。
- [#4521](https://github.com/hyperf/hyperf/pull/4521) 在使用 Redis 的哨兵模式時,如果第一個哨兵節點連接失敗,則嘗試連接其餘哨兵節點。
- [#4529](https://github.com/hyperf/hyperf/pull/4529) 將組件 `hyperf/context` 從主鍵 `hyperf/utils` 中分離出來。
# v2.2.25 - 2022-01-30
## 修復

View File

@ -0,0 +1,5 @@
# 企業服務
我們提供了付費的企業技術支持服務,以保障您在商業環境中更順暢更輕鬆的使用 Hyperf 應用於您的業務上,有相關的技術支持服務需求,請添加下面的微信聯繫我們。
![wechat](imgs/wechat.jpg ':size=375')

View File

@ -4,6 +4,7 @@
* [線上交流](zh-hk/communication.md)
* [捐獻及贊助](zh-hk/donate.md)
* [參與開發](zh-hk/contribute.md)
* [企業服務](zh-hk/enterprise-service.md)
* 版本管理

View File

@ -1,5 +1,21 @@
# 版本更新記錄
# v2.2.26 - 2022-02-21
## 修復
- [#4536](https://github.com/hyperf/hyperf/pull/4536) 修復使用 `JsonRPC` 時,會設定多次 `content-type` 的問題。
## 新增
- [#4527](https://github.com/hyperf/hyperf/pull/4527) 為 `Hyperf\Database\Schema\Blueprint` 增加了一些比較有用的方法。
## 優化
- [#4514](https://github.com/hyperf/hyperf/pull/4514) 通過使用小寫 `key` 獲取 `HTTP``Header` 資訊,提升一部分效能。
- [#4521](https://github.com/hyperf/hyperf/pull/4521) 在使用 Redis 的哨兵模式時,如果第一個哨兵節點連線失敗,則嘗試連線其餘哨兵節點。
- [#4529](https://github.com/hyperf/hyperf/pull/4529) 將元件 `hyperf/context` 從主鍵 `hyperf/utils` 中分離出來。
# v2.2.25 - 2022-01-30
## 修復

View File

@ -0,0 +1,5 @@
# 企業服務
我們提供了付費的企業技術支援服務,以保障您在商業環境中更順暢更輕鬆的使用 Hyperf 應用於您的業務上,有相關的技術支援服務需求,請新增下面的微信聯絡我們。
![wechat](imgs/wechat.jpg ':size=375')

View File

@ -4,6 +4,7 @@
* [線上交流](zh-tw/communication.md)
* [捐獻及贊助](zh-tw/donate.md)
* [參與開發](zh-tw/contribute.md)
* [企業服務](zh-tw/enterprise-service.md)
* 版本管理

View File

@ -20,6 +20,7 @@
<directory suffix=".php">./src/config-nacos/src</directory>
<directory suffix=".php">./src/constants/src</directory>
<directory suffix=".php">./src/consul/src</directory>
<directory suffix=".php">./src/context/src</directory>
<directory suffix=".php">./src/coordinator/src</directory>
<directory suffix=".php">./src/crontab/src</directory>
<directory suffix=".php">./src/dag/src</directory>
@ -75,6 +76,7 @@
<directory suffix="Test.php">./src/config-zookeeper/tests</directory>
<directory suffix="Test.php">./src/constants/tests</directory>
<directory suffix="Test.php">./src/consul/tests</directory>
<directory suffix="Test.php">./src/context/tests</directory>
<directory suffix="Test.php">./src/coordinator/tests</directory>
<directory suffix="Test.php">./src/crontab/tests</directory>
<directory suffix="Test.php">./src/dag/tests</directory>

View File

@ -68,7 +68,7 @@ class Client implements ClientInterface
'query' => $query,
'headers' => $headers,
]);
if ($response->getStatusCode() === 200 && str_contains($response->getHeaderLine('Content-Type'), 'application/json')) {
if ($response->getStatusCode() === 200 && str_contains($response->getHeaderLine('content-type'), 'application/json')) {
$body = json_decode((string) $response->getBody(), true);
$result = $body['configurations'] ?? [];
$this->cache[$cacheKey] = [

View File

@ -34,7 +34,7 @@ class ConsulResponse
public function json(string $key = null, $default = null)
{
if ($this->response->getHeaderLine('Content-Type') !== 'application/json') {
if ($this->response->getHeaderLine('content-type') !== 'application/json') {
throw new ServerException('The Content-Type of response is not equal application/json');
}
$data = json_decode((string) $this->response->getBody(), true);

2
src/context/.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
/tests export-ignore
/.github export-ignore

View 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

21
src/context/LICENSE Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
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.

40
src/context/composer.json Normal file
View File

@ -0,0 +1,40 @@
{
"name": "hyperf/context",
"description": "A coroutine context library.",
"license": "MIT",
"keywords": [
"php",
"swoole",
"hyperf",
"context"
],
"homepage": "https://hyperf.io",
"support": {
"docs": "https://hyperf.wiki",
"issues": "https://github.com/hyperf/hyperf/issues",
"pull-request": "https://github.com/hyperf/hyperf/pulls",
"source": "https://github.com/hyperf/hyperf"
},
"require": {
"php": ">=7.2",
"hyperf/engine": "^1.1"
},
"autoload": {
"psr-4": {
"Hyperf\\Context\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"HyperfTest\\Context\\": "tests/"
}
},
"config": {
"sort-packages": true
},
"extra": {
"branch-alias": {
"dev-master": "2.2-dev"
}
}
}

111
src/context/src/Context.php Normal file
View File

@ -0,0 +1,111 @@
<?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\Context;
use Hyperf\Engine\Coroutine;
class Context
{
protected static array $nonCoContext = [];
public static function set(string $id, mixed $value): mixed
{
if (Coroutine::id() > 0) {
Coroutine::getContextFor()[$id] = $value;
} else {
static::$nonCoContext[$id] = $value;
}
return $value;
}
public static function get(string $id, mixed $default = null, ?int $coroutineId = null): mixed
{
if (Coroutine::id() > 0) {
return Coroutine::getContextFor($coroutineId)[$id] ?? $default;
}
return static::$nonCoContext[$id] ?? $default;
}
public static function has(string $id, ?int $coroutineId = null): bool
{
if (Coroutine::id() > 0) {
return isset(Coroutine::getContextFor($coroutineId)[$id]);
}
return isset(static::$nonCoContext[$id]);
}
/**
* Release the context when you are not in coroutine environment.
*/
public static function destroy(string $id): void
{
unset(static::$nonCoContext[$id]);
}
/**
* Copy the context from a coroutine to current coroutine.
* This method will delete the origin values in current coroutine.
*/
public static function copy(int $fromCoroutineId, array $keys = []): void
{
$from = Coroutine::getContextFor($fromCoroutineId);
if ($from === null) {
return;
}
$current = Coroutine::getContextFor();
if ($keys) {
$map = array_intersect_key($from->getArrayCopy(), array_flip($keys));
} else {
$map = $from->getArrayCopy();
}
$current->exchangeArray($map);
}
/**
* Retrieve the value and override it by closure.
*/
public static function override(string $id, \Closure $closure): mixed
{
$value = null;
if (self::has($id)) {
$value = self::get($id);
}
$value = $closure($value);
self::set($id, $value);
return $value;
}
/**
* Retrieve the value and store it if not exists.
*/
public static function getOrSet(string $id, mixed $value): mixed
{
if (! self::has($id)) {
return self::set($id, value($value));
}
return self::get($id);
}
public static function getContainer()
{
if (Coroutine::id() > 0) {
return Coroutine::getContextFor();
}
return static::$nonCoContext;
}
}

View File

@ -0,0 +1,111 @@
<?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\Context;
use Hyperf\Context\Context;
use Hyperf\Utils\Coroutine;
use PHPUnit\Framework\TestCase;
/**
* @internal
* @coversNothing
*/
class ContextTest extends TestCase
{
public function testOverride()
{
Context::set('override.id', 1);
$this->assertSame(2, Context::override('override.id', function ($id) {
return $id + 1;
}));
$this->assertSame(2, Context::get('override.id'));
}
public function testGetOrSet()
{
Context::set('test.store.id', null);
$this->assertSame(1, Context::getOrSet('test.store.id', function () {
return 1;
}));
$this->assertSame(1, Context::getOrSet('test.store.id', function () {
return 2;
}));
Context::set('test.store.id', null);
$this->assertSame(1, Context::getOrSet('test.store.id', 1));
}
public function testCopy()
{
Context::set('test.store.id', $uid = uniqid());
$id = Coroutine::id();
parallel([function () use ($id, $uid) {
Context::copy($id, ['test.store.id']);
$this->assertSame($uid, Context::get('test.store.id'));
}]);
}
public function testCopyAfterSet()
{
Context::set('test.store.id', $uid = uniqid());
$id = Coroutine::id();
parallel([function () use ($id, $uid) {
Context::set('test.store.name', 'Hyperf');
Context::copy($id, ['test.store.id']);
$this->assertSame($uid, Context::get('test.store.id'));
// TODO: Context::copy will delete origin values.
$this->assertNull(Context::get('test.store.name'));
}]);
}
public function testContextChangeAfterCopy()
{
$obj = new \stdClass();
$obj->id = $uid = uniqid();
Context::set('test.store.id', $obj);
Context::set('test.store.useless.id', 1);
$id = Coroutine::id();
$tid = uniqid();
parallel([function () use ($id, $uid, $tid) {
Context::copy($id, ['test.store.id']);
$obj = Context::get('test.store.id');
$this->assertSame($uid, $obj->id);
$obj->id = $tid;
$this->assertFalse(Context::has('test.store.useless.id'));
}]);
$this->assertSame($tid, Context::get('test.store.id')->id);
}
public function testContextFromNull()
{
$res = Context::get('id', $default = 'Hello World!', -1);
$this->assertSame($default, $res);
$res = Context::get('id', null, -1);
$this->assertSame(null, $res);
$this->assertFalse(Context::has('id', -1));
Context::copy(-1);
parallel([function () {
Context::set('id', $id = uniqid());
Context::copy(-1, ['id']);
$this->assertSame($id, Context::get('id'));
}]);
}
}

View File

@ -18,7 +18,7 @@ use Hyperf\Database\Commands\Ast\ModelRewriteConnectionVisitor;
use Hyperf\Database\Commands\Ast\ModelUpdateVisitor;
use Hyperf\Database\ConnectionResolverInterface;
use Hyperf\Database\Model\Model;
use Hyperf\Database\Schema\MySqlBuilder;
use Hyperf\Database\Schema\Builder;
use Hyperf\Utils\CodeGen\Project;
use Hyperf\Utils\Str;
use PhpParser\NodeTraverser;
@ -104,7 +104,7 @@ class ModelCommand extends Command
$this->addOption('property-case', null, InputOption::VALUE_OPTIONAL, 'Which property case you want use, 0: snake case, 1: camel case.');
}
protected function getSchemaBuilder(string $poolName): MySqlBuilder
protected function getSchemaBuilder(string $poolName): Builder
{
$connection = $this->resolver->connection($poolName);
return $connection->getSchemaBuilder();

View File

@ -14,6 +14,7 @@ namespace Hyperf\Database\Schema;
use BadMethodCallException;
use Closure;
use Hyperf\Database\Connection;
use Hyperf\Database\Query\Expression;
use Hyperf\Database\Schema\Grammars\Grammar;
use Hyperf\Database\SQLiteConnection;
use Hyperf\Macroable\Macroable;
@ -47,6 +48,13 @@ class Blueprint
*/
public $temporary = false;
/**
* The column to add new columns after.
*
* @var string
*/
public $after;
/**
* The comment of the table.
*
@ -1212,6 +1220,71 @@ class Blueprint
});
}
/**
* Determine if the blueprint has auto-increment columns.
*/
public function hasAutoIncrementColumn(): bool
{
return ! is_null(collect($this->getAddedColumns())->first(function ($column) {
return $column->autoIncrement === true;
}));
}
/**
* Get the auto-increment column starting values.
*/
public function autoIncrementingStartingValues(): array
{
if (! $this->hasAutoIncrementColumn()) {
return [];
}
return collect($this->getAddedColumns())->mapWithKeys(function ($column) {
return $column->autoIncrement === true
? [$column->name => $column->get('startingValue', $column->get('from'))]
: [$column->name => null];
})->filter()->all();
}
/**
* Specify an fulltext for the table.
*
* @param array|string $columns
* @param null|string $name
* @param null|string $algorithm
* @return \Hyperf\Utils\Fluent
*/
public function fulltext($columns, $name = null, $algorithm = null)
{
return $this->indexCommand('fulltext', $columns, $name, $algorithm);
}
/**
* Specify a raw index for the table.
*
* @param string $expression
* @param string $name
* @return \Hyperf\Utils\Fluent
*/
public function rawIndex($expression, $name)
{
return $this->index([new Expression($expression)], $name);
}
/**
* Create a new UUID column on the table with a foreign key constraint.
*
* @param string $column
* @return \Hyperf\Database\Schema\ForeignIdColumnDefinition
*/
public function foreignUuid($column)
{
return $this->addColumnDefinition(new ForeignIdColumnDefinition($this, [
'type' => 'uuid',
'name' => $column,
]));
}
/**
* Ensure the commands on the blueprint are valid for the connection type.
*
@ -1386,4 +1459,23 @@ class Blueprint
{
return new Fluent(array_merge(compact('name'), $parameters));
}
/**
* Add a new column definition to the blueprint.
*
* @param \Hyperf\Database\Schema\ColumnDefinition $definition
* @return \Hyperf\Database\Schema\ColumnDefinition
*/
protected function addColumnDefinition($definition)
{
$this->columns[] = $definition;
if ($this->after) {
$definition->after($this->after);
$this->after = $definition->name;
}
return $definition;
}
}

View File

@ -85,8 +85,7 @@ class Request implements RequestInterface
}
/**
* Retrieve the input data from request, include query parameters, parsed body and json body,
* if $key is null, will return all the parameters.
* Retrieve the input data from request, include query parameters, parsed body and json body.
*/
public function input(string $key, mixed $default = null): mixed
{

View File

@ -41,14 +41,14 @@ class ResponseBuilder
public function buildErrorResponse(ServerRequestInterface $request, int $code, \Throwable $error = null): ResponseInterface
{
$body = new SwooleStream($this->formatErrorResponse($request, $code, $error));
return $this->response()->withAddedHeader('content-type', 'application/json')->withBody($body);
return $this->response()->withHeader('content-type', 'application/json')->withBody($body);
}
public function buildResponse(ServerRequestInterface $request, $response): ResponseInterface
{
$body = new SwooleStream($this->formatResponse($response, $request));
return $this->response()
->withAddedHeader('content-type', 'application/json')
->withHeader('content-type', 'application/json')
->withBody($body);
}

View File

@ -16,6 +16,7 @@ use Hyperf\Contract\PoolInterface;
use Hyperf\Contract\StdoutLoggerInterface;
use Hyperf\Pool\Connection as BaseConnection;
use Hyperf\Pool\Exception\ConnectionException;
use Hyperf\Redis\Exception\InvalidRedisConnectionException;
use Psr\Container\ContainerInterface;
/**
@ -193,25 +194,37 @@ class RedisConnection extends BaseConnection implements ConnectionInterface
$retryInterval = $this->config['retry_interval'] ?? 0;
$readTimeout = $this->config['sentinel']['read_timeout'] ?? 0;
$masterName = $this->config['sentinel']['master_name'] ?? '';
shuffle($nodes);
$host = '';
$port = 0;
$host = null;
$port = null;
foreach ($nodes as $node) {
[$sentinelHost, $sentinelPort] = explode(':', $node);
$sentinel = new \RedisSentinel(
$sentinelHost,
intval($sentinelPort),
$timeout,
$persistent,
$retryInterval,
$readTimeout
);
$masterInfo = $sentinel->getMasterAddrByName($masterName);
if (is_array($masterInfo) && count($masterInfo) >= 2) {
[$host, $port] = $masterInfo;
break;
try {
[$sentinelHost, $sentinelPort] = explode(':', $node);
$sentinel = new \RedisSentinel(
$sentinelHost,
intval($sentinelPort),
$timeout,
$persistent,
$retryInterval,
$readTimeout
);
$masterInfo = $sentinel->getMasterAddrByName($masterName);
if (is_array($masterInfo) && count($masterInfo) >= 2) {
[$host, $port] = $masterInfo;
break;
}
} catch (\Throwable $exception) {
$logger = $this->container->get(StdoutLoggerInterface::class);
$logger->warning('Redis sentinel connection failed, caused by ' . $exception->getMessage());
continue;
}
}
if ($host === null && $port === null) {
throw new InvalidRedisConnectionException('Connect sentinel redis server failed.');
}
$redis = $this->createRedis($host, $port, $timeout);
} catch (\Throwable $e) {
throw new ConnectionException('Connection reconnect failed ' . $e->getMessage());

View File

@ -172,6 +172,16 @@ class RedisTest extends TestCase
}
}
public function testShuffleNodes()
{
$nodes = ['127.0.0.1:6379', '127.0.0.1:6378', '127.0.0.1:6377'];
shuffle($nodes);
$this->assertIsArray($nodes);
$this->assertSame(3, count($nodes));
}
private function getRedis()
{
$container = $this->getContainer();

View File

@ -18,6 +18,7 @@
"require": {
"php": ">=7.2",
"doctrine/inflector": "^2.0",
"hyperf/context": "~3.0.0",
"hyperf/contract": "~3.0.0",
"hyperf/coordinator": "~3.0.0",
"hyperf/engine": "^1.2|^2.0",

View File

@ -13,6 +13,9 @@ namespace Hyperf\Utils;
use Hyperf\Engine\Coroutine as Co;
/**
* @deprecated v3.0 please use `hyperf/context` instead.
*/
class Context
{
protected static array $nonCoContext = [];

View File

@ -19,9 +19,9 @@ class Security
public const KEY = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
public const SEC_WEBSOCKET_KEY = 'Sec-Websocket-Key';
public const SEC_WEBSOCKET_KEY = 'sec-websocket-key';
public const SEC_WEBSOCKET_PROTOCOL = 'Sec-WebSocket-Protocol';
public const SEC_WEBSOCKET_PROTOCOL = 'sec-webSocket-protocol';
public function isInvalidSecurityKey(string $key): bool
{