Merge pull request #5171 from aldok10/3.0

Update doc [en]
This commit is contained in:
黄朝晖 2022-11-15 13:50:04 +08:00 committed by GitHub
commit 99211d154c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
52 changed files with 4698 additions and 934 deletions

View File

@ -1,5 +1,16 @@
# Changelogs # Changelogs
# v2.2.33 - 2022-05-30
## fix
- [#4776](https://github.com/hyperf/hyperf/pull/4776) Fix `GraphQL` event collection failure issue.
- [#4790](https://github.com/hyperf/hyperf/pull/4790) Fix the problem that the method `toRPNExpression` in the `RPN` component does not work properly in some scenarios.
## Added
- [#4763](https://github.com/hyperf/hyperf/pull/4763) Added validation rule `array:key1,key2` to ensure that there is no other `key` in the array except `key1` `key2` key.
- [#4781](https://github.com/hyperf/hyperf/pull/4781) Added configuration `close-pull-request.yml` to automatically close read-only repositories.
# v2.2.32 - 2022-05-16 # v2.2.32 - 2022-05-16
## Fixed ## Fixed

View File

@ -0,0 +1,104 @@
# ConfigProvider mechanism
The ConfigProvider mechanism is a very important mechanism for Hyperf componentization. `Decoupling between components` and `component independence` and `component reusability` are all based on this mechanism.
# What is the ConfigProvider mechanism?
In short, each component will provide a `ConfigProvider`, usually a `ConfigProvider` class is provided in the root directory of the component, and `ConfigProvider` will provide all the configuration information of the corresponding component, which will be started by the Hyperf framework When loading, the configuration information in `ConfigProvider` will be merged into the corresponding implementation class of `Hyperf\Contract\ConfigInterface`, so as to realize the configuration initialization when each component is used under the Hyperf framework.
`ConfigProvider` itself does not have any dependencies, does not inherit any abstract classes and does not require to implement any interfaces, just provide a `__invoke` method and return an array of corresponding configuration structures.
# How to define a ConfigProvider?
Usually, `ConfigProvider` is defined in the root directory of the component. A `ConfigProvider` class is usually as follows:
```php
<?php
namespace Hyperf\Foo;
class ConfigProvider
{
public function __invoke(): array
{
return [
// Merged into config/autoload/dependencies.php file
'dependencies' => [],
// Merged into config/autoload/annotations.php file
'annotations' => [
'scan' => [
'paths' => [
__DIR__,
],
],
],
// The definition of the default Command is merged into Hyperf\Contract\ConfigInterface and understood in another way, that is, it corresponds to config/autoload/commands.php
'commands' => [],
// similar to commands
'listeners' => [],
// The default configuration file of the component, that is, after executing the command, the corresponding file of source will be copied to the file corresponding to destination
'publish' => [
[
'id' => 'config',
'description' => 'description of this config file.', // describe
// It is recommended that the default configuration be placed in the publish folder with the same file name as the component name
'source' => __DIR__ . '/../publish/file.php', // Corresponding configuration file path
'destination' => BASE_PATH . '/config/autoload/file.php', // Copy as the file under this path
],
],
// You can also continue to define other configurations, which will eventually be merged into the configuration store corresponding to the ConfigInterface
];
}
}
```
## Default Profile Description
After defining `publish` in `ConfigProvider`, you can use the following commands to quickly generate configuration files
```bash
php bin/hyperf.php vendor:publish 包名称
```
If the package name is `hyperf/amqp`, you can execute the command to generate the `amqp` default configuration file
```bash
php bin/hyperf.php vendor:publish hyperf/amqp
```
Just creating a class will not be loaded automatically by Hyperf, you still need to add some definitions to the component's `composer.json` to tell Hyperf that this is a ConfigProvider class that needs to be loaded, you need to add some definitions in the component's `composer.json` Add the `extra.hyperf.config` configuration to the file, and specify the corresponding namespace of the `ConfigProvider` class, as follows:
```json
{
"name": "hyperf/foo",
"require": {
"php": ">=7.3"
},
"autoload": {
"psr-4": {
"Hyperf\\Foo\\": "src/"
}
},
"extra": {
"hyperf": {
"config": "Hyperf\\Foo\\ConfigProvider"
}
}
}
```
After the definition, you need to execute `composer install` or `composer update` or `composer dump-autoload` and other commands that will make Composer regenerate the `composer.lock` file before it can be read normally.
# Execution process of the ConfigProvider mechanism
The configuration of `ConfigProvider` is not necessarily divided in this way. This is some conventional format. In fact, the final decision on how to parse these configurations is also up to the user. The user can modify the Skeleton project's `config/container.php' ` code in the file to adjust the relevant loading, which means that the `config/container.php` file determines the scanning and loading of the `ConfigProvider`.
# Component design specification
Since the `extra` property in `composer.json` has no other function and influence when the data is not used, the definitions in these components will not cause any interference and influence when other frameworks are used, so `ConfigProvider` is A mechanism that only acts on the Hyperf framework and will not have any impact on other frameworks that do not use this mechanism, which lays the foundation for the reuse of components, but it also requires the following when designing components. specification:
- All classes must be designed to allow use through standard `OOP` usage, all Hyperf-specific functionality must be provided as enhancements and provided as separate classes, which means that standard non-Hyperf frameworks can still pass the standard means to achieve the use of components;
- If the dependent design of a component can meet the [PSR standard](https://www.php-fig.org/psr), it will first satisfy and depend on the corresponding interface rather than the implementation class; such as [PSR standard](https://www.php-fig.org/psr) www.php-fig.org/psr) does not contain functions, it can satisfy the interface in the contract library [Hyperf/contract](https://github.com/hyperf/contract) defined by Hyperf first and depend on The corresponding interface instead of the implementing class;
- For the enhanced function classes that implement Hyperf's proprietary functions, usually also have dependencies on some components of Hyperf, so the dependencies of these components should not be written in the `require` item of `composer.json`, but write exists as a suggestion in the `suggest` item;
- Component design should not perform any dependency injection through annotations. The injection method should only use the `constructor injection` method, which can also meet the use under `OOP`;
- During component design, any function definition should not be carried out through annotations, and function definitions should only be defined through `ConfigProvider`;
- The class design should not store state data as much as possible, because this will cause the class to not be provided as an object with a long life cycle, and it will not be able to use the dependency injection function very conveniently, which will reduce performance and state to a certain extent. Data should be stored through the `Hyperf\Context\Context` coroutine context;

View File

@ -0,0 +1,42 @@
# Create new component
`Hyperf` Official tools are provided to quickly create component packages.
```
# Create a component package adapted to the latest version of Hyperf
composer create-project hyperf/component-creator your_component dev-master
# Create a package for Hyperf 3.0
composer create-project hyperf/component-creator your_component "3.0.*"
```
## Using an unpublished component package in a project
Suppose the project directory is as follows
```
/opt/project // project directory
/opt/your_component // Component package directory
```
Suppose the component is named `your_component/your_component`
Revise /opt/project/composer.json
> Other irrelevant configurations are omitted below
```json
{
"require": {
"your_component/your_component": "dev-master"
},
"repositories": {
"your_component": {
"type": "path",
"url": "/opt/your_component"
}
}
}
```
Finally, execute `composer update -o` in the directory `/opt/project`.

View File

@ -1,31 +1,31 @@
# 指南前言 # Guide Preface
为了帮助开发者更好的为 Hyperf 开发组件,共建生态,我们提供了本指南用于指导开发者进行组件开发,在阅读本指南前,需要您对 Hyperf 的文档进行了 **全面** 的阅读,特别是 [协程](en/coroutine.md) 和 [依赖注入](en/di.md) 章节,如果对 Hyperf 的基础组件缺少充分的理解,可能会导致开发时出现错误。 In order to help developers better develop components for Hyperf and build an ecosystem together, we provide this guide to guide developers in component development. Before reading this guide, you need a **comprehensive** of Hyperf's documentation. Read, especially the [Coroutines](en/coroutine.md) and [Dependency Injection](en/di.md) chapters, a lack of a good understanding of the underlying components of Hyperf may lead to development errors.
# 组件开发的目的 # The purpose of component development
在传统的 PHP-FPM 架构下的开发,通常在我们需要借助第三方库来解决我们的需求时,都会通过 Composer 来直接引入一个对应的 `库(Library)`,但是在 Hyperf 下,由于 `持久化应用``协程` 这两个特性,导致了应用的生命周期和模式存在一些差异,所以并不是所有的 `库(Library)` 都能在 Hyperf 里直接使用,当然,一些设计优秀的 `库(Library)` 也是可以被直接使用的。通读本指南,便可知道如何甄别一些 `库(Library)` 是否能直接用于项目内,不能的话该进行如何的改动。 In the development under the traditional PHP-FPM architecture, usually when we need to use third-party libraries to solve our needs, we will directly introduce a corresponding `Library` through Composer, but under Hyperf, due to `persistence' The two characteristics of `Application` and `Coroutine` lead to some differences in the life cycle and mode of the application, so not all `Library` can be used directly in Hyperf. Of course, some well-designed ones `Library` can also be used directly. After reading this guide, you will know how to identify whether some `Library` can be used directly in the project, and how to change it if not.
# 组件开发准备工作 # Component development preparation
这里所指的开发准备工作,除了 Hyperf 的基础运行条件外,这里关注的更多是如何更加便捷的组织代码的结构以便于组件的开发工作,注意以下方式可能会由于 *软连接无法跳转的问题* 而并不适用于 Windows for Docker 下的开发环境。 The development preparation work referred to here, in addition to the basic operating conditions of Hyperf, focuses more on how to organize the structure of the code more conveniently to facilitate the development of components. Note that the following methods may not be able to jump due to *soft connections Problem* and does not apply to development environments under Windows for Docker.
在代码组织上,我们建议在同一个目录下 Clone [hyperf-cloud/hyperf-skeleton](https://github.com/hyperf-cloud/hyperf-skeleton) 项目骨架和 [hyperf-cloud/hyperf](https://github.com/hyperf-cloud/hyperf) 项目组件库两个项目。进行下面的操作并呈以下结构: In terms of code organization, we recommend Clone [hyperf-cloud/hyperf-skeleton](https://github.com/hyperf-cloud/hyperf-skeleton) project skeleton and [hyperf-cloud/hyperf]( https://github.com/hyperf-cloud/hyperf) project component library two projects. Do the following and have the following structure:
```bash ```bash
// 安装 skeleton并配置完成 // Install the skeleton and configure it
composer create-project hyperf/hyperf-skeleton composer create-project hyperf/hyperf-skeleton
// 克隆 hyperf 组件库项目,这里记得要替换 hyperf-cloud 为您的 Github ID也就是克隆您所 Fork 的项目 // Clone the hyperf component library project, remember to replace hyperf-cloud with your Github ID, that is, clone the project you fork
git clone git@github.com:hyperf-cloud/hyperf.git git clone git@github.com:hyperf-cloud/hyperf.git
``` ```
呈以下结构: Has the following structure:
``` ```
. .
├── hyperf ├── hyperf
   ├── bin ├── bin
   └── src └── src
└── hyperf-skeleton └── hyperf-skeleton
├── app ├── app
├── bin ├── bin
@ -35,7 +35,7 @@ git clone git@github.com:hyperf-cloud/hyperf.git
└── vendor └── vendor
``` ```
这样做的目的是为了让 `hyperf-skeleton` 项目可以直接通过 `path` 来源的形式,让 Composer 直接通过 `hyperf` 文件夹内的项目作为依赖项被加载到 `hyperf-skelton` 项目的 `vendor` 目录中,我们对 `hyperf-skelton` 内的 `composer.json` 文件增加一个 `repositories` 项,如下: The purpose of this is to make the `hyperf-skeleton` project available directly through the `path` source form, and let Composer directly pass the projects in the `hyperf` folder as dependencies to be loaded into the `vendor of the `hyperf-skelton` project ` directory, we add a `repositories` item to the `composer.json` file in `hyperf-skelton`, as follows:
```json ```json
{ {
@ -51,21 +51,21 @@ git clone git@github.com:hyperf-cloud/hyperf.git
} }
} }
``` ```
然后再在 `hyperf-skeleton` 项目内删除 `composer.lock` 文件和 `vendor` 文件夹,再执行 `composer update` 让依赖重新更新,命令如下: Then delete the `composer.lock` file and `vendor` folder in the `hyperf-skeleton` project, and then execute `composer update` to update the dependencies again, the command is as follows:
```bash ```bash
cd hyperf-skeleton cd hyperf-skeleton
rm -rf composer.lock && rm -rf vendor && composer update rm -rf composer.lock && rm -rf vendor && composer update
``` ```
最终使 `hyperf-skeleton/vendor/hyperf` 文件夹内的项目文件夹全部通过 `软连接(softlink)` 连接到 `hyperf` 文件夹内。我们可以通过 `ls -l` 命令来验证 `软连接(softlink)` 是否已经建立成功: Finally, the project folders in the `hyperf-skeleton/vendor/hyperf` folder are all connected to the `hyperf` folder through a `softlink`. We can use the `ls -l` command to verify whether the `softlink` has been established successfully:
```bash ```bash
cd vendor/hyperf/ cd vendor/hyperf/
ls -l ls -l
``` ```
当我们看到类似下面这样的连接关系,即表明 `软连接(softlink)` 建立成功了: When we see a connection relationship like the following, it means that the `softlink` has been established successfully:
``` ```
cache -> ../../../hyperf/src/cache cache -> ../../../hyperf/src/cache
@ -94,4 +94,4 @@ testing -> ../../../hyperf/src/testing
utils -> ../../../hyperf/src/utils utils -> ../../../hyperf/src/utils
``` ```
此时,我们便可达到在 IDE 内直接对 `vendor/hyperf` 内的文件进行修改,而修改的却是 `hyperf` 内的代码的目的,这样最终我们便可直接对 `hyperf` 项目内进行 `commit`,然后向主干提交 `Pull Request(PR)` 了。 At this point, we can directly modify the files in `vendor/hyperf` in the IDE, but the code in `hyperf` is modified, so that we can directly modify the `hyperf` project. `commit`, and then submit a `Pull Request(PR)` to the trunk.

178
docs/en/constants.md Normal file
View File

@ -0,0 +1,178 @@
# Enum class
When you need to define error codes and error messages, the following methods may be used,
```php
<?php
class ErrorCode
{
const SERVER_ERROR = 500;
const PARAMS_INVALID = 1000;
public static $messages = [
self::SERVER_ERROR => 'Server Error',
self::PARAMS_INVALID => 'Illegal parameter'
];
}
$message = ErrorCode::messages[ErrorCode::SERVER_ERROR] ?? 'unknown mistake';
```
But this implementation method is not friendly. Whenever you want to query the error code and the corresponding error information, you have to search the current `Class` twice, so the framework provides an annotation-based enumeration class.
## Install
```
composer require hyperf/constants
```
## Use
### Define the enum class
An enumeration class can be generated quickly with the `gen:constant` command.
```bash
php bin/hyperf.php gen:constant ErrorCode
```
```php
<?php
declare(strict_types=1);
namespace App\Constants;
use Hyperf\Constants\AbstractConstants;
use Hyperf\Constants\Annotation\Constants;
#[Constants]
class ErrorCode extends AbstractConstants
{
/**
* @Message("Server Error")
*/
const SERVER_ERROR = 500;
/**
* @Message("System parameter error")
*/
const SYSTEM_INVALID = 700;
}
```
User can use `ErrorCode::getMessage(ErrorCode::SERVER_ERROR)` to get the corresponding error message.
### Define exception class
If you simply use the `enumeration class`, it is not convenient enough when handling exceptions. So we need to define our own exception class `BusinessException`. When an exception comes in, it will actively query the corresponding error information according to the error code.
```php
<?php
declare(strict_types=1);
namespace App\Exception;
use App\Constants\ErrorCode;
use Hyperf\Server\Exception\ServerException;
use Throwable;
class BusinessException extends ServerException
{
public function __construct(int $code = 0, string $message = null, Throwable $previous = null)
{
if (is_null($message)) {
$message = ErrorCode::getMessage($code);
}
parent::__construct($message, $code, $previous);
}
}
```
### Throw an exception
After completing the above two steps, the corresponding exception can be thrown in the business logic.
```php
<?php
declare(strict_types=1);
namespace App\Controller;
use App\Constants\ErrorCode;
use App\Exception\BusinessException;
class IndexController extends AbstractController
{
public function index()
{
throw new BusinessException(ErrorCode::SERVER_ERROR);
}
}
```
### Variable parameter
When using `ErrorCode::getMessage(ErrorCode::SERVER_ERROR)` to get the corresponding error message, we can also pass in variable parameters to combine error messages. For example the following
```php
<?php
use Hyperf\Constants\AbstractConstants;
use Hyperf\Constants\Annotation\Constants;
#[Constants]
class ErrorCode extends AbstractConstants
{
/**
* @Message("Params %s is invalid.")
*/
const PARAMS_INVALID = 1000;
}
$message = ErrorCode::getMessage(ErrorCode::PARAMS_INVALID, ['user_id']);
// 1.2 Below version The following methods can be used, but will be removed in version 1.2
$message = ErrorCode::getMessage(ErrorCode::PARAMS_INVALID, 'user_id');
```
### Globalization
> This feature is only available on v1.1.13 and later
To enable the [hyperf/constants](https://github.com/hyperf/constants) component to support internationalization, the [hyperf/translation](https://github.com/hyperf/translation) component must be installed and configured Good language files, as follows:
```
composer require hyperf/translation
```
For related configuration, see [Internationalization](en/translation.md)
```php
<?php
// International configuration
return [
'params.invalid' => 'Params :param is invalid.',
];
use Hyperf\Constants\AbstractConstants;
use Hyperf\Constants\Annotation\Constants;
#[Constants]
class ErrorCode extends AbstractConstants
{
/**
* @Message("params.invalid")
*/
const PARAMS_INVALID = 1000;
}
$message = ErrorCode::getMessage(ErrorCode::SERVER_ERROR, ['param' => 'user_id']);
```

File diff suppressed because one or more lines are too long

106
docs/en/db/db.md Normal file
View File

@ -0,0 +1,106 @@
# Minimalist DB component
[hyperf/database](https://github.com/hyperf/database) The function is very powerful, but it is undeniable that the efficiency is indeed a little insufficient. Here is a minimalist `hyperf/db` component that supports `PDO` and `Swoole Mysql`.
## Install
```bash
composer require hyperf/db
```
## Publish component configuration
The configuration file for this component is located at `config/autoload/db.php`. If the file does not exist, you can publish the configuration file to the skeleton with the following command:
```bash
php bin/hyperf.php vendor:publish hyperf/db
```
## Component configuration
The default configuration `config/autoload/db.php` is as follows, the database supports multi-database configuration, the default is `default`.
| configuration item | type | default | remark |
|:--------------------:|:------:|:------------------:|:--------------------------------------------------------:|
| driver | string | none | The database engine supports `pdo` and `mysql` |
| host | string | `localhost` | database address |
| port | int | 3306 | database address |
| database | string | none | Database default DB |
| username | string | none | database username |
| password | string | null | database password |
| charset | string | utf8 | database charset |
| collation | string | utf8_unicode_ci | database collation |
| fetch_mode | int | `PDO::FETCH_ASSOC` | PDO Query result set type |
| pool.min_connections | int | 1 | The minimum number of connections in the connection pool |
| pool.max_connections | int | 10 | The maximum number of connections in the connection pool |
| pool.connect_timeout | float | 10.0 | connection wait timeout |
| pool.wait_timeout | float | 3.0 | overtime time |
| pool.heartbeat | int | -1 | heartbeat |
| pool.max_idle_time | float | 60.0 | maximum idle time |
| options | array | | PDO configure |
## Component supported methods
The specific interface can be viewed `Hyperf\DB\ConnectionInterface`
| method name | return value | remark |
|:----------------:|:--------------:|:-----------------------------------------------------------------------------------:|
| beginTransaction | `void` | Open transaction (Support transaction nesting) |
| commit | `void` | Commit transaction (Support transaction nesting |
| rollBack | `void` | Rollback transaction (Support transaction nesting) |
| insert | `int` | Insert data, return the primary key ID, non-auto-incrementing primary key returns 0 |
| execute | `int` | Execute SQL to return the number of rows affected |
| query | `array` | Query SQL, return a list of result sets |
| fetch | `array, object`| Query SQL to return the first row of the result set |
| connection | `self` | Specify the database to connect to |
## Use
### Use DB instance
```php
<?php
use Hyperf\Utils\ApplicationContext;
use Hyperf\DB\DB;
$db = ApplicationContext::getContainer()->get(DB::class);
$res = $db->query('SELECT * FROM `user` WHERE gender = ?;', [1]);
```
### Use static methods
```php
<?php
use Hyperf\DB\DB;
$res = DB::query('SELECT * FROM `user` WHERE gender = ?;', [1]);
```
### Custom methods using anonymous functions
> This method allows users to directly operate the underlying `PDO` or `MySQL`, so you need to deal with compatibility issues yourself
For example, if we want to execute certain queries and use different `fetch mode`, we can customize our own methods in the following ways
```php
<?php
use Hyperf\DB\DB;
$sql = 'SELECT * FROM `user` WHERE id = ?;';
$bindings = [2];
$mode = \PDO::FETCH_OBJ;
$res = DB::run(function (\PDO $pdo) use ($sql, $bindings, $mode) {
$statement = $pdo->prepare($sql);
$this->bindValues($statement, $bindings);
$statement->execute();
return $statement->fetchAll($mode);
});
```

View File

@ -1,12 +1,12 @@
# 事件 # Event
模型事件实现于 [psr/event-dispatcher](https://github.com/php-fig/event-dispatcher) 接口。 Model events are implemented in the [psr/event-dispatcher](https://github.com/php-fig/event-dispatcher) interface.
## 自定义监听器 ## Custom listener
得益于 [hyperf/event](https://github.com/hyperf-cloud/event) 组件的支撑,用户可以很方便的对以下事件进行监听。 Thanks to the support of the [hyperf/event](https://github.com/hyperf-cloud/event) component, users can easily monitor the following events.
例如 `QueryExecuted` , `StatementPrepared` , `TransactionBeginning` , `TransactionCommitted` , `TransactionRolledBack` For example `QueryExecuted` , `StatementPrepared` , `TransactionBeginning` , `TransactionCommitted` , `TransactionRolledBack` .
接下来我们就实现一个记录SQL的监听器来说一下怎么使用。 Next, we will implement a listener that records SQL and talk about how to use it.
首先我们定义好 `DbQueryExecutedListener` ,实现 `Hyperf\Event\Contract\ListenerInterface` 接口并对类定义 `Hyperf\Event\Annotation\Listener` 注解,这样 Hyperf 就会自动把该监听器注册到事件调度器中,无需任何手动配置,示例代码如下: First, we define `DbQueryExecutedListener`, implement the `Hyperf\Event\Contract\ListenerInterface` interface and define the `Hyperf\Event\Annotation\Listener` annotation on the class, so that Hyperf will automatically register the listener to the event scheduler, Without any manual configuration, the sample code is as follows:
```php ```php
<?php <?php
@ -66,30 +66,30 @@ class DbQueryExecutedListener implements ListenerInterface
``` ```
## 模型事件 ## Model events
模型事件与 `EloquentORM` 不太一致,`EloquentORM` 使用 `Observer` 监听模型事件。`Hyperf` 直接使用 `钩子函数` 来处理对应的事件。如果你还是喜欢 `Observer` 的方式,可以通过 `事件监听`,自己实现。当然,你也可以在 [issue#2](https://github.com/hyperf-cloud/hyperf/issues/2) 下面告诉我们。 Model events are not consistent with `EloquentORM`, which uses `Observer` to listen for model events. `Hyperf` directly uses `hooks` to handle corresponding events. If you still like the way of `Observer`, you can implement `event listener` by yourself. Of course, you can also let us know under [issue#2](https://github.com/hyperf-cloud/hyperf/issues/2).
### 钩子函数 ### Hook function
| 事件名 | 触发实际 | 是否阻断 | 备注 | | Event name | Trigger actual | Whether to block | Remark |
|:------------:|:----------------:|:--------:|:-------------------------- --:| |:------------:|:-----------------------------------------------:|:----------------:|:----------------------------------------------------------:|
| booting | 模型首次加载前 | 否 | 进程生命周期中只会触发一次 | | booting | Before the model is loaded for the first time | no | Triggered only once in the life cycle of the process |
| booted | 模型首次加载后 | 否 | 进程生命周期中只会触发一次 | | booted | After the model is loaded for the first time | no | Triggered only once in the life cycle of the process |
| retrieved | 填充数据后 | 否 | 每当模型从DB或缓存查询出来后触发 | | retrieved | After filling the data | no | Fired whenever the model is queried from the DB or cache |
| creating | 数据创建时 | 是 | | | creating | When the data was created | yes | |
| created | 数据创建后 | 否 | | | created | After the data is created | no | |
| updating | 数据更新时 | 是 | | | updating | When data is updated | yes | |
| updated | 数据更新后 | 否 | | | updated | After data update | no | |
| saving | 数据创建或更新时 | 是 | | | saving | When data is created or updated | yes | |
| saved | 数据创建或更新后 | 否 | | | saved | After data is created or updated | no | |
| restoring | 软删除数据回复时 | 是 | | | restoring | When soft deleted data is restored | yes | |
| restored | 软删除数据回复后 | 否 | | | restored | After Soft Deleted Data Recovery | no | |
| deleting | 数据删除时 | 是 | | | deleting | When data is deleted | yes | |
| deleted | 数据删除后 | 否 | | | deleted | After data deletion | no | |
| forceDeleted | 数据强制删除后 | 否 | | | forceDeleted | After the data is forcibly deleted | no | |
针对某个模型的事件使用十分简单,只需要在模型中增加对应的方法即可。例如下方保存数据时,触发 `saving` 事件,主动覆写 `created_at` 字段。 The use of events for a model is very simple, just add the corresponding method to the model. For example, when the data is saved below, the `saving` event is triggered, and the `created_at` field is actively overwritten.
```php ```php
<?php <?php
@ -133,9 +133,9 @@ class User extends Model
``` ```
### 事件监听 ### Event listener
当你需要监听所有的模型事件时,可以很方便的自定义对应的 `Listener`,比如下方模型缓存的监听器,当模型修改和删除后,会删除对应缓存。 When you need to monitor all model events, you can easily customize the corresponding `Listener`, such as the listener of the model cache below. When the model is modified and deleted, the corresponding cache will be deleted.
```php ```php
<?php <?php

View File

@ -1,6 +1,6 @@
# Model creation script # Model creation script
Hyperf provides commands to create models, and you can easily create corresponding models based on data tables. The command generates the model via `AST`, so when you add certain methods, you can also easily reset the model with a script. Hyperf provides commands to create models, and you can easily create corresponding models based on data tables. The command generates the model via `AST`, so when you add certain methods, you can also easily reset the model with the script.
```bash ```bash
php bin/hyperf.php gen:model table_name php bin/hyperf.php gen:model table_name
@ -10,23 +10,24 @@ php bin/hyperf.php gen:model table_name
The optional parameters are as follows The optional parameters are as follows
| parameter | type | defaults | Remark | | Parameter | Type | Defaults | Remark |
| :----------------: | :----: | :-------------------------------: | :-----------------------------------------------: | |:------------------:|:------:|:---------------------------------:|:----------------------------------------------------------------------------------------------:|
| --pool | string | `default` | Connection pool, the script will be created based on the current connection pool configuration | | --pool | string | `default` | Connection pool, the script will be created based on the current connection pool configuration |
| --path | string | `app/Model` | model path | | --path | string | `app/Model` | model path |
| --force-casts | bool | `false` | Whether to force reset the `casts` parameter | | --force-casts | bool | `false` | Whether to force reset the `casts` parameter |
| --prefix | string | empty string | table prefix | | --prefix | string | empty string | table prefix |
| --inheritance | string | `Model` | father | | --inheritance | string | `Model` | The parent class |
| --uses | string | `Hyperf\DbConnection\Model\Model` | Use with `inheritance` | | --uses | string | `Hyperf\DbConnection\Model\Model` | Use with `inheritance` |
| --refresh-fillable | bool | `false` | whether to refresh the `fillable` parameter | | --refresh-fillable | bool | `false` | Whether to refresh the `fillable` parameter |
| --table-mapping | array | `[]` | Add a mapping relationship for table name -> model such as ['users:Account'] | | --table-mapping | array | `[]` | Add a mapping relationship for table name -> model such as ['users:Account'] |
| --ignore-tables | array | `[]` | There is no need to generate the table name of the model e.g. ['users'] | | --ignore-tables | array | `[]` | There is no need to generate the table name of the model e.g. ['users'] |
| --with-comments | bool | `false` | Whether to add field comments | | --with-comments | bool | `false` | Whether to add field comments |
| --property-case | int | `0` | Field Type 0 snakecase 1 CamelCase | | --property-case | int | `0` | Field Type: 0 Snakecase, 1 CamelCase |
When using `--property-case` to convert the field type to camel case, you also need to manually add `Hyperf\Database\Model\Concerns\CamelCase` to the model. When using `--property-case` to convert the field type to camel case, you also need to manually add `Hyperf\Database\Model\Concerns\CamelCase` to the model.
The corresponding configuration can also be configured in `databases.{pool}.commands.gen:model`, as follows The corresponding configuration can also be configured in `databases.{pool}.commands.gen:model`, as follows
> All underscores need to be converted to underscores
> All struck-through need to be converted to underscores
```php ```php
<?php <?php
@ -55,6 +56,7 @@ return [
``` ```
The created model is as follows The created model is as follows
```php ```php
<?php <?php
@ -99,6 +101,7 @@ class User extends Model
## Visitors ## Visitors
The framework provides several `Visitors` for users to extend the scripting capabilities. The usage is very simple, just add the corresponding `Visitor` in the `visitors` configuration. The framework provides several `Visitors` for users to extend the scripting capabilities. The usage is very simple, just add the corresponding `Visitor` in the `visitors` configuration.
```php ```php
<?php <?php
@ -131,6 +134,7 @@ This `Visitor` can judge whether the model contains soft delete fields according
- Hyperf\Database\Commands\Ast\ModelRewriteTimestampsVisitor - Hyperf\Database\Commands\Ast\ModelRewriteTimestampsVisitor
This `Visitor` can automatically determine, based on `created_at` and `updated_at`, whether to enable the default recording of `created and modified times`. This `Visitor` can automatically determine, based on `created_at` and `updated_at`, whether to enable the default recording of `created and modified times`.
- Hyperf\Database\Commands\Ast\ModelRewriteGetterSetterVisitor - Hyperf\Database\Commands\Ast\ModelRewriteGetterSetterVisitor
This `Visitor` can generate corresponding `getters` and `setters` based on database fields. This `Visitor` can generate corresponding `getters` and `setters` based on database fields.
@ -181,6 +185,7 @@ class UserExt extends Model
``` ```
At this point, we can modify this feature by overriding `ModelUpdateVisitor`. At this point, we can modify this feature by overriding `ModelUpdateVisitor`.
```php ```php
<?php <?php
@ -210,7 +215,7 @@ class ModelUpdateVisitor extends Visitor
case 'bigint': case 'bigint':
return 'integer'; return 'integer';
case 'decimal': case 'decimal':
// 设置为 decimal并设置对应精度 // Set to decimal, and set the corresponding precision
return 'decimal:2'; return 'decimal:2';
case 'float': case 'float':
case 'double': case 'double':
@ -251,6 +256,7 @@ class ModelUpdateVisitor extends Visitor
``` ```
Configure the mapping relationship `dependencies.php` Configure the mapping relationship `dependencies.php`
```php ```php
<?php <?php
@ -261,6 +267,7 @@ return [
``` ```
After re-executing `gen:model`, the corresponding model is as follows: After re-executing `gen:model`, the corresponding model is as follows:
```php ```php
<?php <?php

View File

@ -1,29 +1,29 @@
# 数据库迁移 # Database migration
数据库迁移可以理解为对数据库结构的版本管理,可以有效的解决团队中跨成员对数据库结构的管理。 Database migration can be understood as version management of the database structure, which can effectively solve the management of the database structure across members of the team.
# 生成迁移 # Generate migrations
通过 `gen:migration` 生成一个迁移文件,命令后面跟的是一个文件名参数,通常为这个迁移要打算做的事情。 Generate a migration file via `gen:migration`, the command is followed by a filename parameter, usually for what the migration is intended to do.
```bash ```bash
php bin/hyperf.php gen:migration create_users_table php bin/hyperf.php gen:migration create_users_table
``` ```
生成的迁移文件位于根目录下的 `migrations` 文件夹内,每个迁移文件都包含一个时间戳,以便迁移程序确定迁移的顺序。 The generated migration files are located in the `migrations` folder in the root directory, and each migration file includes a timestamp so that the migration program can determine the order of migrations.
`--table` 选项可以用来指定数据表的名称,指定的表名将会默认生成在迁移文件中。 The `--table` option can be used to specify the name of the data table. The specified table name will be generated in the migration file by default.
`--create` 选项也是用来指定数据表的名称,但跟 `--table` 的差异在于该选项是生成创建表的迁移文件,而 `--table` 是用于修改表的迁移文件。 The `--create` option is also used to specify the name of the data table, but the difference from `--table` is that this option generates a migration file for creating a table, while `--table` is a migration file for modifying the table.
```bash ```bash
php bin/hyperf.php gen:migration create_users_table --table=users php bin/hyperf.php gen:migration create_users_table --table=users
php bin/hyperf.php gen:migration create_users_table --create=users php bin/hyperf.php gen:migration create_users_table --create=users
``` ```
# 迁移结构 # Migration structure
迁移类默认会包含 `2` 个方法:`up` 和 `down` The migration class will contain `2` methods by default: `up` and `down`.
`up` 方法用于添加新的数据表,字段或者索引到数据库,而 `down` 方法就是 `up` 方法的反操作,和 `up` 里的操作相反,以便在回退的时候执行。 The `up` method is used to add a new data table, field or index to the database, and the `down` method is the inverse of the `up` method, which is the opposite of the operation in `up`, so that it is executed during rollback.
```php ```php
<?php <?php
@ -55,77 +55,77 @@ class CreateUsersTable extends Migration
} }
``` ```
# 运行迁移 # Run migration
通过执行 `migrate` 命令运行所有尚未完成的迁移文件: Run all pending migration files by executing the `migrate` command:
```bash ```bash
php bin/hyperf.php migrate php bin/hyperf.php migrate
``` ```
## 强制执行迁移 ## Force the migration
一些迁移操作是具有破坏性的,这意味着可能会导致数据丢失,为了防止有人在生产环境中运行这些命令,系统会在这些命令运行之前与你进行确认,但如果您希望忽略这些确认信息强制运行命令,可以使用 `--force` 标记: Some migration operations are destructive, which means that data loss may result. To prevent someone from running these commands in a production environment, the system will confirm with you before these commands are run, but if you wish to ignore these confirmations, force To run a command, you can use the `--force` flag:
```bash ```bash
php bin/hyperf.php migrate --force php bin/hyperf.php migrate --force
``` ```
## 回滚迁移 ## Rollback migration
若您希望回滚最后一次的迁移,可以通过 `migrate:rollback` 命令回滚最后一侧的迁移,注意一次迁移可能会包含多个迁移文件: If you want to roll back the last migration, you can use the `migrate:rollback` command to roll back the last migration. Note that a migration may contain multiple migration files:
```bash ```bash
php bin/hyperf.php migrate:rollback php bin/hyperf.php migrate:rollback
``` ```
您还可以在 `migrate:rollback` 命令后面加上 `step` 参数来设置回滚迁移的次数,比如以下命令将回滚最近 5 次迁移: You can also set the number of rollback migrations by appending the `step` parameter to the `migrate:rollback` command. For example, the following command will roll back the last 5 migrations:
```bash ```bash
php bin/hyperf.php migrate:rollback --step=5 php bin/hyperf.php migrate:rollback --step=5
``` ```
如果您希望回滚所有的迁移,可以通过 `migrate:reset` 来回滚: If you wish to roll back all migrations, you can do so with `migrate:reset`:
```bash ```bash
php bin/hyperf.php migrate:reset php bin/hyperf.php migrate:reset
``` ```
## 回滚并迁移 ## Rollback & Migrate
`migrate:refresh` 命令不仅会回滚迁移还会接着运行 `migrate` 命令,这样可以高效地重建某些迁移: `migrate:refresh` The command not only rolls back the migration but also runs `migrate` command, which rebuilds some migrations efficiently:
```bash ```bash
php bin/hyperf.php migrate:refresh php bin/hyperf.php migrate:refresh
// 重建数据库结构并执行数据填充 // Rebuild database structure and perform data population
php bin/hyperf.php migrate:refresh --seed php bin/hyperf.php migrate:refresh --seed
``` ```
通过 `--step` 参数指定回滚和重建次数,比如以下命令将回滚并重新执行最后 5 次迁移: Specify the number of rollbacks and rebuilds with the `--step` parameter. For example, the following command will rollback and re-execute the last 5 migrations:
```bash ```bash
php bin/hyperf.php migrate:refresh --step=5 php bin/hyperf.php migrate:refresh --step=5
``` ```
## 重建数据库 ## Rebuild database
通过 `migrate:fresh` 命令可以高效地重建整个数据库,这个命令会先删除所有的数据库,然后再执行 `migrate` 命令: The entire database can be efficiently rebuilt with the `migrate:fresh` command, which deletes all databases before executing the `migrate` command:
```bash ```bash
php bin/hyperf.php migrate:fresh php bin/hyperf.php migrate:fresh
// 重建数据库结构并执行数据填充 // Rebuild database structure and perform data population
php bin/hyperf.php migrate:fresh --seed php bin/hyperf.php migrate:fresh --seed
``` ```
# 数据表 # Schema
在迁移文件中主要通过 `Hyperf\Database\Schema\Schema` 类来定义数据表和管理迁移流程。 In the migration file, the `Hyperf\Database\Schema\Schema` class is mainly used to define the data table and manage the migration process.
## 创建数据表 ## Create table
通过 `create` 方法来创建新的数据库表。 `create` 方法接受两个参数:第一个参数为数据表的名称,第二个参数是一个 `闭包(Closure)`,此闭包会接收一个用于定义新数据表的 `Hyperf\Database\Schema\Blueprint` 对象: Create a new database table with the `create` method. The `create` method accepts two parameters: the first parameter is the name of the data table, and the second parameter is a `Closure`, which will receive a `Hyperf\Database' to define the new data table \Schema\Blueprint` object:
```php ```php
<?php <?php
@ -146,34 +146,34 @@ class CreateUsersTable extends Migration
} }
``` ```
您可以在数据库结构生成器上使用以下命令来定义表的选项: You can use the following commands on the database structure generator to define options for a table:
```php ```php
// 指定表存储引擎 // Specify the table storage engine
$table->engine = 'InnoDB'; $table->engine = 'InnoDB';
// 指定数据表的默认字符集 // Specifies the default character set for data tables
$table->charset = 'utf8'; $table->charset = 'utf8';
// 指定数据表默认的排序规则 // Specifies the default collation of the data table
$table->collation = 'utf8_unicode_ci'; $table->collation = 'utf8_unicode_ci';
// 创建临时表 // Create a temporary table
$table->temporary(); $table->temporary();
``` ```
## 重命名数据表 ## Rename table
若您希望重命名一个数据表,可以通过 `rename` 方法: If you wish to rename a data table, you can use the `rename` method:
```php ```php
Schema::rename($from, $to); Schema::rename($from, $to);
``` ```
### 重命名带外键的数据表 ### Rename table with foreign key
在重命名表之前,您应该验证表上的所有外键约束在迁移文件中都有明确的名称,而不是让迁移程序按照约定来设置一个名称,否则,外键的约束名称将引用旧表名。 Before renaming a table, you should verify that all foreign key constraints on the table have an explicit name in the migration file, rather than letting the migration program set a name by convention, otherwise, the foreign key's constraint name will refer to the old table name .
## 删除数据表 ## Drop table
删除一个已存在的数据表,可以通过 `drop``dropIfExists` 方法: To drop an existing table, use the `drop` or `dropIfExists` methods:
```php ```php
Schema::drop('users'); Schema::drop('users');
@ -181,9 +181,9 @@ Schema::drop('users');
Schema::dropIfExists('users'); Schema::dropIfExists('users');
``` ```
## 检查数据表或字段是否存在 ## Check if the data table or field exists
可以通过 `hasTable``hasColumn` 方法来检查数据表或字段是否存在: The `hasTable` and `hasColumn` methods can be used to check whether a data table or field exists:
```php ```php
if (Schema::hasTable('users')) { if (Schema::hasTable('users')) {
@ -195,9 +195,9 @@ if (Schema::hasColumn('name', 'email')) {
} }
``` ```
## 数据库连接选项 ## Database connection options
如果在同时管理多个数据库的情况下,不同的迁移会对应不同的数据库连接,那么此时我们可以在迁移文件中通过重写父类的 `$connection` 类属性来定义不同的数据库连接: If multiple databases are managed at the same time, different migrations will correspond to different database connections, then we can define different database connections in the migration file by overriding the `$connection` class attribute of the parent class:
```php ```php
<?php <?php
@ -208,7 +208,7 @@ use Hyperf\Database\Migrations\Migration;
class CreateUsersTable extends Migration class CreateUsersTable extends Migration
{ {
// 这里对应 config/autoload/databases.php 内的连接 key // This corresponds to the connection key in config/autoload/databases.php
protected $connection = 'foo'; protected $connection = 'foo';
public function up(): void public function up(): void
@ -221,11 +221,11 @@ class CreateUsersTable extends Migration
} }
``` ```
# 字段 # Fields
## 创建字段 ## Create fields
`table``create` 方法的第二个参数的 `闭包(Closure)` 内定义该迁移文件要执行的定义或变更,比如下面的代码为定义一个 `name` 的字符串字段: Define the definition or change to be performed by the migration file within the `Closure` of the second parameter of the `table` or `create` method. For example, the following code defines a string field of `name`:
```php ```php
<?php <?php
@ -245,194 +245,194 @@ class CreateUsersTable extends Migration
} }
``` ```
## 可用的字段定义方法 ## Available field definition methods
| 命令 | 描述 | Command | Description
| --- | --- | | ------------------------------------------ | ------------------------------------------------------------------------------- |
| $table->bigIncrements('id'); | 递增 ID主键相当于「UNSIGNED BIG INTEGER」 | | $table->bigIncrements('id'); | Increment ID (primary key), equivalent to "UNSIGNED BIG INTEGER" |
| $table->bigInteger('votes'); | 相当于 BIGINT | | $table->bigInteger('votes'); | equivalent to BIGINT |
| $table->binary('data'); | 相当于 BLOB | | $table->binary('data'); | equivalent to BLOB |
| $table->boolean('confirmed'); | 相当于 BOOLEAN | | $table->boolean('confirmed'); | equivalent to BOOLEAN |
| $table->char('name', 100); | 相当于带有长度的 CHAR | | $table->char('name', 100); | equivalent to with length CHAR |
| $table->date('created_at'); | 相当于 DATE | | $table->date('created_at'); | equivalent to DATE |
| $table->dateTime('created_at'); | 相当于 DATETIME | | $table->dateTime('created_at'); | equivalent to DATETIME |
| $table->dateTimeTz('created_at'); | 相当于带时区 DATETIME | | $table->dateTimeTz('created_at'); | equivalent to with time zone DATETIME |
| $table->decimal('amount', 8, 2); | 相当于带有精度与基数 DECIMAL | | $table->decimal('amount', 8, 2); | equivalent to with precision and base DECIMAL |
| $table->double('amount', 8, 2); | 相当于带有精度与基数 DOUBLE | | $table->double('amount', 8, 2); | equivalent to with precision and base DOUBLE |
| $table->enum('level', ['easy', 'hard']); | 相当于 ENUM | | $table->enum('level', ['easy', 'hard']); | equivalent to ENUM |
| $table->float('amount', 8, 2); | 相当于带有精度与基数 FLOAT | | $table->float('amount', 8, 2); | equivalent to with precision and base FLOAT |
| $table->geometry('positions'); | 相当于 GEOMETRY | | $table->geometry('positions'); | equivalent to GEOMETRY |
| $table->geometryCollection('positions'); | 相当于 GEOMETRYCOLLECTION | | $table->geometryCollection('positions'); | equivalent to GEOMETRYCOLLECTION |
| $table->increments('id'); | 递增的 ID (主键)相当于「UNSIGNED INTEGER」 | | $table->increments('id'); | Incrementing ID (primary key), equivalent to "UNSIGNED INTEGER" |
| $table->integer('votes'); | 相当于 INTEGER | | $table->integer('votes'); | equivalent to INTEGER |
| $table->ipAddress('visitor'); | 相当于 IP 地址 | | $table->ipAddress('visitor'); | equivalent to IP address |
| $table->json('options'); | 相当于 JSON | | $table->json('options'); | equivalent to JSON |
| $table->jsonb('options'); | 相当于 JSONB | | $table->jsonb('options'); | equivalent to JSONB |
| $table->lineString('positions'); | 相当于 LINESTRING | | $table->lineString('positions'); | equivalent to LINESTRING |
| $table->longText('description'); | 相当于 LONGTEXT | | $table->longText('description'); | equivalent to LONGTEXT |
| $table->macAddress('device'); | 相当于 MAC 地址 | | $table->macAddress('device'); | equivalent to MAC address |
| $table->mediumIncrements('id'); | 递增 ID (主键) 相当于「UNSIGNED MEDIUM INTEGER」 | | $table->mediumIncrements('id'); | Increment ID (primary key), equivalent to "UNSIGNED MEDIUM INTEGER" |
| $table->mediumInteger('votes'); | 相当于 MEDIUMINT | | $table->mediumInteger('votes'); | equivalent to MEDIUMINT |
| $table->mediumText('description'); | 相当于 MEDIUMTEXT | | $table->mediumText('description'); | equivalent to MEDIUMTEXT |
| $table->morphs('taggable'); | 相当于加入递增的 taggable_id 与字符串 taggable_type | | $table->morphs('taggable'); | equivalent to adding incremental taggable_id and string taggable_type |
| $table->multiLineString('positions'); | 相当于 MULTILINESTRING | | $table->multiLineString('positions'); | equivalent to MULTILINESTRING |
| $table->multiPoint('positions'); | 相当于 MULTIPOINT | | $table->multiPoint('positions'); | equivalent to MULTIPOINT |
| $table->multiPolygon('positions'); | 相当于 MULTIPOLYGON | | $table->multiPolygon('positions'); | equivalent to MULTIPOLYGON |
| $table->nullableMorphs('taggable'); | 相当于可空版本的 morphs() 字段 | | $table->nullableMorphs('taggable'); | equivalent to nullable version morphs() field |
| $table->nullableTimestamps(); | 相当于可空版本的 timestamps() 字段 | | $table->nullableTimestamps(); | equivalent to nullable version timestamps() field |
| $table->point('position'); | 相当于 POINT | | $table->point('position'); | equivalent to POINT |
| $table->polygon('positions'); | 相当于 POLYGON | | $table->polygon('positions'); | equivalent to POLYGON |
| $table->rememberToken(); | 相当于可空版本的 VARCHAR (100) 的 remember_token 字段 | | $table->rememberToken(); | equivalent to nullable version VARCHAR (100) of remember_token field |
| $table->smallIncrements('id'); | 递增 ID (主键) 相当于「UNSIGNED SMALL INTEGER」 | | $table->smallIncrements('id'); | Increment ID (primary key), equivalent to "UNSIGNED SMALL INTEGER" |
| $table->smallInteger('votes'); | 相当于 SMALLINT | | $table->smallInteger('votes'); | equivalent to SMALLINT |
| $table->softDeletes(); | 相当于为软删除添加一个可空的 deleted_at 字段 | | $table->softDeletes(); | equivalent to add a nullable for soft delete deleted_at field |
| $table->softDeletesTz(); | 相当于为软删除添加一个可空的 带时区的 deleted_at 字段 | | $table->softDeletesTz(); | equivalent to add a nullable for soft delete deleted_at field with time zone |
| $table->string('name', 100); | 相当于带长度的 VARCHAR | | $table->string('name', 100); | equivalent to with length VARCHAR |
| $table->text('description'); | 相当于 TEXT | | $table->text('description'); | equivalent to TEXT |
| $table->time('sunrise'); | 相当于 TIME | | $table->time('sunrise'); | equivalent to TIME |
| $table->timeTz('sunrise'); | 相当于带时区的 TIME | | $table->timeTz('sunrise'); | equivalent to with time zone of TIME |
| $table->timestamp('added_on'); | 相当于 TIMESTAMP | | $table->timestamp('added_on'); | equivalent to TIMESTAMP |
| $table->timestampTz('added_on'); | 相当于带时区的 TIMESTAMP | | $table->timestampTz('added_on'); | equivalent to with time zone TIMESTAMP |
| $table->timestamps(); | 相当于可空的 created_at 和 updated_at TIMESTAMP | | $table->timestamps(); | equivalent to nullable created_at and updated_at TIMESTAMP |
| $table->timestampsTz(); | 相当于可空且带时区的 created_at 和 updated_at TIMESTAMP | | $table->timestampsTz(); | equivalent to nullable with timezone created_at and updated_at TIMESTAMP |
| $table->tinyIncrements('id'); | 相当于自动递增 UNSIGNED TINYINT | | $table->tinyIncrements('id'); | equivalent to auto increment UNSIGNED TINYINT |
| $table->tinyInteger('votes'); | 相当于 TINYINT | | $table->tinyInteger('votes'); | equivalent to TINYINT |
| $table->unsignedBigInteger('votes'); | 相当于 Unsigned BIGINT | | $table->unsignedBigInteger('votes'); | equivalent to UNSIGNED BIGINT |
| $table->unsignedDecimal('amount', 8, 2); | 相当于带有精度和基数的 UNSIGNED DECIMAL | | $table->unsignedDecimal('amount', 8, 2); | equivalent to with precision and base UNSIGNED DECIMAL |
| $table->unsignedInteger('votes'); | 相当于 Unsigned INT | | $table->unsignedInteger('votes'); | equivalent to UNSIGNED INT |
| $table->unsignedMediumInteger('votes'); | 相当于 Unsigned MEDIUMINT | | $table->unsignedMediumInteger('votes'); | equivalent to UNSIGNED MEDIUMINT |
| $table->unsignedSmallInteger('votes'); | 相当于 Unsigned SMALLINT | | $table->unsignedSmallInteger('votes'); | equivalent to UNSIGNED SMALLINT |
| $table->unsignedTinyInteger('votes'); | 相当于 Unsigned TINYINT | | $table->unsignedTinyInteger('votes'); | equivalent to UNSIGNED TINYINT |
| $table->uuid('id'); | 相当于 UUID | | $table->uuid('id'); | equivalent to UUID |
| $table->year('birth_year'); | 相当于 YEAR | | $table->year('birth_year'); | equivalent to YEAR |
## 修改字段 ## Modify fields
### 先决条件 ### Prerequisites
在修改字段之前,请确保将 `doctrine/dbal` 依赖添加到 `composer.json` 文件中。Doctrine DBAL 库用于确定字段的当前状态, 并创建对该字段进行指定调整所需的 SQL 查询: Make sure to add the `doctrine/dbal` dependency to the `composer.json` file before modifying the fields. The Doctrine DBAL library is used to determine the current state of a field and create the SQL query required to make the specified adjustments to that field:
```bash ```bash
composer require doctrine/dbal composer require doctrine/dbal
``` ```
### 更新字段属性 ### Update field properties
`change` 方法可以将现有的字段类型修改为新的类型或修改其它属性。 `change` Methods can modify existing field types to new types or modify other properties.
```php ```php
<?php <?php
Schema::create('users', function (Blueprint $table) { Schema::create('users', function (Blueprint $table) {
// 将字段的长度修改为 50 // Modify the length of the field to 50
$table->string('name', 50)->change(); $table->string('name', 50)->change();
}); });
``` ```
或修改字段为 `可为空` Or modify the field to be `nullable`:
```php ```php
<?php <?php
Schema::table('users', function (Blueprint $table) { Schema::table('users', function (Blueprint $table) {
// 将字段的长度修改为 50 并允许为空 // Modify the length of the field to 50 and allow null
$table->string('name', 50)->nullable()->change(); $table->string('name', 50)->nullable()->change();
}); });
``` ```
> 只有下面的字段类型能被 "修改" bigInteger、 binary、 boolean、date、dateTime、dateTimeTz、decimal、integer、json、 longText、mediumText、smallInteger、string、text、time、 unsignedBigInteger、unsignedInteger and unsignedSmallInteger。 > Only the following field types can be "modified": bigInteger, binary, boolean, date, dateTime, dateTimeTz, decimal, integer, json, longText, mediumText, smallInteger, string, text, time, unsignedBigInteger, unsignedInteger and unsignedSmallInteger.
### 重命名字段 ### Rename field
可以通过 `renameColumn` 方法来重命名字段: Fields can be renamed via the `renameColumn` method:
```php ```php
<?php <?php
Schema::table('users', function (Blueprint $table) { Schema::table('users', function (Blueprint $table) {
// 将字段从 from 重命名为 to // Rename field from from to to
$table->renameColumn('from', 'to')->change(); $table->renameColumn('from', 'to')->change();
}); });
``` ```
> 当前不支持 enum 类型的字段重命名。 > Field renaming of type enum is not currently supported.
### 删除字段 ### Delete field
可以通过 `dropColumn` 方法来删除字段: Fields can be dropped via the `dropColumn` method:
```php ```php
<?php <?php
Schema::table('users', function (Blueprint $table) { Schema::table('users', function (Blueprint $table) {
// 删除 name 字段 // Remove the name field
$table->dropColumn('name'); $table->dropColumn('name');
// 删除多个字段 // Delete multiple fields
$table->dropColumn(['name', 'age']); $table->dropColumn(['name', 'age']);
}); });
``` ```
#### 可用的命令别名 #### Available command aliases
| 命令 | 描述 | Command | Description |
| --- | --- | | ---------------------------- | ---------------------------------------------- |
| $table->dropRememberToken(); | 删除 remember_token 字段。 | $table->dropRememberToken(); | Remove the remember_token field. |
| $table->dropSoftDeletes(); | 删除 deleted_at 字段。 | $table->dropSoftDeletes(); | Delete the deleted_at field. |
| $table->dropSoftDeletesTz(); | dropSoftDeletes() 方法的别名。 | $table->dropSoftDeletesTz(); | Alias for the dropSoftDeletes() method. |
| $table->dropTimestamps(); | 删除 created_at and updated_at 字段。 | $table->dropTimestamps(); | Delete the created_at and updated_at fields. |
| $table->dropTimestampsTz(); | dropTimestamps() 方法的别名。 | $table->dropTimestampsTz(); | Alias for the dropTimestamps() method. |
## 索引 ## Index
### 创建索引 ### Create index
### 唯一索引 ### Unique index
通过 `unique` 方法来创建一个唯一索引: Use the `unique` method to create a unique index:
```php ```php
<?php <?php
// 在定义时创建索引 // Create index at definition time
$table->string('name')->unique(); $table->string('name')->unique();
// 在定义完字段之后创建索引 // Create indexes after fields are defined
$table->unique('name'); $table->unique('name');
``` ```
#### 复合索引 #### Compound index
```php ```php
<?php <?php
// 创建一个复合索引 // Create a compound index
$table->index(['account_id', 'created_at'], 'index_account_id_and_created_at'); $table->index(['account_id', 'created_at'], 'index_account_id_and_created_at');
``` ```
#### 定义索引名称 #### Define index name
迁移程序会自动生成一个合理的索引名称,每个索引方法都接受一个可选的第二个参数来指定索引的名称: The migrator automatically generates a reasonable index name, and each index method accepts an optional second argument to specify the name of the index:
```php ```php
<?php <?php
// 定义唯一索引名称为 unique_name // Define a unique index name as unique_name
$table->unique('name', 'unique_name'); $table->unique('name', 'unique_name');
// 定义一个复合索引名称为 index_account_id_and_created_at // Define a composite index named index_account_id_and_created_at
$table->index(['account_id', 'created_at'], ''); $table->index(['account_id', 'created_at'], '');
``` ```
##### 可用的索引类型 ##### Available index types
| 命令 | 描述 | Command | Description |
| --- | --- | | ------------------------------------- | ----------------- |
| $table->primary('id'); | 添加主键 | $table->primary('id'); | Add primary key |
| $table->primary(['id', 'parent_id']); | 添加复合键 | $table->primary(['id', 'parent_id']); | Add composite key |
| $table->unique('email'); | 添加唯一索引 | $table->unique('email'); | Add unique index |
| $table->index('state'); | 添加普通索引 | $table->index('state'); | Add normal index |
| $table->spatialIndex('location'); | 添加空间索引 | $table->spatialIndex('location'); | Add spatial index |
### 重命名索引 ### Rename index
您可通过 `renameIndex` 方法重命名一个索引的名称: You can rename an index with the `renameIndex` method:
```php ```php
<?php <?php
@ -440,18 +440,18 @@ $table->index(['account_id', 'created_at'], '');
$table->renameIndex('from', 'to'); $table->renameIndex('from', 'to');
``` ```
### 删除索引 ### delete index
您可通过下面的方法来删除索引,默认情况下迁移程序会自动将数据库名称、索引的字段名及索引类型简单地连接在一起作为名称。举例如下: You can drop an index in the following way. By default, the migration program will automatically concatenate the database name, the field name of the index, and the index type as the name. Examples are as follows:
| 命令 | 描述 | Command | Description |
| --- | --- | | ------------------------------------------------------ | ----------------------------------------- |
| $table->dropPrimary('users_id_primary'); | 从 users 表中删除主键 | $table->dropPrimary('users_id_primary'); | Drop the primary key from the users table |
| $table->dropUnique('users_email_unique'); | 从 users 表中删除唯一索引 | $table->dropUnique('users_email_unique'); | Drop unique index from users table |
| $table->dropIndex('geo_state_index'); | 从 geo 表中删除基本索引 | $table->dropIndex('geo_state_index'); | Drop base index from geo table |
| $table->dropSpatialIndex('geo_location_spatialindex'); | 从 geo 表中删除空间索引 | $table->dropSpatialIndex('geo_location_spatialindex'); | Drop the spatial index from the geo table |
您也可以通过传递字段数组到 `dropIndex` 方法,迁移程序会根据表名、字段和键类型生成的索引名称: You can also pass an array of fields to the `dropIndex` method and the migrator will generate an index name based on the table name, field and key type:
```php ```php
<?php <?php
@ -461,9 +461,9 @@ Schema:table('users', function (Blueprint $table) {
}); });
``` ```
### 外键约束 ### foreign key constraints
我们还可以通过 `foreign`、`references`、`on` 方法创建数据库层的外键约束。比如我们让 `posts` 表定义一个引用 `users` 表的 `id` 字段的 `user_id` 字段: We can also create foreign key constraints at the database layer through the `foreign`, `references`, `on` methods. For example, let's let the `posts` table define a `user_id` field that references the `id` field of the `users` table:
```php ```php
Schema::table('posts', function (Blueprint $table) { Schema::table('posts', function (Blueprint $table) {
@ -473,7 +473,7 @@ Schema::table('posts', function (Blueprint $table) {
}); });
``` ```
还可以为 `on delete``on update` 属性指定所需的操作: You can also specify the desired action for the `on delete` and `on update` properties:
```php ```php
$table->foreign('user_id') $table->foreign('user_id')
@ -481,23 +481,23 @@ $table->foreign('user_id')
->onDelete('cascade'); ->onDelete('cascade');
``` ```
您可以通过 `dropForeign` 方法来删除外键。外键约束采用的命名方式与索引相同,然后加上 `_foreign` 后缀: You can drop foreign keys with the `dropForeign` method. Foreign key constraints are named in the same way as indexes, followed by a `_foreign` suffix:
```php ```php
$table->dropForeign('posts_user_id_foreign'); $table->dropForeign('posts_user_id_foreign');
``` ```
或者传递一个字段数组,让迁移程序按照约定的规则生成名称: Or pass an array of fields and have the migrator generate the names according to the agreed-upon rules:
```php ```php
$table->dropForeign(['user_id'']); $table->dropForeign(['user_id'']);
``` ```
您可以在迁移文件中使用以下方法来开启或关闭外键约束: You can turn foreign key constraints on or off using the following methods in the migration file:
```php ```php
// 开启外键约束 // Enable foreign key constraints
Schema::enableForeignKeyConstraints(); Schema::enableForeignKeyConstraints();
// 禁用外键约束 // Disable foreign key constraints
Schema::disableForeignKeyConstraints(); Schema::disableForeignKeyConstraints();
``` ```

View File

@ -1,8 +1,8 @@
# 模型缓存 # Model cache
在高频场景下我们会频繁的查询数据库虽然有主键加持但也会影响到数据库性能。这种kv查询方式我们可以很方便的使用 `模型缓存` 来减缓数据库压力。本模块实现了自动缓存,删除和修改模型时,自动删除缓存。累加、减操作时,直接操作缓存进行对应累加、减。 In high-frequency scenarios, we will frequently query the database. Although there is a primary key blessing, it will also affect the database performance. With this kv query method, we can easily use `model cache` to reduce database pressure. This module implements automatic caching. When deleting and modifying the model, the cache is automatically deleted. When accumulating and subtracting, directly operate the cache to perform the corresponding accumulation and subtraction.
> 模型缓存暂支持 `Redis`存储,其他存储引擎会慢慢补充。 > Model cache temporarily supports `Redis` storage, other storage engines will be added gradually.
## Installation ## Installation
@ -10,18 +10,18 @@
composer require hyperf/model-cache composer require hyperf/model-cache
``` ```
## 配置 ## configure
模型缓存的配置在 `databases` 中。示例如下 Model caching is configured in `databases`. Examples are as follows
| 配置 | 类型 | 默认值 | 备注 | | Configuration | Type | Default | Remarks |
|:---------------:|:------:|:-----------------------------------------------------:|:-----------------------------------:| |:---------------:|:------:|:------------------------------------------------------:|:----------------------------------------------------------------:|
| handler | string | Hyperf\DbConnection\Cache\Handler\RedisHandler::class | 无 | | handler | string | Hyperf\DbConnection\Cache\Handler\RedisHandler::class | none |
| cache_key | string | `mc:%s:m:%s:%s:%s` | `mc:缓存前缀:m:表名:主键KEY:主键值` | | cache_key | string | `mc:%s:m:%s:%s:%s` | `mc:cache prefix:m:table name:primary key KEY:primary key value` |
| prefix | string | db connection name | 缓存前缀 | | prefix | string | db connection name | cache prefix |
| ttl | int | 3600 | 超时时间 | | ttl | int | 3600 | timeout |
| empty_model_ttl | int | 60 | 查询不到数据时的超时时间 | | empty_model_ttl | int | 60 | Timeout when no data is queried |
| load_script | bool | true | Redis引擎下 是否使用evalSha代替eval | | load_script | bool | true | Whether to use evalSha instead of eval under the Redis engine |
```php ```php
<?php <?php
@ -56,9 +56,9 @@ return [
]; ];
``` ```
## 使用 ## use
模型缓存的使用十分简单只需要在对应Model中实现 `Hyperf\ModelCache\CacheableInterface` 接口,当然,框架已经提供了对应实现,只需要引入 `Hyperf\ModelCache\Cacheable` Trait 即可。 The use of the model cache is very simple. You only need to implement the `Hyperf\ModelCache\CacheableInterface` interface in the corresponding Model. Of course, the framework has already provided the corresponding implementation, you only need to introduce the `Hyperf\ModelCache\Cacheable` Trait.
```php ```php
<?php <?php
@ -99,15 +99,15 @@ class User extends Model implements CacheableInterface
protected $casts = ['id' => 'integer', 'gender' => 'integer']; protected $casts = ['id' => 'integer', 'gender' => 'integer'];
} }
// 查询单个缓存 // Query a single cache
$model = User::findFromCache($id); $model = User::findFromCache($id);
// 批量查询缓存,返回 Hyperf\Database\Model\Collection // Batch query cache, return Hyperf\Database\Model\Collection
$models = User::findManyFromCache($ids); $models = User::findManyFromCache($ids);
``` ```
对应Redis数据如下其中 `HF-DATA:DEFAULT` 作为占位符存在于 `HASH` 中,*所以用户不要使用 `HF-DATA` 作为数据库字段*。 The corresponding Redis data is as follows, where `HF-DATA:DEFAULT` exists as a placeholder in `HASH`, *so users do not use `HF-DATA` as a database field*.
``` ```
127.0.0.1:6379> hgetall "mc:default:m:user:id:1" 127.0.0.1:6379> hgetall "mc:default:m:user:id:1"
1) "id" 1) "id"
@ -124,5 +124,5 @@ $models = User::findManyFromCache($ids);
12) "DEFAULT" 12) "DEFAULT"
``` ```
另外一点就是,缓存更新机制,框架内实现了对应的 `Hyperf\ModelCache\Listener\DeleteCacheListener` 监听器,每当数据修改,会主动删除缓存。 Another point is that the cache update mechanism implements the corresponding `Hyperf\ModelCache\Listener\DeleteCacheListener` listener in the framework. Whenever the data is modified, the cache will be actively deleted.
如果用户不想由框架来删除缓存,可以主动覆写 `deleteCache` 方法,然后由自己实现对应监听即可。 If the user does not want the framework to delete the cache, he can actively override the `deleteCache` method, and then implement the corresponding monitoring by yourself.

554
docs/en/db/mutators.md Normal file
View File

@ -0,0 +1,554 @@
# Modifier
> This document borrows heavily from [LearnKu](https://learnku.com) Many thanks to LearnKu for contributing to the PHP community.
Accessors and modifiers allow you to format model property values when you get or set certain property values on a model instance.
## Accessors & Modifiers
### Define an accessor
To define an accessor, you need to create a `getFooAttribute` method on the model, and the `Foo` field to be accessed needs to be named in "camel case". In this example, we will define an accessor for the `first_name` property. This accessor is automatically called when the model tries to get the `first_name` property:
```php
<?php
namespace App;
use Hyperf\DbConnection\Model\Model;
class User extends Model
{
/**
* Get the user's name.
*
* @param string $value
* @return string
*/
public function getFirstNameAttribute($value)
{
return ucfirst($value);
}
}
```
As you can see, the raw value of the field is passed into the accessor, allowing you to process it and return the result. To get the modified value, you can access the `first_name` property on the model instance:
```php
$user = App\User::find(1);
$firstName = $user->first_name;
```
Of course, you can also pass an existing property value and use an accessor to return a new computed value:
```php
namespace App;
use Hyperf\DbConnection\Model\Model;
class User extends Model
{
/**
* Get the user's name.
*
* @return string
*/
public function getFullNameAttribute()
{
return "{$this->first_name} {$this->last_name}";
}
}
```
### Define a modifier
To define a modifier, define the `setFooAttribute` method on the model. The `Foo` fields to be accessed are named using "camel case". Let's define a modifier for the `first_name` property again. This modifier will be called automatically when we try to set the value of the `first_name` property on the schema:
```php
<?php
namespace App;
use Hyperf\DbConnection\Model\Model;
class User extends Model
{
/**
* 设置用户的姓名.
*
* @param string $value
* @return void
*/
public function setFirstNameAttribute($value)
{
$this->attributes['first_name'] = strtolower($value);
}
}
```
Modifiers take the value of an attribute that has already been set, allowing you to modify and set its value to the `$attributes` property inside the model. For example, if we try to set the value of the `first_name` property to `Sally`:
```php
$user = App\User::find(1);
$user->first_name = 'Sally';
```
In this example, the `setFirstNameAttribute` method is called with the value `Sally` as a parameter. The modifier then applies the `strtolower` function and sets the result of the processing to the internal `$attributes` array.
## date converter
By default, the model converts the `created_at` and `updated_at` fields to `Carbon` instances, which inherit the `PHP` native `DateTime` class and provide various useful methods. You can add other date properties by setting the model's `$dates` property:
```php
<?php
namespace App;
use Hyperf\DbConnection\Model\Model;
class User extends Model
{
/**
* Properties that should be converted to date format.
*
* @var array
*/
protected $dates = [
'seen_at',
];
}
```
> Tip: You can disable the default created_at and updated_at timestamps by setting the model's public $timestamps value to false.
When a field is in date format, you can set the value to a `UNIX` timestamp, a datetime `(Y-m-d)` string, or a `DateTime` / `Carbon` instance. The date value will be properly formatted and saved to your database:
As mentioned above, when the fetched property is contained in the `$dates` property, it is automatically converted to a `Carbon` instance, allowing you to use any `Carbon` method on the property:
```php
$user = App\User::find(1);
return $user->deleted_at->getTimestamp();
```
### Time format
Timestamps will all be formatted as `Y-m-d H:i:s`. If you need a custom timestamp format, set the `$dateFormat` property in the model. This property determines how the date property will be stored in the database, and the format when the model is serialized into an array or `JSON`:
```php
<?php
namespace App;
use Hyperf\DbConnection\Model\Model;
class Flight extends Model
{
/**
* This property should be cast to the native type.
*
* @var string
*/
protected $dateFormat = 'U';
}
```
## Attribute type conversion
The `$casts` property on the model provides a convenience method to cast properties to common data types. The `$casts` property should be an array whose keys are the names of the properties to be cast, and the values are the data types you wish to cast.
The supported data types are: `integer`, `real`, `float`, `double`, `decimal:<digits>`, `string`, `boolean`, `object`, `array`, `collection` , `date`, `datetime` and `timestamp`. When converting to `decimal` type, you need to define the number of decimal places, such as: `decimal:2`.
As an example, let's convert the `is_admin` property stored in the database as an integer ( `0` or `1` ) to a boolean value:
```php
<?php
namespace App;
use Hyperf\DbConnection\Model\Model;
class User extends Model
{
/**
* The attributes that should be cast.
*
* @var array
*/
protected $casts = [
'is_admin' => 'boolean',
];
}
```
Now when you access the `is_admin` property, although the value stored in the database is an integer type, the return value is always converted to a boolean type:
```php
$user = App\User::find(1);
if ($user->is_admin) {
//
}
```
### Custom type conversion
Models have several common type conversions built into them. However, users occasionally need to convert data into custom types. Now, this requirement can be accomplished by defining a class that implements the `CastsAttributes` interface
Classes that implement this interface must define a `get` and `set` method in advance. The `get` method is responsible for converting the raw data obtained from the database into the corresponding type, while the `set` method converts the data into the corresponding database type for storing in the database. For example, let's reimplement the built-in `json` type conversion as a custom type conversion:
```php
<?php
namespace App\Casts;
use Hyperf\Contract\CastsAttributes;
class Json implements CastsAttributes
{
/**
* Convert the extracted data
*/
public function get($model, $key, $value, $attributes)
{
return json_decode($value, true);
}
/**
* Convert to the value to be stored
*/
public function set($model, $key, $value, $attributes)
{
return json_encode($value);
}
}
```
Once a custom type cast is defined, it can be attached to a model property using its class name:
```php
<?php
namespace App;
use App\Casts\Json;
use Hyperf\DbConnection\Model\Model;
class User extends Model
{
/**
* Properties that should be typecast
*
* @var array
*/
protected $casts = [
'options' => Json::class,
];
}
```
#### Value object type conversion
Not only can you convert data to native data types, but you can also convert data to objects. The two custom type conversions are defined in a very similar way. But the `set` method in the custom conversion class that converts the data to an object needs to return an array of key-value pairs, which are used to set the original, storable value into the corresponding model.
As an example, define a custom type conversion class to convert multiple model property values into a single `Address` value object, assuming that the `Address` object has two public properties `lineOne` and `lineTwo`:
```php
<?php
namespace App\Casts;
use App\Address;
use Hyperf\Contract\CastsAttributes;
class AddressCaster implements CastsAttributes
{
/**
* Convert the extracted data
*/
public function get($model, $key, $value, $attributes): Address
{
return new Address(
$attributes['address_line_one'],
$attributes['address_line_two']
);
}
/**
* Convert to the value to be stored
*/
public function set($model, $key, $value, $attributes)
{
return [
'address_line_one' => $value->lineOne,
'address_line_two' => $value->lineTwo,
];
}
}
```
After the value object type conversion, any data changes to the value object will be automatically synced back to the model before the model is saved:
```php
<?php
$user = App\User::find(1);
$user->address->lineOne = 'Updated Address Value';
$user->address->lineTwo = '#10000';
$user->save();
var_dump($user->getAttributes());
//[
// 'address_line_one' => 'Updated Address Value',
// 'address_line_two' => '#10000'
//];
```
**The implementation here is different from Laravel, if the following usage occurs, please pay special attention**
```php
$user = App\User::find(1);
var_dump($user->getAttributes());
//[
// 'address_line_one' => 'Address Value',
// 'address_line_two' => '#10000'
//];
$user->address->lineOne = 'Updated Address Value';
$user->address->lineTwo = '#20000';
// After directly modifying the field of address, it cannot take effect in attributes immediately, but you can get the modified data directly through $user->address.
var_dump($user->getAttributes());
//[
// 'address_line_one' => 'Address Value',
// 'address_line_two' => '#10000'
//];
// When we save the data or delete the data, the attributes will be changed to the modified data.
$user->save();
var_dump($user->getAttributes());
//[
// 'address_line_one' => 'Updated Address Value',
// 'address_line_two' => '#20000'
//];
```
If after modifying `address`, you don't want to save it or get the data of `address_line_one` through `address->lineOne`, you can also use the following method
```php
$user = App\User::find(1);
$user->address->lineOne = 'Updated Address Value';
$user->syncAttributes();
var_dump($user->getAttributes());
```
Of course, if you still need to modify the function of `attributes` synchronously after modifying the corresponding `value`, you can try the following methods. First, we implement a `UserInfo` and inherit `CastsValue`.
```php
namespace App\Caster;
use Hyperf\Database\Model\CastsValue;
/**
* @property string $name
* @property int $gender
*/
class UserInfo extends CastsValue
{
}
```
Then implement the corresponding `UserInfoCaster`
```php
<?php
declare(strict_types=1);
namespace App\Caster;
use Hyperf\Contract\CastsAttributes;
use Hyperf\Utils\Arr;
class UserInfoCaster implements CastsAttributes
{
public function get($model, string $key, $value, array $attributes): UserInfo
{
return new UserInfo($model, Arr::only($attributes, ['name', 'gender']));
}
public function set($model, string $key, $value, array $attributes)
{
return [
'name' => $value->name,
'gender' => $value->gender,
];
}
}
```
When we modify UserInfo in the following way, we can synchronize the data modified to attributes.
```php
/** @var User $user */
$user = User::query()->find(100);
$user->userInfo->name = 'John1';
var_dump($user->getAttributes()); // ['name' => 'John1']
```
#### Inbound type conversion
Sometimes, you may only need to typecast property values written to the model without doing any processing on property values fetched from the model. A typical example of inbound type conversion is "hashing". Inbound type conversion classes need to implement the `CastsInboundAttributes` interface, and only need to implement the `set` method.
```php
<?php
namespace App\Casts;
use Hyperf\Contract\CastsInboundAttributes;
class Hash implements CastsInboundAttributes
{
/**
* hash algorithm
*
* @var string
*/
protected $algorithm;
/**
* Create a new instance of the typecast class
*/
public function __construct($algorithm = 'md5')
{
$this->algorithm = $algorithm;
}
/**
* Convert to the value to be stored
*/
public function set($model, $key, $value, $attributes)
{
return hash($this->algorithm, $value);
}
}
```
#### Type conversion parameters
When attaching a custom cast to a model, you can specify the cast parameter passed in. To pass in type conversion parameters, use `:` to separate the parameters from the class name, and use commas to separate multiple parameters. These parameters will be passed to the constructor of the type conversion class:
```php
<?php
namespace App;
use App\Casts\Json;
use Hyperf\DbConnection\Model\Model;
class User extends Model
{
/**
* Properties that should be typecast
*
* @var array
*/
protected $casts = [
'secret' => Hash::class.':sha256',
];
}
```
### Array & `JSON` conversion
`array` type conversions are very useful when you store serialized `JSON` data in the database. For example: if your database has a `JSON` or `TEXT` field type that is serialized to `JSON`, and you add an `array` type conversion to the model, it will be automatically converted to ` when you access it PHP` array:
```php
<?php
namespace App;
use Hyperf\DbConnection\Model\Model;
class User extends Model
{
/**
* Properties that should be typecast
*
* @var array
*/
protected $casts = [
'options' => 'array',
];
}
```
一Once the conversion is defined, it will be automatically deserialized from the `JSON` type to a `PHP` array when you access the `options` property. When you set the value of the `options` property, the given array is also automatically serialized to `JSON` type storage:
```php
$user = App\User::find(1);
$options = $user->options;
$options['key'] = 'value';
$user->options = $options;
$user->save();
```
### Date type conversion
When using the `date` or `datetime` attributes, you can specify the format of the date. This format will be used when models are serialized as arrays or `JSON`:
```php
<?php
namespace App;
use Hyperf\DbConnection\Model\Model;
class User extends Model
{
/**
* Properties that should be typecast
*
* @var array
*/
protected $casts = [
'created_at' => 'datetime:Y-m-d',
];
}
```
### Query-time type conversion
There are times when you need to typecast specific properties during query execution, such as when you need to fetch data from a database table. As an example, consider the following query:
```php
use App\Post;
use App\User;
$users = User::select([
'users.*',
'last_posted_at' => Post::selectRaw('MAX(created_at)')
->whereColumn('user_id', 'users.id')
])->get();
```
In the result set obtained by this query, the `last_posted_at` attribute will be a string. It would be more convenient if we did a `date` type conversion when executing the query. You can do this by using the `withCasts` method:
```php
$users = User::select([
'users.*',
'last_posted_at' => Post::selectRaw('MAX(created_at)')
->whereColumn('user_id', 'users.id')
])->withCasts([
'last_posted_at' => 'date'
])->get();
```

View File

@ -1,28 +1,28 @@
# 查询分页 # Query pagination
在使用 [hyperf/database](https://github.com/hyperf-cloud/database) 来查询数据时,可以很方便的通过与 [hyperf/paginator](https://github.com/hyperf-cloud/paginator) 组件配合便捷地对查询结果进行分页。 When using [hyperf/database](https://github.com/hyperf-cloud/database) to query data, it is very convenient to use [hyperf/paginator](https://github.com/hyperf-cloud /paginator) component to easily paginate query results.
# 使用方法 # Instructions
在您通过 [查询构造器](en/db/querybuilder.md) 或 [模型](en/db/model.md) 查询数据时,可以通过 `paginate` 方法来处理分页,该方法会自动根据用户正在查看的页面来设置限制和偏移量,默认情况下,通过当前 HTTP 请求所带的 `page` 参数的值来检测当前的页数: When you query data through [Query Builder](en/db/querybuilder.md) or [Model](en/db/model.md), pagination can be handled through the `paginate` method, which automatically The page being viewed is used to set the limit and offset. By default, the current number of pages is detected by the value of the `page` parameter carried by the current HTTP request:
> 由于 Hyperf 当前并不支持视图,所以分页组件尚未支持对视图的渲染,直接返回分页结果默认会以 application/json 格式输出。 > Since Hyperf does not currently support views, the paging component does not yet support rendering of views, and the paging results returned directly will be output in application/json format by default.
## 查询构造器分页 ## query builder pagination
```php ```php
<?php <?php
// 展示应用中的所有用户,每页显示 10 条数据 // Show all users in the app, 10 pieces of data per page
return Db::table('users')->paginate(10); return Db::table('users')->paginate(10);
``` ```
## 模型分页 ## Model pagination
您可以直接通过静态方法调用 `paginate` 方法来进行分页: You can do pagination by calling the `paginate` method directly from a static method:
```php ```php
<?php <?php
// 展示应用中的所有用户,每页显示 10 条数据 // Show all users in the app, 10 pieces of data per page
return User::paginate(10); return User::paginate(10);
``` ```
@ -30,10 +30,10 @@ return User::paginate(10);
```php ```php
<?php <?php
// 展示应用中的所有用户,每页显示 10 条数据 // Show all users in the app, 10 pieces of data per page
return User::where('gender', 1)->paginate(10); return User::where('gender', 1)->paginate(10);
``` ```
## 分页器实例方法 ## Paginator instance methods
这里仅说明分页器在数据库查询上的使用方法,更多关于分页器的细节可阅读 [分页](en/paginator.md) 章节。 Only the usage of the paginator in database queries is described here. For more details about the paginator, please read the [Pagination](en/paginator.md) chapter.

View File

@ -1,15 +1,15 @@
# 查询构造器 # Query builder
## 简介 ## Introduction
Hyperf 的数据库查询构造器为创建和运行数据库查询提供了一个方便的接口。它可用于执行应用程序中大部分数据库操作,且可在所有支持的数据库系统上运行。 Hyperf's database query builder provides a convenient interface for creating and running database queries. It can be used to perform most database operations in an application and runs on all supported database systems.
Hyperf 的查询构造器使用 PDO 参数绑定来保护您的应用程序免受 SQL 注入攻击。因此没有必要清理作为绑定传递的字符串。 Hyperf's query builder uses PDO parameter binding to protect your application from SQL injection attacks. So there is no need to sanitize strings passed as bindings.
这里只提供一部分常用的教程,具体教程可以到 Laravel 官网查看。 Only some commonly used tutorials are provided here, and specific tutorials can be viewed on the Laravel official website.
[Laravel Query Builder](https://laravel.com/docs/5.8/queries) [Laravel Query Builder](https://laravel.com/docs/5.8/queries)
## 获取结果 ## Get results
```php ```php
use Hyperf\DbConnection\Db; use Hyperf\DbConnection\Db;
@ -19,7 +19,7 @@ $users = Db::table('user')->get();
$users = Db::table('user')->select('name', 'gender as user_gender')->get(); $users = Db::table('user')->select('name', 'gender as user_gender')->get();
``` ```
`Db::select()` 方法会返回一个array`get` 方法会返回 `Hyperf\Utils\Collection`。其中元素是 `stdClass`,所以可以通过以下代码返回各个元素的数据 The `Db::select()` method returns an array, and the `get` method returns `Hyperf\Utils\Collection`. The element is `stdClass`, so the data of each element can be returned by the following code
```php ```php
<?php <?php
@ -29,9 +29,9 @@ foreach ($users as $user) {
} }
``` ```
### 将结果转为数组格式 ### Convert the result to array format
在某些场景下,您可能会希望查询出来的结果内采用 `数组(Array)` 而不是 `stdClass` 对象结构时,而 `Eloquent` 又去除了通过配置的形式配置默认的 `FetchMode`,那么此时可以通过监听器来监听 `Hyperf\Database\Events\StatementPrepared` 事件来变更该配置: In some scenarios, you may want to use `Array` instead of `stdClass` object structure in the query result, and `Eloquent` removes the default `FetchMode` configured by configuration, then At this point, you can change the configuration by listening to the `Hyperf\Database\Events\StatementPrepared` event through the listener:
```php ```php
<?php <?php
@ -65,9 +65,9 @@ class FetchModeListener implements ListenerInterface
} }
``` ```
### 获取一列的值 ### Get the value of a column
如果你想获取包含单列值的集合,则可以使用 `pluck` 方法。在下面的例子中,我们将获取角色表中标题的集合: If you want to get a collection containing a single column of values, you can use the `pluck` method. In the following example, we will get a collection of titles in the roles table:
```php ```php
<?php <?php
@ -81,7 +81,7 @@ foreach ($names as $name) {
``` ```
你还可以在返回的集合中指定字段的自定义键值: You can also specify custom keys for fields in the returned collection:
```php ```php
<?php <?php
@ -95,9 +95,9 @@ foreach ($roles as $name => $title) {
``` ```
### 分块结果 ### Chunked results
如果你需要处理上千条数据库记录,你可以考虑使用 `chunk` 方法。该方法一次获取结果集的一小块,并将其传递给 `闭包` 函数进行处理。该方法在 `Command` 编写数千条处理数据的时候非常有用。例如,我们可以将全部 user 表数据切割成一次处理 100 条记录的一小块: If you need to process thousands of database records, you might consider using the `chunk` method. This method takes a small chunk of the result set at a time and passes it to the `closure` function for processing. This method is very useful when `Command` is writing thousands of pieces of processing data. For example, we can cut the entire user table data into small pieces that process 100 records at a time:
```php ```php
<?php <?php
@ -110,7 +110,7 @@ Db::table('user')->orderBy('id')->chunk(100, function ($users) {
}); });
``` ```
你可以通过在 闭包 中返回 `false` 来终止继续获取分块结果: You can stop fetching chunked results by returning `false` in the closure:
```php ```php
use Hyperf\DbConnection\Db; use Hyperf\DbConnection\Db;
@ -121,7 +121,7 @@ Db::table('user')->orderBy('id')->chunk(100, function ($users) {
}); });
``` ```
如果要在分块结果时更新数据库记录,则块结果可能会和预计的返回结果不一致。 因此,在分块更新记录时,最好使用 chunkById 方法。 此方法将根据记录的主键自动对结果进行分页: If you are updating database records while chunking the results, the chunked results may not be the same as expected. Therefore, when updating records in chunks, it is better to use the chunkById method. This method will automatically paginate the results based on the record's primary key:
```php ```php
use Hyperf\DbConnection\Db; use Hyperf\DbConnection\Db;
@ -135,11 +135,11 @@ Db::table('user')->where('gender', 1)->chunkById(100, function ($users) {
}); });
``` ```
> 在块的回调里面更新或删除记录时,对主键或外键的任何更改都可能影响块查询。 这可能会导致记录没有包含在分块结果中。 > Any changes to the primary or foreign keys may affect the block query while updating or deleting records inside the block's callback. This may result in records not being included in the chunked result.
### 聚合查询 ### Aggregate query
框架还提供了聚合类方法,例如 `count`, `max`, `min`, `avg`, `sum` The framework also provides aggregate class methods such as `count`, `max`, `min`, `avg`, `sum`.
```php ```php
use Hyperf\DbConnection\Db; use Hyperf\DbConnection\Db;
@ -147,9 +147,9 @@ use Hyperf\DbConnection\Db;
$count = Db::table('user')->count(); $count = Db::table('user')->count();
``` ```
#### 判断记录是否存在 #### Determine if the record exists
除了通过 `count` 方法可以确定查询条件的结果是否存在之外,还可以使用 `exists``doesntExist` 方法: In addition to using the `count` method to determine whether the result of a query condition exists, you can also use the `exists` and `doesntExist` methods:
```php ```php
return Db::table('orders')->where('finalized', 1)->exists(); return Db::table('orders')->where('finalized', 1)->exists();
@ -157,23 +157,23 @@ return Db::table('orders')->where('finalized', 1)->exists();
return Db::table('orders')->where('finalized', 1)->doesntExist(); return Db::table('orders')->where('finalized', 1)->doesntExist();
``` ```
## 查询 ## Inquire
### 指定一个 Select 语句 ### Specify a Select statement
当然你可能并不总是希望从数据库表中获取所有列。使用 select 方法,你可以自定义一个 select 查询语句来查询指定的字段: Of course you may not always want to get all the columns from the database table. Using the select method, you can customize a select query statement to query the specified fields:
```php ```php
$users = Db::table('user')->select('name', 'email as user_email')->get(); $users = Db::table('user')->select('name', 'email as user_email')->get();
``` ```
`distinct` 方法会强制让查询返回的结果不重复: The `distinct` method forces the query to return unique results:
```php ```php
$users = Db::table('user')->distinct()->get(); $users = Db::table('user')->distinct()->get();
``` ```
如果你已经有了一个查询构造器实例,并且希望在现有的查询语句中加入一个字段,那么你可以使用 addSelect 方法: If you already have a query builder instance and want to add a field to the existing query, you can use the addSelect method:
```php ```php
$query = Db::table('users')->select('name'); $query = Db::table('users')->select('name');
@ -181,9 +181,9 @@ $query = Db::table('users')->select('name');
$users = $query->addSelect('age')->get(); $users = $query->addSelect('age')->get();
``` ```
## 原始表达式 ## Original expression
有时你需要在查询中使用原始表达式,例如实现 `COUNT(0) AS count`,这就需要用到`raw`方法。 Sometimes you need to use raw expressions in a query, for example to implement `COUNT(0) AS count`, which requires the use of the `raw` method.
```php ```php
use Hyperf\DbConnection\Db; use Hyperf\DbConnection\Db;
@ -191,11 +191,11 @@ use Hyperf\DbConnection\Db;
$res = Db::table('user')->select('gender', Db::raw('COUNT(0) AS `count`'))->groupBy('gender')->get(); $res = Db::table('user')->select('gender', Db::raw('COUNT(0) AS `count`'))->groupBy('gender')->get();
``` ```
### 原生方法 ### Native method
可以使用以下方法代替 `Db::raw`,将原生表达式插入查询的各个部分。 The following methods can be used instead of `Db::raw` to insert raw expressions into various parts of the query.
`selectRaw` 方法可以代替 `select(Db::raw(...))`。该方法的第二个参数是可选项,值是一个绑定参数的数组: The `selectRaw` method can be used in place of `select(Db::raw(...))`. The second parameter of this method is optional, and the value is an array of bound parameters:
```php ```php
$orders = Db::table('order') $orders = Db::table('order')
@ -203,7 +203,7 @@ $orders = Db::table('order')
->get(); ->get();
``` ```
`whereRaw``orWhereRaw` 方法将原生的 `where` 注入到你的查询中。这两个方法的第二个参数还是可选项,值还是绑定参数的数组: The `whereRaw` and `orWhereRaw` methods inject native `where` into your query. The second parameter of these two methods is still optional, and the value is still an array of bound parameters:
```php ```php
$orders = Db::table('order') $orders = Db::table('order')
@ -211,7 +211,7 @@ $orders = Db::table('order')
->get(); ->get();
``` ```
`havingRaw``orHavingRaw` 方法可以用于将原生字符串设置为 `having` 语句的值: The `havingRaw` and `orHavingRaw` methods can be used to set a raw string as the value of a `having` statement:
```php ```php
$orders = Db::table('order') $orders = Db::table('order')
@ -221,7 +221,7 @@ $orders = Db::table('order')
->get(); ->get();
``` ```
`orderByRaw` 方法可用于将原生字符串设置为 `order by` 子句的值: The `orderByRaw` method can be used to set a raw string as the value of the `order by` clause:
```php ```php
$orders = Db::table('order') $orders = Db::table('order')
@ -229,11 +229,11 @@ $orders = Db::table('order')
->get(); ->get();
``` ```
## 表连接 ## Join table
### Inner Join Clause ### Inner Join Clause
查询构造器也可以编写 `join` 方法。若要执行基本的`「内链接」`,你可以在查询构造器实例上使用 `join` 方法。传递给 `join` 方法的第一个参数是你需要连接的表的名称,而其他参数则使用指定连接的字段约束。你还可以在单个查询中连接多个数据表: Query builders can also write `join` methods. To perform basic `"inner join"`, you can use the `join` method on the query builder instance. The first argument passed to the `join` method is the name of the table you want to join, while the other arguments use the field constraints that specify the join. You can also join multiple tables in a single query:
```php ```php
$users = Db::table('users') $users = Db::table('users')
@ -245,7 +245,7 @@ $users = Db::table('users')
### Left Join ### Left Join
如果你想使用`「左连接」`或者`「右连接」`代替`「内连接」`,可以使用 `leftJoin` 或者 `rightJoin` 方法。这两个方法与 `join` 方法用法相同: If you want to use `"left join"` or `"right join"` instead of `"inner join"`, use the `leftJoin` or `rightJoin` methods. These two methods are used in the same way as the `join` method:
```php ```php
$users = Db::table('users') $users = Db::table('users')
@ -256,9 +256,9 @@ $users = Db::table('users')
->get(); ->get();
``` ```
### Cross Join 语句 ### Cross Join Statement
使用 `crossJoin` 方法和你想要连接的表名做`「交叉连接」`。交叉连接在第一个表和被连接的表之间会生成笛卡尔积: Use the `crossJoin` method to do a `"cross join"` with the name of the table you want to join. A cross join produces a Cartesian product between the first table and the joined tables:
```php ```php
$users = Db::table('sizes') $users = Db::table('sizes')
@ -266,9 +266,9 @@ $users = Db::table('sizes')
->get(); ->get();
``` ```
### 高级 Join 语句 ### Advanced Join Statement
你可以指定更高级的 `join` 语句。比如传递一个 `闭包` 作为 `join` 方法的第二个参数。此 `闭包` 接收一个 `JoinClause` 对象,从而指定 `join` 语句中指定的约束: You can specify more advanced `join` statements. For example passing a `closure` as the second parameter of the `join` method. This `closure` accepts a `JoinClause` object, specifying the constraints specified in the `join` statement:
```php ```php
Db::table('users') Db::table('users')
@ -278,7 +278,7 @@ Db::table('users')
->get(); ->get();
``` ```
如果你想要在连接上使用`「where」`风格的语句,你可以在连接上使用 `where``orWhere` 方法。这些方法会将列和值进行比较,而不是列和列进行比较: If you want to use `"where"` style statements on the join, you can use the `where` and `orWhere` methods on the join. These methods compare columns to values instead of columns to columns:
```php ```php
Db::table('users') Db::table('users')
@ -289,9 +289,9 @@ Db::table('users')
->get(); ->get();
``` ```
### 子连接查询 ### Subjoin query
你可以使用 `joinSub``leftJoinSub``rightJoinSub` 方法关联一个查询作为子查询。他们每一种方法都会接收三个参数:子查询,表别名和定义关联字段的闭包: You can use the `joinSub`, leftJoinSub` and `rightJoinSub` methods to join a query as a subquery. Each of their methods takes three parameters: a subquery, a table alias, and a closure that defines the associated fields:
```php ```php
$latestPosts = Db::table('posts') $latestPosts = Db::table('posts')
@ -305,9 +305,9 @@ $users = Db::table('users')
})->get(); })->get();
``` ```
## 联合查询 ## Combined query
查询构造器还提供了将两个查询 「联合」 的快捷方式。比如,你可以先创建一个查询,然后使用 `union` 方法将其和第二个查询进行联合: The query builder also provides a shortcut for "joining" two queries. For example, you can create a query first, then use the `union` method to union it with the second query:
```php ```php
$first = Db::table('users')->whereNull('first_name'); $first = Db::table('users')->whereNull('first_name');
@ -318,25 +318,25 @@ $users = Db::table('users')
->get(); ->get();
``` ```
## Where 语句 ## Where statement
### 简单的 Where 语句 ### Simple Where Statement
在构造 `where` 查询实例的中,你可以使用 `where` 方法。调用 `where` 最基本的方式是需要传递三个参数:第一个参数是列名,第二个参数是任意一个数据库系统支持的运算符,第三个是该列要比较的值。 In constructing a `where` query instance, you can use the `where` method. The most basic way to call `where` is to pass three parameters: the first parameter is the column name, the second parameter is any operator supported by the database system, and the third parameter is the value to be compared for the column.
例如,下面是一个要验证 gender 字段的值等于 1 的查询: For example, here is a query to verify that the value of the gender field is equal to 1:
```php ```php
$users = Db::table('user')->where('gender', '=', 1)->get(); $users = Db::table('user')->where('gender', '=', 1)->get();
``` ```
为了方便,如果你只是简单比较列值和给定数值是否相等,可以将数值直接作为 `where` 方法的第二个参数: For convenience, if you are simply comparing the column value to a given value, you can pass the value directly as the second parameter of the `where` method:
```php ```php
$users = Db::table('user')->where('gender', 1)->get(); $users = Db::table('user')->where('gender', 1)->get();
``` ```
当然,你也可以使用其他的运算符来编写 where 子句: Of course, you can also use other operators to write where clauses:
```php ```php
$users = Db::table('users')->where('gender', '>=', 0)->get(); $users = Db::table('users')->where('gender', '>=', 0)->get();
@ -346,7 +346,7 @@ $users = Db::table('users')->where('gender', '<>', 1)->get();
$users = Db::table('users')->where('name', 'like', 'T%')->get(); $users = Db::table('users')->where('name', 'like', 'T%')->get();
``` ```
你还可以传递条件数组到 where 函数中: You can also pass an array of conditions to the where function:
```php ```php
$users = Db::table('user')->where([ $users = Db::table('user')->where([
@ -355,9 +355,9 @@ $users = Db::table('user')->where([
])->get(); ])->get();
``` ```
### Or 语句 ### Or Statement
你可以一起链式调用 `where` 约束,也可以在查询中添加 `or` 字句。 `orWhere` 方法和 `where` 方法接收的参数一样: You can chain `where` constraints together or add `or` clauses to the query. The `orWhere` method accepts the same parameters as the `where` method:
```php ```php
$users = Db::table('user') $users = Db::table('user')
@ -366,11 +366,11 @@ $users = Db::table('user')
->get(); ->get();
``` ```
### 其他 Where 语句 ### Other Where Statements
#### whereBetween #### whereBetween
`whereBetween` 方法验证字段值是否在给定的两个值之间: The `whereBetween` method verifies that the field value is between two given values:
```php ```php
$users = Db::table('users')->whereBetween('votes', [1, 100])->get(); $users = Db::table('users')->whereBetween('votes', [1, 100])->get();
@ -378,7 +378,7 @@ $users = Db::table('users')->whereBetween('votes', [1, 100])->get();
#### whereNotBetween #### whereNotBetween
`whereNotBetween` 方法验证字段值是否在给定的两个值之外: The `whereNotBetween` method verifies that the field value is outside the given two values:
```php ```php
$users = Db::table('users')->whereNotBetween('votes', [1, 100])->get(); $users = Db::table('users')->whereNotBetween('votes', [1, 100])->get();
@ -386,21 +386,21 @@ $users = Db::table('users')->whereNotBetween('votes', [1, 100])->get();
#### whereIn / whereNotIn #### whereIn / whereNotIn
`whereIn` 方法验证字段的值必须存在指定的数组里: The `whereIn` method validates that the value of the field must exist in the specified array:
```php ```php
$users = Db::table('users')->whereIn('id', [1, 2, 3])->get(); $users = Db::table('users')->whereIn('id', [1, 2, 3])->get();
``` ```
`whereNotIn` 方法验证字段的值必须不存在于指定的数组里: The `whereNotIn` method verifies that the value of the field must not exist in the specified array:
```php ```php
$users = Db::table('users')->whereNotIn('id', [1, 2, 3])->get(); $users = Db::table('users')->whereNotIn('id', [1, 2, 3])->get();
``` ```
### 参数分组 ### Parameter grouping
有时候你需要创建更高级的 `where` 子句,例如`「where exists」`或者嵌套的参数分组。查询构造器也能够处理这些。下面,让我们看一个在括号中进行分组约束的例子: Sometimes you need to create more advanced `where` clauses, such as `"where exists"` or nested parameter groupings. The query builder can also handle these. Below, let's see an example of grouping constraints in parentheses:
```php ```php
Db::table('users')->where('name', '=', 'John') Db::table('users')->where('name', '=', 'John')
@ -411,17 +411,17 @@ Db::table('users')->where('name', '=', 'John')
->get(); ->get();
``` ```
你可以看到,通过一个 `Closure` 写入 `where` 方法构建一个查询构造器 来约束一个分组。这个 `Closure` 接收一个查询实例,你可以使用这个实例来设置应该包含的约束。上面的例子将生成以下 SQL: As you can see, a `Closure` is written to the `where` method to construct a query builder to constrain a grouping. The `Closure` receives a query instance that you can use to set constraints that should be included. The above example will generate the following SQL:
```sql ```sql
select * from users where name = 'John' and (votes > 100 or title = 'Admin') select * from users where name = 'John' and (votes > 100 or title = 'Admin')
``` ```
> 你应该用 orWhere 调用这个分组,以避免应用全局作用出现意外. > You should call this grouping with orWhere to avoid accidental application of global effects.
#### Where Exists 语句 #### Where Exists Statement
`whereExists` 方法允许你使用 `where exists SQL` 语句。 `whereExists` 方法接收一个 `Closure` 参数,该 `whereExists` 方法接受一个 `Closure` 参数,该闭包获取一个查询构建器实例从而允许你定义放置在 `exists` 字句中查询: The `whereExists` method allows you to use the `where exists SQL` statement. The `whereExists` method accepts a `Closure` parameter, the `whereExists` method accepts a `Closure` parameter, the closure takes a query builder instance allowing you to define queries placed in the `exists` clause:
```php ```php
Db::table('users')->whereExists(function ($query) { Db::table('users')->whereExists(function ($query) {
@ -432,7 +432,7 @@ Db::table('users')->whereExists(function ($query) {
->get(); ->get();
``` ```
上述查询将产生如下的 SQL 语句: The above query will produce the following SQL statement:
```sql ```sql
select * from users select * from users
@ -441,9 +441,9 @@ where exists (
) )
``` ```
#### JSON Where 语句 #### JSON Where Statement
`Hyperf` 也支持查询 `JSON` 类型的字段(仅在对 `JSON` 类型支持的数据库上)。 `Hyperf` also supports querying fields of type `JSON` (only on databases that support type `JSON`).
```php ```php
$users = Db::table('users') $users = Db::table('users')
@ -455,7 +455,7 @@ $users = Db::table('users')
->get(); ->get();
``` ```
你也可以使用 `whereJsonContains` 来查询 `JSON` 数组: You can also use `whereJsonContains` to query `JSON` arrays:
```php ```php
$users = Db::table('users') $users = Db::table('users')
@ -463,7 +463,7 @@ $users = Db::table('users')
->get(); ->get();
``` ```
你可以使用 `whereJsonLength` 来查询 `JSON` 数组的长度: You can use `whereJsonLength` to query the length of a `JSON` array:
```php ```php
$users = Db::table('users') $users = Db::table('users')
@ -479,7 +479,7 @@ $users = Db::table('users')
### orderBy ### orderBy
`orderBy` 方法允许你通过给定字段对结果集进行排序。 `orderBy` 的第一个参数应该是你希望排序的字段,第二个参数控制排序的方向,可以是 `asc` `desc` The `orderBy` method allows you to order the result set by a given field. The first parameter of `orderBy` should be the field you want to sort, and the second parameter controls the direction of sorting, which can be `asc` or `desc`
```php ```php
$users = Db::table('users') $users = Db::table('users')
@ -489,7 +489,7 @@ $users = Db::table('users')
### latest / oldest ### latest / oldest
`latest``oldest` 方法可以使你轻松地通过日期排序。它默认使用 `created_at` 列作为排序依据。当然,你也可以传递自定义的列名: The `latest` and `oldest` methods allow you to easily sort by date. It uses the `created_at` column as the sort by default. Of course, you can also pass custom column names:
```php ```php
$user = Db::table('users')->latest()->first(); $user = Db::table('users')->latest()->first();
@ -497,7 +497,7 @@ $user = Db::table('users')->latest()->first();
### inRandomOrder ### inRandomOrder
`inRandomOrder` 方法被用来将结果随机排序。例如,你可以使用此方法随机找到一个用户。 The `inRandomOrder` method is used to randomly order the results. For example, you can use this method to find a random user.
```php ```php
$randomUser = Db::table('users')->inRandomOrder()->first(); $randomUser = Db::table('users')->inRandomOrder()->first();
@ -505,7 +505,7 @@ $randomUser = Db::table('users')->inRandomOrder()->first();
### groupBy / having ### groupBy / having
`groupBy``having` 方法可以将结果分组。 `having` 方法的使用与 `where` 方法十分相似: The `groupBy` and `having` methods can group results. The use of the `having` method is very similar to the `where` method:
```php ```php
$users = Db::table('users') $users = Db::table('users')
@ -514,7 +514,7 @@ $users = Db::table('users')
->get(); ->get();
``` ```
你可以向 `groupBy` 方法传递多个参数: You can pass multiple arguments to the `groupBy` method:
```php ```php
$users = Db::table('users') $users = Db::table('users')
@ -523,25 +523,25 @@ $users = Db::table('users')
->get(); ->get();
``` ```
> 对于更高级的 having 语法,参见 havingRaw 方法。 > For more advanced having syntax, see havingRaw method.
### skip / take ### skip / take
要限制结果的返回数量,或跳过指定数量的结果,你可以使用 `skip``take` 方法: To limit the number of results returned, or to skip a specified number of results, you can use the `skip` and `take` methods:
```php ```php
$users = Db::table('users')->skip(10)->take(5)->get(); $users = Db::table('users')->skip(10)->take(5)->get();
``` ```
或者你也可以使用 limit 和 offset 方法: Or you can also use the limit and offset methods:
```php ```php
$users = Db::table('users')->offset(10)->limit(5)->get(); $users = Db::table('users')->offset(10)->limit(5)->get();
``` ```
## 条件语句 ## Conditional statements
有时候你可能想要子句只适用于某个情况为真是才执行查询。例如你可能只想给定值在请求中存在的情况下才应用 `where` 语句。 你可以通过使用 `when` 方法: Sometimes you may want to execute a query only if the clause applies if a certain condition is true. For example, you might only want to apply a `where` statement if a given value exists in the request. You can do this by using the `when` method:
```php ```php
$role = $request->input('role'); $role = $request->input('role');
@ -553,9 +553,9 @@ $users = Db::table('users')
->get(); ->get();
``` ```
`when` 方法只有在第一个参数为 `true` 的时候才执行给的的闭包。如果第一个参数为 `false` ,那么这个闭包将不会被执行 The `when` method executes the given closure only if the first argument is `true`. If the first argument is `false` , then the closure will not be executed
你可以传递另一个闭包作为 `when` 方法的第三个参数。 该闭包会在第一个参数为 `false` 的情况下执行。为了说明如何使用这个特性,我们来配置一个查询的默认排序: You can pass another closure as the third parameter of the `when` method. The closure will be executed if the first argument is `false`. To illustrate how to use this feature, let's configure the default ordering of a query:
```php ```php
$sortBy = null; $sortBy = null;
@ -569,9 +569,9 @@ $users = Db::table('users')
->get(); ->get();
``` ```
## 插入 ## Insert
查询构造器还提供了 `insert` 方法用于插入记录到数据库中。 `insert` 方法接收数组形式的字段名和字段值进行插入操作: The query builder also provides the `insert` method for inserting records into the database. The `insert` method accepts an array of field names and field values for insertion:
```php ```php
Db::table('users')->insert( Db::table('users')->insert(
@ -579,7 +579,7 @@ Db::table('users')->insert(
); );
``` ```
你甚至可以将数组传递给 `insert` 方法,将多个记录插入到表中 You can even pass an array to the `insert` method to insert multiple records into the table
```php ```php
Db::table('users')->insert([ Db::table('users')->insert([
@ -588,9 +588,9 @@ Db::table('users')->insert([
]); ]);
``` ```
### 自增 ID ### Auto Increment ID
如果数据表有自增 `ID` ,使用 `insertGetId` 方法来插入记录并返回 `ID` If the table has an auto-incrementing `ID`, use the `insertGetId` method to insert the record and return the `ID` value
```php ```php
$id = Db::table('users')->insertGetId( $id = Db::table('users')->insertGetId(
@ -598,19 +598,19 @@ $id = Db::table('users')->insertGetId(
); );
``` ```
## 更新 ## Update
当然, 除了插入记录到数据库中,查询构造器也可以通过 `update` 方法更新已有的记录。 `update` 方法和 `insert` 方法一样,接受包含要更新的字段及值的数组。你可以通过 `where` 子句对 `update` 查询进行约束: Of course, in addition to inserting records into the database, the query builder can also update existing records via the `update` method. The `update` method, like the `insert` method, accepts an array containing the fields and values to update. You can constrain the `update` query with the `where` clause:
```php ```php
Db::table('users')->where('id', 1)->update(['votes' => 1]); Db::table('users')->where('id', 1)->update(['votes' => 1]);
``` ```
### 更新或者新增 ### Update or Insert
有时您可能希望更新数据库中的现有记录,或者如果不存在匹配记录则创建它。 在这种情况下,可以使用 `updateOrInsert` 方法。 `updateOrInsert` 方法接受两个参数:一个用于查找记录的条件数组,以及一个包含要更该记录的键值对数组。 Sometimes you may wish to update an existing record in the database, or create a matching record if it does not exist. In this case, the `updateOrInsert` method can be used. The `updateOrInsert` method accepts two parameters: an array of conditions to find the record, and an array of key-value pairs containing the record to update.
`updateOrInsert` 方法将首先尝试使用第一个参数的键和值对来查找匹配的数据库记录。 如果记录存在,则使用第二个参数中的值去更新记录。 如果找不到记录,将插入一个新记录,更新的数据是两个数组的集合: The `updateOrInsert` method will first try to find a matching database record using the key and value pair of the first argument. If the record exists, use the value in the second parameter to update the record. If the record is not found, a new record is inserted, and the updated data is a collection of two arrays:
```php ```php
Db::table('users')->updateOrInsert( Db::table('users')->updateOrInsert(
@ -619,19 +619,19 @@ Db::table('users')->updateOrInsert(
); );
``` ```
### 更新 JSON 字段 ### Update JSON fields
更新 JSON 字段时,你可以使用 -> 语法访问 JSON 对象中相应的值,此操作只能支持 MySQL 5.7+ When updating a JSON field, you can use the -> syntax to access the corresponding value in the JSON object, which is only supported on MySQL 5.7+:
```php ```php
Db::table('users')->where('id', 1)->update(['options->enabled' => true]); Db::table('users')->where('id', 1)->update(['options->enabled' => true]);
``` ```
### 自增与自减 ### Auto increment and decrement
查询构造器还为给定字段的递增或递减提供了方便的方法。此方法提供了一个比手动编写 `update` 语句更具表达力且更精练的接口。 The query builder also provides convenience methods for incrementing or decrementing a given field. This method provides a more expressive and concise interface than manually writing `update` statements.
这两种方法都至少接收一个参数:需要修改的列。第二个参数是可选的,用于控制列递增或递减的量: Both methods receive at least one parameter: the column that needs to be modified. The second parameter is optional and controls the amount by which the column is incremented or decremented:
```php ```php
Db::table('users')->increment('votes'); Db::table('users')->increment('votes');
@ -643,15 +643,15 @@ Db::table('users')->decrement('votes');
Db::table('users')->decrement('votes', 5); Db::table('users')->decrement('votes', 5);
``` ```
你也可以在操作过程中指定要更新的字段: You can also specify fields to update during the operation:
```php ```php
Db::table('users')->increment('votes', 1, ['name' => 'John']); Db::table('users')->increment('votes', 1, ['name' => 'John']);
``` ```
## 删除 ## Delete
查询构造器也可以使用 `delete` 方法从表中删除记录。 在使用 `delete` 前,可以添加 `where` 子句来约束 `delete` 语法: The query builder can also delete records from a table using the `delete` method. Before using `delete`, you can add a `where` clause to constrain the `delete` syntax:
```php ```php
Db::table('users')->delete(); Db::table('users')->delete();
@ -659,21 +659,21 @@ Db::table('users')->delete();
Db::table('users')->where('votes', '>', 100)->delete(); Db::table('users')->where('votes', '>', 100)->delete();
``` ```
如果你需要清空表,你可以使用 `truncate` 方法,它将删除所有行,并重置自增 `ID` 为零: If you need to empty the table, you can use the `truncate` method, which will delete all rows and reset the auto-incrementing `ID` to zero:
```php ```php
Db::table('users')->truncate(); Db::table('users')->truncate();
``` ```
## 悲观锁 ## Pessimistic lock
查询构造器也包含一些可以帮助你在 `select` 语法上实现`「悲观锁定」`的函数。若想在查询中实现一个`「共享锁」` 你可以使用 `sharedLock` 方法。 共享锁可防止选中的数据列被篡改,直到事务被提交为止 The query builder also contains some functions that can help you implement `pessimistic locking` on the `select` syntax. To implement a `"shared lock"` in a query, you can use the `sharedLock` method. Shared locks prevent selected data columns from being tampered with until the transaction is committed
```php ```php
Db::table('users')->where('votes', '>', 100)->sharedLock()->get(); Db::table('users')->where('votes', '>', 100)->sharedLock()->get();
``` ```
或者,你可以使用 `lockForUpdate` 方法。使用`「update」`锁可避免行被其它共享锁修改或选取: Alternatively, you can use the `lockForUpdate` method. Use the `"update"` lock to prevent rows from being modified or selected by other shared locks:
```php ```php
Db::table('users')->where('votes', '>', 100)->lockForUpdate()->get(); Db::table('users')->where('votes', '>', 100)->lockForUpdate()->get();

View File

@ -1,16 +1,16 @@
# 模型关联 # Model association
## 定义关联 ## Define association
关联在 `Hyperf` 模型类中以方法的形式呈现。如同 `Hyperf` 模型本身,关联也可以作为强大的 `查询语句构造器` 使用,提供了强大的链式调用和查询功能。例如,我们可以在 role 关联的链式调用中附加一个约束条件: Associations are presented as methods in the `Hyperf` model class. Like the `Hyperf` model itself, associations can also be used as powerful `query builder`, providing powerful chaining and querying capabilities. For example, we can attach a constraint to the chained calls associated with role:
```php ```php
$user->role()->where('level', 1)->get(); $user->role()->where('level', 1)->get();
``` ```
### 一对一 ### One to one
一对一是最基本的关联关系。例如,一个 `User` 模型可能关联一个 `Role` 模型。为了定义这个关联,我们要在 `User` 模型中写一个 `role` 方法。在 `role` 方法内部调用 `hasOne` 方法并返回其结果: One-to-one is the most basic relationship. For example, a `User` model might be associated with a `Role` model. To define this association, we need to write a `role` method in the `User` model. Call the `hasOne` method inside the `role` method and return its result:
```php ```php
<?php <?php
@ -30,15 +30,15 @@ class User extends Model
} }
``` ```
`hasOne` 方法的第一个参数是关联模型的类名。一旦定义了模型关联,我们就可以使用 `Hyperf` 动态属性获得相关的记录。动态属性允许你访问关系方法就像访问模型中定义的属性一样: The first parameter of the `hasOne` method is the class name of the associated model. Once the model associations are defined, we can use the `Hyperf` dynamic properties to get the related records. Dynamic properties allow you to access relationship methods just like properties defined in the model:
```php ```php
$role = User::query()->find(1)->role; $role = User::query()->find(1)->role;
``` ```
### 一对多 ### One-to-many
『一对多』关联用于定义单个模型拥有任意数量的其它关联模型。例如,一个作者可能写有多本书。正如其它所有的 `Hyperf` 关联一样,一对多关联的定义也是在 `Hyperf` 模型中写一个方法: A "one-to-many" association is used to define a single model with any number of other associated models. For example, an author may have written multiple books. As with all other `Hyperf` relationships, the definition of a one-to-many relationship is to write a method in the `Hyperf` model:
```php ```php
<?php <?php
@ -58,9 +58,9 @@ class User extends Model
} }
``` ```
记住一点,`Hyperf` 将会自动确定 `Book` 模型的外键属性。按照约定,`Hyperf` 将会使用所属模型名称的 『snake case』形式再加上 `_id` 后缀作为外键字段。因此,在上面这个例子中,`Hyperf` 将假定 `User` 对应到 `Book` 模型上的外键就是 `user_id` Remember that `Hyperf` will automatically determine the foreign key properties of the `Book` model. By convention, `Hyperf` will use the "snake case" form of the owning model name, plus the `_id` suffix as the foreign key field. Therefore, in the above example, `Hyperf` will assume that the foreign key corresponding to `User` on the `Book` model is `user_id`.
一旦关系被定义好以后,就可以通过访问 `User` 模型的 `books` 属性来获取评论的集合。记住,由于 Hyperf 提供了『动态属性』 ,所以我们可以像访问模型的属性一样访问关联方法: Once the relationship is defined, the collection of comments can be obtained by accessing the `books` property of the `User` model. Remember, since Hyperf provides "dynamic properties", we can access associated methods just like properties of the model:
```php ```php
$books = User::query()->find(1)->books; $books = User::query()->find(1)->books;
@ -70,15 +70,15 @@ foreach ($books as $book) {
} }
``` ```
当然,由于所有的关联还可以作为查询语句构造器使用,因此你可以使用链式调用的方式,在 books 方法上添加额外的约束条件: Of course, since all associations can also be used as query constructors, you can use chained calls to add additional constraints to the books method:
```php ```php
$book = User::query()->find(1)->books()->where('title', '一个月精通Hyperf框架')->first(); $book = User::query()->find(1)->books()->where('title', 'Mastering the Hyperf framework in one month')->first();
``` ```
### 一对多(反向) ### One-to-many (reverse)
现在,我们已经能获得一个作者的所有作品,接着再定义一个通过书获得其作者的关联关系。这个关联是 `hasMany` 关联的反向关联,需要在子级模型中使用 `belongsTo` 方法定义它: Now that we can get all the works of an author, let's define an association to get its author through the book. This association is the inverse of the `hasMany` association and needs to be defined in the child model using the `belongsTo` method:
```php ```php
<?php <?php
@ -98,7 +98,7 @@ class Book extends Model
} }
``` ```
这个关系定义好以后,我们就可以通过访问 `Book` 模型的 author 这个『动态属性』来获取关联的 `User` 模型了: After this relationship is defined, we can get the associated `User` model by accessing the "dynamic property" of the author of the `Book` model:
```php ```php
$book = Book::find(1); $book = Book::find(1);
@ -106,11 +106,11 @@ $book = Book::find(1);
echo $book->author->name; echo $book->author->name;
``` ```
### 多对多 ### many-to-many
多对多关联比 `hasOne``hasMany` 关联稍微复杂些。举个例子,一个用户可以拥有很多种角色,同时这些角色也被其他用户共享。例如,许多用户可能都有 「管理员」 这个角色。要定义这种关联,需要三个数据库表: `users``roles` 和 `role_user`。`role_user` 表的命名是由关联的两个模型按照字母顺序来的,并且包含了 `user_id``role_id` 字段。 Many-to-many associations are slightly more complicated than `hasOne` and `hasMany` associations. For example, a user can have many roles, and these roles are also shared by other users. For example, many users may have the role of "Administrator". To define this association, three database tables are required: `users`, `roles` and `role_user`. The `role_user` table is named alphabetically by the associated two models, and contains the `user_id` and `role_id` fields.
多对多关联通过调用 `belongsToMany` 这个内部方法返回的结果来定义,例如,我们在 `User` 模型中定义 `roles` 方法: Many-to-many associations are defined by calling the result returned by the internal method `belongsToMany`. For example, we define the `roles` method in the `User` model:
```php ```php
<?php <?php
@ -128,7 +128,7 @@ class User extends Model
} }
``` ```
一旦关联关系被定义后,你可以通过 `roles` 动态属性获取用户角色: Once the relationship is defined, you can get user roles via the `roles` dynamic property:
```php ```php
$user = User::query()->find(1); $user = User::query()->find(1);
@ -138,27 +138,27 @@ foreach ($user->roles as $role) {
} }
``` ```
当然,像其它所有关联模型一样,你可以使用 `roles` 方法,利用链式调用对查询语句添加约束条件: Of course, like all other relational models, you can use the `roles` method to add constraints to queries using chained calls:
```php ```php
$roles = User::find(1)->roles()->orderBy('name')->get(); $roles = User::find(1)->roles()->orderBy('name')->get();
``` ```
正如前面所提到的,为了确定关联连接表的表名,`Hyperf` 会按照字母顺序连接两个关联模型的名字。当然,你也可以不使用这种约定,传递第二个参数到 belongsToMany 方法即可: As mentioned earlier, in order to determine the table name of the relational join table, `Hyperf` will concatenate the names of the two relational models in alphabetical order. Of course, you can also skip this convention and pass the second parameter to the belongsToMany method:
```php ```php
return $this->belongsToMany(Role::class, 'role_user'); return $this->belongsToMany(Role::class, 'role_user');
``` ```
除了自定义连接表的表名,你还可以通过传递额外的参数到 `belongsToMany` 方法来定义该表中字段的键名。第三个参数是定义此关联的模型在连接表里的外键名,第四个参数是另一个模型在连接表里的外键名: In addition to customizing the table name of the join table, you can also define the key name of the field in the table by passing additional parameters to the `belongsToMany` method. The third parameter is the foreign key name of the model that defines this association in the join table, and the fourth parameter is the foreign key name of another model in the join table:
```php ```php
return $this->belongsToMany(Role::class, 'role_user', 'user_id', 'role_id'); return $this->belongsToMany(Role::class, 'role_user', 'user_id', 'role_id');
``` ```
#### 获取中间表字段 #### Get intermediate table fields
就如你刚才所了解的一样,多对多的关联关系需要一个中间表来提供支持, `Hyperf` 提供了一些有用的方法来和这张表进行交互。例如,假设我们的 `User` 对象关联了多个 `Role` 对象。在获得这些关联对象后,可以使用模型的 `pivot` 属性访问中间表的数据: As you just learned, many-to-many relationships require an intermediate table to support, and `Hyperf` provides some useful methods to interact with this table. For example, let's say our `User` object has multiple `Role` objects associated with it. After obtaining these association objects, the data in the intermediate table can be accessed using the model's `pivot` attribute:
```php ```php
$user = User::find(1); $user = User::find(1);
@ -168,31 +168,31 @@ foreach ($user->roles as $role) {
} }
``` ```
需要注意的是,我们获取的每个 `Role` 模型对象,都会被自动赋予 `pivot` 属性,它代表中间表的一个模型对象,并且可以像其他的 `Hyperf` 模型一样使用。 It should be noted that each `Role` model object we get is automatically assigned a `pivot` attribute, which represents a model object of the intermediate table and can be used like other `Hyperf` models.
默认情况下,`pivot` 对象只包含两个关联模型的主键,如果你的中间表里还有其他额外字段,你必须在定义关联时明确指出: By default, the `pivot` object contains only the primary keys of the two relational models. If you have additional fields in the intermediate table, you must specify them when defining the relation:
```php ```php
return $this->belongsToMany(Role::class)->withPivot('column1', 'column2'); return $this->belongsToMany(Role::class)->withPivot('column1', 'column2');
``` ```
如果你想让中间表自动维护 `created_at``updated_at` 时间戳,那么在定义关联时附加上 `withTimestamps` 方法即可: If you want the intermediate table to automatically maintain the `created_at` and `updated_at` timestamps, then add the `withTimestamps` method when defining the association:
```php ```php
return $this->belongsToMany(Role::class)->withTimestamps(); return $this->belongsToMany(Role::class)->withTimestamps();
``` ```
#### 自定义 `pivot` 属性名称 #### custom `pivot` attribute name
如前所述,来自中间表的属性可以使用 `pivot` 属性访问。但是,你可以自由定制此属性的名称,以便更好的反应其在应用中的用途。 As mentioned earlier, properties from intermediate tables can be accessed using the `pivot` attribute. However, you are free to customize the name of this property to better reflect its use in your application.
例如,如果你的应用中包含可能订阅的用户,则用户与博客之间可能存在多对多的关系。如果是这种情况,你可能希望将中间表访问器命名为 `subscription` 取代 `pivot` 。这可以在定义关系时使用 `as` 方法完成: For example, if your app includes users who may subscribe, there may be a many-to-many relationship between users and blogs. If this is the case, you may wish to name the intermediate table accessor `subscription` instead of `pivot` . This can be done using the `as` method when defining the relationship:
```php ```php
return $this->belongsToMany(Podcast::class)->as('subscription')->withTimestamps(); return $this->belongsToMany(Podcast::class)->as('subscription')->withTimestamps();
``` ```
一旦定义完成,你可以使用自定义名称访问中间表数据: Once defined, you can access the intermediate table data with a custom name:
```php ```php
$users = User::with('podcasts')->get(); $users = User::with('podcasts')->get();
@ -202,9 +202,9 @@ foreach ($users->flatMap->podcasts as $podcast) {
} }
``` ```
#### 通过中间表过滤关系 #### Filter relations by intermediate table
在定义关系时,你还可以使用 `wherePivot``wherePivotIn` 方法来过滤 `belongsToMany` 返回的结果: When defining a relationship, you can also use the `wherePivot` and `wherePivotIn` methods to filter the results returned by `belongsToMany`:
```php ```php
return $this->belongsToMany('App\Role')->wherePivot('approved', 1); return $this->belongsToMany('App\Role')->wherePivot('approved', 1);
@ -213,9 +213,9 @@ return $this->belongsToMany('App\Role')->wherePivotIn('priority', [1, 2]);
``` ```
## 预加载 ## Preloading
当以属性方式访问 `Hyperf` 关联时,关联数据「懒加载」。这着直到第一次访问属性时关联数据才会被真实加载。不过 `Hyperf` 能在查询父模型时「预先载入」子关联。预加载可以缓解 N + 1 查询问题。为了说明 N + 1 查询问题,考虑 `User` 模型关联到 `Role` 的情形: When accessing a `Hyperf` relationship as an attribute, the associated data is "lazy loaded". This means that the associated data is not actually loaded until the property is accessed for the first time. However, `Hyperf` can "preload" child associations when querying the parent model. Eager loading can alleviate the N+1 query problem. To illustrate the N + 1 query problem, consider a `User` model associated with a `Role`:
```php ```php
<?php <?php
@ -235,7 +235,7 @@ class User extends Model
} }
``` ```
现在,我们来获取所有的用户及其对应角色 Now, let's get all users and their corresponding roles
```php ```php
$users = User::query()->get(); $users = User::query()->get();
@ -245,9 +245,9 @@ foreach ($users as $user){
} }
``` ```
此循环将执行一个查询,用于获取全部用户,然后为每个用户执行获取角色的查询。如果我们有 10 个人,此循环将运行 11 个查询1 个用于查询用户10 个附加查询对应的角色。 This loop will execute a query to get all users, and then execute a query to get roles for each user. If we have 10 people, this loop will run 11 queries: 1 for users and 10 additional queries for roles.
谢天谢地,我们能够使用预加载将操作压缩到只有 2 个查询。在查询时,可以使用 with 方法指定想要预加载的关联: Thankfully, we were able to squeeze the operation down to just 2 queries using eager loading. At query time, you can use the with method to specify which associations you want to preload:
```php ```php
$users = User::query()->with('role')->get(); $users = User::query()->with('role')->get();
@ -257,7 +257,7 @@ foreach ($users as $user){
} }
``` ```
在这个例子中,仅执行了两个查询 In this example, only two queries are executed
``` ```
SELECT * FROM `user`; SELECT * FROM `user`;

View File

@ -1,30 +1,30 @@
# API 资源构造器 # API resource constructor
> 支持返回 Grpc 响应的资源扩展 > Support for resource extensions that return Grpc responses
## 简介 ## Introduction
当构建 API 时,你往往需要一个转换层来联结你的 Model 模型和实际返回给用户的 JSON 响应。资源类能够让你以更直观简便的方式将模型和模型集合转化成 JSON。 When building APIs, you often need a translation layer to connect your Model with the actual JSON response returned to the user. Resource classes allow you to convert models and collections of models to JSON in a more intuitive and easy way.
## 安装 ## Install
``` ```
composer require hyperf/resource composer require hyperf/resource
``` ```
## 生成资源 ## Generate resources
你可以使用 `gen:resource` 命令来生成一个资源类。默认情况下生成的资源都会被放置在应用程序的 `app/Resource` 文件夹下。资源继承自 `Hyperf\Resource\Json\JsonResource` 类: You can use the `gen:resource` command to generate a resource class. By default generated resources are placed in the application's `app/Resource` folder. Resources inherit from the `Hyperf\Resource\Json\JsonResource` class:
```bash ```bash
php bin/hyperf.php gen:resource User php bin/hyperf.php gen:resource User
``` ```
### 资源集合 ### Resource Collection
除了生成资源转换单个模型外,你还可以生成资源集合用来转换模型的集合。这允许你在响应中包含与给定资源相关的链接与其他元信息。 In addition to generating resources to transform a single model, you can also generate a collection of resources to transform a collection of models. This allows you to include links and other meta information related to a given resource in the response.
你需要在生成资源时添加 `--collection` 标志以生成一个资源集合。或者,你也可以直接在资源的名称中包含 `Collection` 表示应该生成一个资源集合。资源集合继承自 `Hyperf\Resource\Json\ResourceCollection` 类: You need to add the `--collection` flag when generating resources to generate a collection of resources. Alternatively, you can include `Collection` directly in the resource name to indicate that a collection of resources should be generated. Resource collections inherit from the `Hyperf\Resource\Json\ResourceCollection` class:
```bash ```bash
php bin/hyperf.php gen:resource Users --collection php bin/hyperf.php gen:resource Users --collection
@ -32,9 +32,9 @@ php bin/hyperf.php gen:resource Users --collection
php bin/hyperf.php gen:resource UserCollection php bin/hyperf.php gen:resource UserCollection
``` ```
## gRPC 资源 ## gRPC resources
> 需要额外安装 `hyperf/resource-grpc` > Requires additional installation of `hyperf/resource-grpc`
``` ```
composer require hyperf/resource-grpc composer require hyperf/resource-grpc
@ -44,9 +44,9 @@ composer require hyperf/resource-grpc
php bin/hyperf.php gen:resource User --grpc php bin/hyperf.php gen:resource User --grpc
``` ```
gRPC 资源需要设置 `message` 类. 通过重写该资源类的 `expect()` 方法来实现. gRPC resources need to set the `message` class. This is achieved by overriding the `expect()` method of the resource class.
gRPC 服务返回时, 必须调用 `toMessage()`. 该方法会返回一个实例化的 `message`. When the gRPC service returns, `toMessage()` must be called. This method returns an instantiated `message` class.
```php ```php
<?php <?php
@ -73,13 +73,13 @@ class HiReplyResource extends GrpcResource
``` ```
默认生成的资源集合, 可通过继承 `Hyperf\ResourceGrpc\GrpcResource` 接口来使其支持 gRPC 返回. The default generated resource collection can support gRPC return by extending the `Hyperf\ResourceGrpc\GrpcResource` interface.
## 概念综述 ## Concept overview
> 这是对资源和资源集合的高度概述。强烈建议你阅读本文档的其他部分,以深入了解如何更好地自定义和使用资源。 > This is a high-level overview of resources and resource collections. It is strongly recommended that you read the rest of this document for an in-depth understanding of how to better customize and use resources.
在深入了解如何定制化编写你的资源之前,让我们先来看看在框架中如何使用资源。一个资源类表示一个单一模型需要被转换成 JSON 格式。例如,现在我们有一个简单的 `User` 资源类: Before diving into how to custom write your resources, let's take a look at how resources are used in the framework. A resource class representing a single model needs to be converted into JSON format. For example, now we have a simple `User` resource class:
```php ```php
<?php <?php
@ -107,7 +107,7 @@ class User extends JsonResource
``` ```
每一个资源类都定义了一个 `toArray` 方法,在发送响应时它会返回应该被转化成 JSON 的属性数组。注意在这里我们可以直接使用 `$this` 变量来访问模型属性。这是因为资源类将自动代理属性和方法到底层模型以方便访问。你可以在控制器中返回已定义的资源: Each resource class defines a `toArray` method which returns an array of properties that should be converted to JSON when sending the response. Note that here we can directly use the `$this` variable to access model properties. This is because the resource class will automatically proxy properties and methods to the underlying model for easy access. You can return defined resources in your controller:
```php ```php
<?php <?php
@ -127,9 +127,9 @@ class IndexController extends AbstractController
``` ```
### 资源集合 ### Resource Collection
你可以在控制器中使用 `collection` 方法来创建资源实例,以返回多个资源的集合或分页响应: You can use the `collection` method in a controller to create resource instances to return collections of multiple resources or paginated responses:
```php ```php
@ -148,13 +148,13 @@ class IndexController extends AbstractController
``` ```
当然了,使用如上方法你将不能添加任何附加的元数据和集合一起返回。如果你需要自定义资源集合响应,你需要创建一个专用的资源来表示集合: Of course, using the above method you will not be able to add any additional metadata to return with the collection. If you need a custom resource collection response, you need to create a dedicated resource to represent the collection:
```bash ```bash
php bin/hyperf.php gen:resource UserCollection php bin/hyperf.php gen:resource UserCollection
``` ```
你可以轻松的在已生成的资源集合类中定义任何你想在响应中返回的元数据: You can easily define any metadata you want returned in the response in the generated resource collection class:
```php ```php
<?php <?php
@ -183,7 +183,7 @@ class UserCollection extends ResourceCollection
``` ```
你可以在控制器中返回已定义的资源集合: You can return a defined collection of resources in your controller:
```php ```php
<?php <?php
@ -202,9 +202,9 @@ class IndexController extends AbstractController
``` ```
### 保护集合的键 ### Protected collection keys
当从路由返回资源集合时,将重置集合的键,使它们以简单的数字顺序。但是,可以将 `preserveKeys` 属性添加到资源类中,指示是否应保留集合键: When a resource collection is returned from a route, the collection's keys are reset so that they are in simple numerical order. However, a `preserveKeys` attribute can be added to a resource class to indicate whether collection keys should be preserved:
```php ```php
<?php <?php
@ -216,7 +216,7 @@ use Hyperf\Resource\Json\JsonResource;
class User extends JsonResource class User extends JsonResource
{ {
/** /**
* 指示是否应保留资源的集合键。 * A collection key indicating whether the resource should be preserved.
* *
* @var bool * @var bool
*/ */
@ -239,7 +239,7 @@ class User extends JsonResource
``` ```
`preserveKeys` 属性被设置为 `true`,集合的键将会被保护: When the `preserveKeys` property is set to `true`, the keys of the collection will be protected:
```php ```php
<?php <?php
@ -259,11 +259,11 @@ class IndexController extends AbstractController
``` ```
### 自定义基础资源类 ### Custom basic resource class
通常,资源集合的 `$this->collection` 属性会自动填充,结果是将集合的每个项映射到其单个资源类。假定单一资源类是集合的类名,但结尾没有 `Collection` 字符串。 Typically, the `$this->collection` property of a resource collection is automatically populated, resulting in a mapping of each item of the collection to its individual resource class. The single resource class is assumed to be the class name of the collection without the `Collection` string at the end.
例如,`UserCollection` 将给定的用户实例映射到 `User` 资源中。若要自定义此行为,你可以重写资源集合的 `$collects` 属性: For example, `UserCollection` maps a given user instance into a `User` resource. To customize this behavior, you can override the `$collects` property of the resource collection:
```php ```php
<?php <?php
@ -275,7 +275,7 @@ use Hyperf\Resource\Json\ResourceCollection;
class UserCollection extends ResourceCollection class UserCollection extends ResourceCollection
{ {
/** /**
* collects 属性定义了资源类。 * collects properties define resource classes.
* *
* @var string * @var string
*/ */
@ -299,11 +299,11 @@ class UserCollection extends ResourceCollection
``` ```
## 编写资源 ## write resources
> 如果你还没有阅读 [概念综述](#概念综述),那么在继续阅读本文档前,强烈建议你去阅读一下。 > If you haven't read [Concept Overview](#Concept Overview), it is strongly recommended that you do so before continuing with this document.
从本质上来说,资源的作用很简单。它们只需要将一个给定的模型转换成一个数组。所以每一个资源都包含一个 `toArray` 方法用来将你的模型属性转换成一个可以返回给用户的 API 友好数组: Essentially, the role of resources is simple. They just need to convert a given model into an array. So every resource contains a `toArray` method to convert your model properties into an API-friendly array that can be returned to the user:
```php ```php
<?php <?php
@ -331,7 +331,7 @@ class User extends JsonResource
``` ```
你可以在控制器中返回已经定义的资源: You can return an already defined resource in a controller:
```php ```php
<?php <?php
@ -351,9 +351,10 @@ class IndexController extends AbstractController
``` ```
### 关联 ### Association
If you wish to include associated resources in the response, you only need to add them to the array returned by the `toArray` method. In the following example, we will use the `collection` method of the `Post` resource to add the user's post to the resource response:
如果你希望在响应中包含关联资源,你只需要将它们添加到 `toArray` 方法返回的数组中。在下面这个例子里,我们将使用 `Post` 资源的 `collection` 方法将用户的文章添加到资源响应中:
```php ```php
<?php <?php
@ -381,11 +382,11 @@ class User extends JsonResource
``` ```
> 如果你只想在关联已经加载时才添加关联资源,请查看相关文档。 > If you only want to add an associated resource when the association is already loaded, see the related documentation.
### 资源集合 ### Resource Collection
资源是将单个模型转换成数组,而资源集合是将多个模型的集合转换成数组。所有的资源都提供了一个 `collection` 方法来生成一个 「临时」 资源集合,所以你没有必要为每一个模型类型都编写一个资源集合类: A resource converts a single model into an array, and a resource collection converts a collection of multiple models into an array. All resources provide a `collection` method to generate a "temporary" collection of resources, so you don't have to write a resource collection class for each model type:
```php ```php
<?php <?php
@ -404,7 +405,7 @@ class IndexController extends AbstractController
``` ```
要自定义返回集合的元数据,则仍需要定义一个资源集合: To customize the metadata of the returned collection, you still need to define a resource collection:
```php ```php
<?php <?php
@ -433,7 +434,7 @@ class UserCollection extends ResourceCollection
``` ```
和单个资源一样,你可以在控制器中直接返回资源集合: As with individual resources, you can return collections of resources directly in your controller:
```php ```php
<?php <?php
@ -452,9 +453,9 @@ class IndexController extends AbstractController
``` ```
### 数据包裹 ### Data package
默认情况下,当资源响应被转换成 JSON 时,顶层资源将会被包裹在 `data` 键中。因此一个典型的资源集合响应如下所示: By default, when the resource response is converted to JSON, the top-level resource will be wrapped in the `data` key. So a typical resource collection response looks like this:
```json ```json
@ -475,7 +476,7 @@ class IndexController extends AbstractController
``` ```
你可以使用资源基类的 `withoutWrapping` 方法来禁用顶层资源的包裹。 You can disable wrapping of top-level resources using the `withoutWrapping` method of the resource base class.
```php ```php
<?php <?php
@ -494,13 +495,13 @@ class IndexController extends AbstractController
``` ```
> withoutWrapping 方法只会禁用顶层资源的包裹,不会删除你手动添加到资源集合中的 data 键。而且只会在当前的资源或资源集合中生效,不影响全局。 > The withoutWrapping method will only disable wrapping of the top-level resource, it will not remove the data key that you manually added to the resource collection. And it will only take effect in the current resource or resource collection, without affecting the global.
#### 包裹嵌套资源 #### Wrapping nested resources
你可以完全自由地决定资源关联如何被包裹。如果你希望无论怎样嵌套,都将所有资源集合包裹在 `data` 键中,那么你需要为每个资源都定义一个资源集合类,并将返回的集合包裹在 `data` 键中。 You are completely free to decide how resource associations are wrapped. If you want all resource collections to be wrapped in a `data` key, no matter how nested, then you need to define a resource collection class for each resource and wrap the returned collection in a `data` key.
当然,你可能会担心这样顶层资源将会被包裹在两个 `data `键中。请放心, 组件将永远不会让你的资源被双层包裹,因此你不必担心被转换的资源集合会被多重嵌套: Of course, you might worry that the top-level resource would then be wrapped in two `data` keys. Rest assured, components will never have your resources double-wrapped, so you don't have to worry about multiple nesting of transformed resource collections:
```php ```php
<?php <?php
@ -526,9 +527,9 @@ class UserCollection extends ResourceCollection
``` ```
#### 分页 #### Pagination
当在资源响应中返回分页集合时,即使你调用了 `withoutWrapping` 方法, 组件也会将你的资源数据包裹在 `data` 键中。这是因为分页响应中总会有 `meta``links` 键包含着分页状态信息: When returning a paginated collection in a resource response, even if you call the `withoutWrapping` method, the component will wrap your resource data in the `data` key. This is because the `meta` and `links` keys in the pagination response always contain pagination status information:
```json ```json
@ -563,7 +564,7 @@ class UserCollection extends ResourceCollection
} }
``` ```
你可以将分页实例传递给资源的 `collection` 方法或者自定义的资源集合: You can pass a pagination instance to the resource's collection method or a custom resource collection:
```php ```php
<?php <?php
@ -582,7 +583,7 @@ class IndexController extends AbstractController
} }
``` ```
分页响应中总有 `meta``links` 键包含着分页状态信息: There are always `meta` and `links` keys in pagination responses that contain pagination status information:
```json ```json
@ -617,9 +618,9 @@ class IndexController extends AbstractController
} }
``` ```
### 条件属性 ### Conditional properties
有些时候,你可能希望在给定条件满足时添加属性到资源响应里。例如,你可能希望如果当前用户是 「管理员」 时添加某个值到资源响应中。在这种情况下组件提供了一些辅助方法来帮助你解决问题。 `when` 方法可以被用来有条件地向资源响应添加属性: Sometimes you may wish to add attributes to the resource response when a given condition is met. For example, you might want to add a value to the resource response if the current user is an "admin". In this case the component provides some helper methods to help you solve the problem. The `when` method can be used to conditionally add attributes to resource responses:
```php ```php
<?php <?php
@ -648,9 +649,9 @@ class User extends JsonResource
``` ```
在上面这个例子中,只有当 `isAdmin` 方法返回 `true` 时, `secret` 键才会最终在资源响应中被返回。如果该方法返回 `false` ,则 `secret` 键将会在资源响应被发送给客户端之前被删除。 `when` 方法可以使你避免使用条件语句拼接数组,转而用更优雅的方式来编写你的资源。 In the above example, the `secret` key will eventually be returned in the resource response only if the `isAdmin` method returns `true`. If this method returns `false`, the `secret` key will be deleted before the resource response is sent to the client. The `when` method allows you to avoid concatenating arrays with conditional statements and instead write your resources in a more elegant way.
`when` 方法也接受闭包作为其第二个参数,只有在给定条件为 `true` 时,才从闭包中计算返回的值: The `when` method also accepts a closure as its second argument, from which the returned value is computed only if the given condition is `true`:
```php ```php
<?php <?php
@ -681,9 +682,9 @@ class User extends JsonResource
``` ```
#### 有条件的合并数据 #### Conditional merge data
有些时候,你可能希望在给定条件满足时添加多个属性到资源响应里。在这种情况下,你可以使用 `mergeWhen` 方法在给定的条件为 `true` 时将多个属性添加到响应中: Sometimes, you may wish to add multiple attributes to the resource response when a given condition is met. In this case, you can use the `mergeWhen` method to add multiple properties to the response when a given condition is `true`:
```php ```php
<?php <?php
@ -715,15 +716,15 @@ class User extends JsonResource
``` ```
同理,如果给定的条件为 `false` 时,则这些属性将会在资源响应被发送给客户端之前被移除。 Likewise, if the given condition is `false`, these attributes will be removed before the resource response is sent to the client.
> `mergeWhen` 方法不应该被使用在混合字符串和数字键的数组中。此外,它也不应该被使用在不按顺序排列的数字键的数组中。 > The `mergeWhen` method should not be used on arrays with mixed string and numeric keys. Also, it shouldn't be used in arrays of out-of-order numeric keys.
### 条件关联 ### Conditional association
除了有条件地添加属性之外,你还可以根据模型关联是否已加载来有条件地在你的资源响应中包含关联。这允许你在控制器中决定加载哪些模型关联,这样你的资源可以在模型关联被加载后才添加它们。 In addition to adding properties conditionally, you can also conditionally include associations in your resource responses based on whether the model association is loaded. This allows you to decide in the controller which model associations to load, so that your resources can add them after the model associations are loaded.
这样做可以避免在你的资源中出现 「N+1」 查询问题。你应该使用 `whenLoaded` 方法来有条件的加载关联。为了避免加载不必要的关联,此方法接受关联的名称而不是关联本身作为其参数: Doing this will avoid the "N+1" query problem in your resources. You should use the `whenLoaded` method to conditionally load associations. To avoid loading unnecessary associations, this method accepts the name of the association rather than the association itself as its parameter:
```php ```php
<?php <?php
@ -752,11 +753,11 @@ class User extends JsonResource
``` ```
在上面这个例子中,如果关联没有被加载,则 `posts` 键将会在资源响应被发送给客户端之前被删除。 In the above example, if the association is not loaded, the `posts` key will be deleted before the resource response is sent to the client.
#### 条件中间表信息 #### Conditional intermediate table information
除了在你的资源响应中有条件地包含关联外,你还可以使用 `whenPivotLoaded` 方法有条件地从多对多关联的中间表中添加数据。 `whenPivotLoaded` 方法接受的第一个参数为中间表的名称。第二个参数是一个闭包,它定义了在模型上如果中间表信息可用时要返回的值: In addition to conditionally including associations in your resource responses, you can also conditionally add data from intermediate tables in many-to-many associations using the `whenPivotLoaded` method. The first parameter accepted by the `whenPivotLoaded` method is the name of the intermediate table. The second parameter is a closure that defines the value to return on the model if intermediate table information is available:
```php ```php
<?php <?php
@ -786,7 +787,7 @@ class User extends JsonResource
``` ```
如果你的中间表使用的是 `pivot` 以外的访问器,你可以使用 `whenPivotLoadedAs`方法: If your intermediate table uses accessors other than `pivot`, you can use the `whenPivotLoadedAs` method:
```php ```php
<?php <?php
@ -816,9 +817,9 @@ class User extends JsonResource
``` ```
### 添加元数据 ### Add metadata
一些 JSON API 标准需要你在资源和资源集合响应中添加元数据。这通常包括资源或相关资源的 `links` ,或一些关于资源本身的元数据。如果你需要返回有关资源的其他元数据,只需要将它们包含在 `toArray` 方法中即可。例如在转换资源集合时你可能需要添加 `links` 信息: Some JSON API standards require you to add metadata to resource and resource collection responses. This usually includes `links` for the resource or related resources, or some metadata about the resource itself. If you need to return additional metadata about the resource, just include them in the `toArray` method. For example, you may need to add `links` information when converting resource collections:
```php ```php
<?php <?php
@ -847,11 +848,11 @@ class UserCollection extends ResourceCollection
``` ```
当添加额外的元数据到你的资源中时,你不必担心会覆盖在返回分页响应时自动添加的 `links``meta` 键。你添加的任何其他 `links` 会与分页响应添加的 `links` 相合并。 When adding extra metadata to your resource, you don't have to worry about overwriting the `links` or `meta` keys that are automatically added when returning paginated responses. Any other `links` you add will be merged with the `links` added by the pagination response.
#### 顶层元数据 #### top-level metadata
有时候你可能希望当资源被作为顶层资源返回时添加某些元数据到资源响应中。这通常包括整个响应的元信息。你可以在资源类中添加 `with` 方法来定义元数据。此方法应返回一个元数据数组,当资源被作为顶层资源渲染时,这个数组将会被包含在资源响应中: Sometimes you may wish to add certain metadata to the resource response when the resource is returned as a top-level resource. This usually includes meta information for the entire response. You can add a `with` method to your resource class to define metadata. This method should return an array of metadata that will be included in the resource response when the resource is rendered as a top-level resource:
```php ```php
<?php <?php
@ -889,9 +890,9 @@ class UserCollection extends ResourceCollection
``` ```
#### 构造资源时添加元数据 #### Add metadata when constructing resources
你还可以在控制器中构造资源实例时添加顶层数据。所有资源都可以使用 `additional` 方法来接受应该被添加到资源响应中的数据数组: You can also add top-level data when constructing a resource instance in a controller. All resources can use the `additional` method to accept an array of data that should be added to the resource response:
```php ```php
<?php <?php
@ -914,9 +915,9 @@ class IndexController extends AbstractController
``` ```
## 响应资源 ## Response resource
就像你知道的那样,资源可以直接在控制器中被返回: As you know, resources can be returned directly in the controller:
```php ```php
<?php <?php
@ -941,4 +942,4 @@ class IndexController extends AbstractController
``` ```
如你想设置响应头信息, 状态码等, 通过调用 `toResponse()` 方法获取到响应对象进行设置. If you want to set the response header information, status code, etc., get the response object by calling the `toResponse()` method to set it.

View File

@ -0,0 +1,5 @@
# Corporate Services
We provide paid enterprise technical support services to ensure that you can use Hyperf in your business environment more smoothly and easily. If you have relevant technical support service needs, please add the following WeChat to contact us.
![wechat](imgs/wechat.jpg ':size=375')

348
docs/en/filesystem.md Normal file
View File

@ -0,0 +1,348 @@
# File system
The file system component integrates the famous `League\Flysystem` in the PHP ecosystem (this is also the underlying library of many well-known frameworks such as Laravel). Through reasonable abstraction, the program does not have to perceive whether the storage engine is a local hard disk or a cloud server, realizing decoupling. This component provides coroutine support for common cloud storage services.
## Install
```shell
composer require hyperf/filesystem
```
The versions of `League\Flysystem` components `v1.0`, `v2.0` and `v3.0` have changed greatly, so you need to install the corresponding adapters according to different versions
- Alibaba Cloud OSS adapter
`Flysystem v1.0` version
```shell
composer require xxtime/flysystem-aliyun-oss
```
`Flysystem v2.0` and `Flysystem v3.0` versions
```shell
composer require hyperf/flysystem-oss
```
- S3 adapter
`Flysystem v1.0` version
```shell
composer require "league/flysystem-aws-s3-v3:^1.0"
composer require hyperf/guzzle
```
`Flysystem v2.0` version
```shell
composer require "league/flysystem-aws-s3-v3:^2.0"
composer require hyperf/guzzle
```
- Qiniu Adapter
`Flysystem v1.0` version
```shell
composer require "overtrue/flysystem-qiniu:^1.0"
```
`Flysystem v2.0` version
```shell
composer require "overtrue/flysystem-qiniu:^2.0"
```
`Flysystem v3.0` version
```shell
composer require "overtrue/flysystem-qiniu:^3.0"
```
- memory adapter
`Flysystem v1.0` version
```shell
composer require "league/flysystem-memory:^1.0"
```
`Flysystem v2.0` version
```shell
composer require "league/flysystem-memory:^2.0"
```
- Tencent Cloud COS Adapter
`Flysystem v1.0` version
> flysystem-cos v2.0 version is deprecated, please modify it to 3.0 version according to the latest documentation
```shell
composer require "overtrue/flysystem-cos:^3.0"
```
`Flysystem v2.0` version
```shell
composer require "overtrue/flysystem-cos:^4.0"
```
`Flysystem v3.0` version
```shell
composer require "overtrue/flysystem-cos:^5.0"
```
After the installation is complete, execute
```bash
php bin/hyperf.php vendor:publish hyperf/filesystem
```
The `config/autoload/file.php` file will be generated. Set the default driver in this file, and configure the access key, access secret and other information of the corresponding driver, and you can use it.
## use
It can be used by injecting `League\Flysystem\Filesystem` through DI.
The API is as follows:
> The following example is Flysystem v1.0 version, please refer to the official documentation for v2.0 version
```php
<?php
declare(strict_types=1);
namespace App\Controller;
class IndexController extends AbstractController
{
public function example(\League\Flysystem\Filesystem $filesystem)
{
// Process Upload
$file = $this->request->file('upload');
$stream = fopen($file->getRealPath(), 'r+');
$filesystem->writeStream(
'uploads/'.$file->getClientFilename(),
$stream
);
fclose($stream);
// Write Files
$filesystem->write('path/to/file.txt', 'contents');
// Add local file
$stream = fopen('local/path/to/file.txt', 'r+');
$result = $filesystem->writeStream('path/to/file.txt', $stream);
if (is_resource($stream)) {
fclose($stream);
}
// Update Files
$filesystem->update('path/to/file.txt', 'new contents');
// Check if a file exists
$exists = $filesystem->has('path/to/file.txt');
// Read Files
$contents = $filesystem->read('path/to/file.txt');
// Delete Files
$filesystem->delete('path/to/file.txt');
// Rename Files
$filesystem->rename('filename.txt', 'newname.txt');
// Copy Files
$filesystem->copy('filename.txt', 'duplicate.txt');
// list the contents
$filesystem->listContents('path', false);
}
}
```
At some point, you will need to use multiple storage media at the same time. At this point, you can inject `Hyperf\Filesystem\FilesystemFactory` to dynamically choose which driver to use.
```php
<?php
declare(strict_types=1);
namespace App\Controller;
class IndexController
{
public function example(\Hyperf\Filesystem\FilesystemFactory $factory)
{
$local = $factory->get('local');
// Write Files
$local->write('path/to/file.txt', 'contents');
$s3 = $factory->get('s3');
$s3->write('path/to/file.txt', 'contents');
}
}
```
### Configure static resources
If you want to access files uploaded locally via http, please add the following configuration to the `config/autoload/server.php` configuration.
```
return [
'settings' => [
...
// Replace public with the upload directory
'document_root' => BASE_PATH . '/public',
'enable_static_handler' => true,
],
];
```
## Precautions
1. Please make sure to install the `hyperf/guzzle` component for S3 storage to provide coroutine support. For Alibaba Cloud, Qiniu Cloud, and Tencent Cloud, please [Open Curl Hook](/zh-cn/coroutine?id=swoole-runtime-hook-level) to use coroutines. Due to the parameter support of Curl Hook, please use Swoole 4.4.13 or later.
2. Private object storage solutions such as minIO and ceph radosgw all support the S3 protocol and can use the S3 adapter.
3. When using the Local driver, the root directory is the configured address, not the root directory of the operating system. For example, if the local driver `root` is set to `/var/www`, then `/var/www/public/file.txt` on the local disk should be accessed through the flysystem API using `/public/file.txt` or ` public/file.txt` .
4. Taking Alibaba Cloud OSS as an example, the read operation performance comparison of 1 core and 1 process:
```bash
ab -k -c 10 -n 1000 http://127.0.0.1:9501/
```
CURL HOOK is not enabled:
```
Concurrency Level: 10
Time taken for tests: 202.902 seconds
Complete requests: 1000
Failed requests: 0
Keep-Alive requests: 1000
Total transferred: 146000 bytes
HTML transferred: 5000 bytes
Requests per second: 4.93 [#/sec] (mean)
Time per request: 2029.016 [ms] (mean)
Time per request: 202.902 [ms] (mean, across all concurrent requests)
Transfer rate: 0.70 [Kbytes/sec] received
```
After enabling CURL HOOK:
```
Concurrency Level: 10
Time taken for tests: 9.252 seconds
Complete requests: 1000
Failed requests: 0
Keep-Alive requests: 1000
Total transferred: 146000 bytes
HTML transferred: 5000 bytes
Requests per second: 108.09 [#/sec] (mean)
Time per request: 92.515 [ms] (mean)
Time per request: 9.252 [ms] (mean, across all concurrent requests)
Transfer rate: 15.41 [Kbytes/sec] received
```
## Detailed configuration
```php
return [
// Select the key corresponding to the driver under storage.
'default' => 'local',
'storage' => [
'local' => [
'driver' => \Hyperf\Filesystem\Adapter\LocalAdapterFactory::class,
'root' => __DIR__ . '/../../runtime',
],
'ftp' => [
'driver' => \Hyperf\Filesystem\Adapter\FtpAdapterFactory::class,
'host' => 'ftp.example.com',
'username' => 'username',
'password' => 'password',
/* optional config settings */
'port' => 21,
'root' => '/path/to/root',
'passive' => true,
'ssl' => true,
'timeout' => 30,
'ignorePassiveAddress' => false,
],
'memory' => [
'driver' => \Hyperf\Filesystem\Adapter\MemoryAdapterFactory::class,
],
's3' => [
'driver' => \Hyperf\Filesystem\Adapter\S3AdapterFactory::class,
'credentials' => [
'key' => env('S3_KEY'),
'secret' => env('S3_SECRET'),
],
'region' => env('S3_REGION'),
'version' => 'latest',
'bucket_endpoint' => false,
'use_path_style_endpoint' => false,
'endpoint' => env('S3_ENDPOINT'),
'bucket_name' => env('S3_BUCKET'),
],
'minio' => [
'driver' => \Hyperf\Filesystem\Adapter\S3AdapterFactory::class,
'credentials' => [
'key' => env('S3_KEY'),
'secret' => env('S3_SECRET'),
],
'region' => env('S3_REGION'),
'version' => 'latest',
'bucket_endpoint' => false,
'use_path_style_endpoint' => true,
'endpoint' => env('S3_ENDPOINT'),
'bucket_name' => env('S3_BUCKET'),
],
'oss' => [
'driver' => \Hyperf\Filesystem\Adapter\AliyunOssAdapterFactory::class,
'accessId' => env('OSS_ACCESS_ID'),
'accessSecret' => env('OSS_ACCESS_SECRET'),
'bucket' => env('OSS_BUCKET'),
'endpoint' => env('OSS_ENDPOINT'),
// 'timeout' => 3600,
// 'connectTimeout' => 10,
// 'isCName' => false,
// 'token' => '',
],
'qiniu' => [
'driver' => \Hyperf\Filesystem\Adapter\QiniuAdapterFactory::class,
'accessKey' => env('QINIU_ACCESS_KEY'),
'secretKey' => env('QINIU_SECRET_KEY'),
'bucket' => env('QINIU_BUCKET'),
'domain' => env('QINIU_DOMAIN'),
],
'cos' => [
'driver' => \Hyperf\Filesystem\Adapter\CosAdapterFactory::class,
'region' => env('COS_REGION'),
// overtrue/flysystem-cos ^2.0 配置如下
'credentials' => [
'appId' => env('COS_APPID'),
'secretId' => env('COS_SECRET_ID'),
'secretKey' => env('COS_SECRET_KEY'),
],
// overtrue/flysystem-cos ^3.0 配置如下
'app_id' => env('COS_APPID'),
'secret_id' => env('COS_SECRET_ID'),
'secret_key' => env('COS_SECRET_KEY'),
// 可选,如果 bucket 为私有访问请打开此项
// 'signed_url' => false,
'bucket' => env('COS_BUCKET'),
'read_from_cdn' => false,
// 'timeout' => 60,
// 'connect_timeout' => 60,
// 'cdn' => '',
// 'scheme' => 'https',
],
],
];
```

141
docs/en/graphql.md Normal file
View File

@ -0,0 +1,141 @@
# GraphQL
The GraphQL component abstracts [thecodingmachine/graphqlite](https://github.com/thecodingmachine/graphqlite).
## Install
```bash
composer require hyperf/graphql
```
## Quick start
### Simple query
```php
<?php
namespace App\Controller;
use GraphQL\GraphQL;
use GraphQL\Type\Schema;
use Hyperf\Di\Annotation\Inject;
use Hyperf\GraphQL\Annotation\Query;
use Hyperf\HttpServer\Annotation\Controller;
use Hyperf\HttpServer\Annotation\PostMapping;
use Hyperf\HttpServer\Contract\RequestInterface;
#[Controller]
class GraphQLController
{
/**
* @var Schema
*/
#[Inject]
protected $schema;
#[PostMapping(path: "/graphql")]
public function test(RequestInterface $request)
{
$rawInput = $request->getBody()->getContents();
$input = json_decode($rawInput, true);
$query = $input['query'];
$variableValues = isset($input['variables']) ? $input['variables'] : null;
return GraphQL::executeQuery($this->schema, $query, null, null, $variableValues)->toArray();
}
#[Query]
public function hello(string $name): string
{
return $name;
}
}
```
Inquire:
```graphql
{
hello(name: "graphql")
}
```
Response:
```json
{
"data": {
"hello": "graphql"
}
}
```
### Typemap
```php
<?php
namespace App\Model;
use Hyperf\GraphQL\Annotation\Type;
use Hyperf\GraphQL\Annotation\Field;
#[Type]
class Product
{
protected $name;
protected $price;
public function __construct(string $name, float $price)
{
$this->name = $name;
$this->price = $price;
}
#[Field]
public function getName(): string
{
return $this->name;
}
#[Field]
public function getPrice(): ?float
{
return $this->price;
}
}
```
Add in `GraphQLController`
```php
<?php
use App\Model\Product;
use Hyperf\GraphQL\Annotation\Query;
#[Query]
public function product(string $name, float $price): Product
{
return new Product($name, $price);
}
```
Inquire:
```graphql
{
hello(name: "graphql")
product(name: "goods", price: 156.5) {
name
price
}
}
```
response:
```json
{
"data": {
"hello": "graphql",
"product": {
"name": "goods",
"price": 156.5
}
}
}
```
For more usage methods, see the documentation of [GraphQLite](https://graphqlite.thecodingmachine.io/docs/queries).

BIN
docs/en/imgs/grafana.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 34 KiB

413
docs/en/metric.md Normal file
View File

@ -0,0 +1,413 @@
# Service monitoring
A core requirement of microservice governance is service observability. As a shepherd of microservices, it is not easy to keep track of the health status of various services. Many solutions have emerged in this field in the cloud-native era. This component abstracts telemetry and monitoring, the important pillars of observability, to allow users to quickly integrate with existing infrastructure while avoiding vendor lock-in.
## Install
### Install components via Composer
```bash
composer require hyperf/metric
```
The [hyperf/metric](https://github.com/hyperf/metric) component has [Prometheus](https://prometheus.io/) dependencies installed by default. If you want to use [StatsD](https://github.com/statsd/statsd) or [InfluxDB](http://influxdb.com), you also need to execute the following commands to install the corresponding dependencies:
```bash
# StatsD required dependencies
composer require domnikl/statsd
# InfluxDB required dependencies
composer require influxdb/influxdb-php
```
### Add component configuration
If the file does not exist, execute the following command to add the `config/autoload/metric.php` configuration file:
```bash
php bin/hyperf.php vendor:publish hyperf/metric
```
## use
### Configuration
#### options
`default`: The value corresponding to `default` in the configuration file is the driver name used. The specific configuration of the driver is defined under `metric`, using the same driver as `key`.
```php
'default' => env('METRIC_DRIVER', 'prometheus'),
```
* `use_standalone_process`: Whether to use `standalone monitoring process`. It is recommended to enable. Metric collection and reporting will be handled in the `Worker process` after shutdown.
```php
'use_standalone_process' => env('TELEMETRY_USE_STANDALONE_PROCESS', true),
```
* `enable_default_metric`: Whether to count default metrics. Default metrics include memory usage, system CPU load, and Swoole Server and Swoole Coroutine metrics.
```php
'enable_default_metric' => env('TELEMETRY_ENABLE_DEFAULT_TELEMETRY', true),
```
`default_metric_interval`: The default metric push interval, in seconds, the same below.
```php
'default_metric_interval' => env('DEFAULT_METRIC_INTERVAL', 5),
```
#### Configuring Prometheus
When using Prometheus, add the specific configuration of Prometheus to the `metric` item in the configuration file.
```php
use Hyperf\Metric\Adapter\Prometheus\Constants;
return [
'default' => env('METRIC_DRIVER', 'prometheus'),
'use_standalone_process' => env('TELEMETRY_USE_STANDALONE_PROCESS', true),
'enable_default_metric' => env('TELEMETRY_ENABLE_DEFAULT_TELEMETRY', true),
'default_metric_interval' => env('DEFAULT_METRIC_INTERVAL', 5),
'metric' => [
'prometheus' => [
'driver' => Hyperf\Metric\Adapter\Prometheus\MetricFactory::class,
'mode' => Constants::SCRAPE_MODE,
'namespace' => env('APP_NAME', 'skeleton'),
'scrape_host' => env('PROMETHEUS_SCRAPE_HOST', '0.0.0.0'),
'scrape_port' => env('PROMETHEUS_SCRAPE_PORT', '9502'),
'scrape_path' => env('PROMETHEUS_SCRAPE_PATH', '/metrics'),
'push_host' => env('PROMETHEUS_PUSH_HOST', '0.0.0.0'),
'push_port' => env('PROMETHEUS_PUSH_PORT', '9091'),
'push_interval' => env('PROMETHEUS_PUSH_INTERVAL', 5),
],
],
];
```
Prometheus has two working modes, crawl mode and push mode (via Prometheus Pushgateway ), which can be supported by this component.
When using the crawl mode (Prometheus official recommendation), you need to set:
```php
'mode' => Constants::SCRAPE_MODE
```
And configure the crawling address `scrape_host`, the crawling port `scrape_port`, and the crawling path `scrape_path`. Prometheus can pull all metrics in the form of HTTP access under the corresponding configuration.
> Note: In crawl mode, standalone process must be enabled, ie use_standalone_process = true.
When using push mode, you need to set:
```php
'mode' => Constants::PUSH_MODE
```
And configure the push address `push_host`, push port `push_port`, push interval `push_interval`. Push mode is only recommended for offline tasks.
Because of the differences in basic settings, the above modes may not meet the needs. This component also supports custom mode. In the custom mode, the component is only responsible for the collection of indicators, and the specific reporting needs to be handled by the user.
```php
'mode' => Constants::CUSTOM_MODE
```
For example, you may want to report metrics through custom routes, or store metrics in Redis, and other independent services are responsible for centralized reporting of metrics, etc. The [custom escalation](#custom escalation) section contains corresponding examples.
#### Configure StatsD
When using StatsD, add the specific configuration of StatsD to the `metric` item in the configuration file.
```php
return [
'default' => env('METRIC_DRIVER', 'statd'),
'use_standalone_process' => env('TELEMETRY_USE_STANDALONE_PROCESS', true),
'enable_default_metric' => env('TELEMETRY_ENABLE_DEFAULT_TELEMETRY', true),
'metric' => [
'statsd' => [
'driver' => Hyperf\Metric\Adapter\StatsD\MetricFactory::class,
'namespace' => env('APP_NAME', 'skeleton'),
'udp_host' => env('STATSD_UDP_HOST', '127.0.0.1'),
'udp_port' => env('STATSD_UDP_PORT', '8125'),
'enable_batch' => env('STATSD_ENABLE_BATCH', true),
'push_interval' => env('STATSD_PUSH_INTERVAL', 5),
'sample_rate' => env('STATSD_SAMPLE_RATE', 1.0),
],
],
];
```
StatsD currently only supports UDP mode, you need to configure UDP address `udp_host`, UDP port `udp_port`, whether to batch push `enable_batch` (reduce the number of requests), batch push interval `push_interval` and sample rate `sample_rate` .
#### Configuring InfluxDB
When using InfluxDB, add the specific configuration of InfluxDB to the `metric` item in the configuration file.
```php
return [
'default' => env('METRIC_DRIVER', 'influxdb'),
'use_standalone_process' => env('TELEMETRY_USE_STANDALONE_PROCESS', true),
'enable_default_metric' => env('TELEMETRY_ENABLE_DEFAULT_TELEMETRY', true),
'metric' => [
'influxdb' => [
'driver' => Hyperf\Metric\Adapter\InfluxDB\MetricFactory::class,
'namespace' => env('APP_NAME', 'skeleton'),
'host' => env('INFLUXDB_HOST', '127.0.0.1'),
'port' => env('INFLUXDB_PORT', '8086'),
'username' => env('INFLUXDB_USERNAME', ''),
'password' => env('INFLUXDB_PASSWORD', ''),
'dbname' => env('INFLUXDB_DBNAME', true),
'push_interval' => env('INFLUXDB_PUSH_INTERVAL', 5),
],
],
];
```
InfluxDB uses the default HTTP mode, you need to configure the address `host`, UDP port `port`, username `username`, password `password`, `dbname` data table and batch push interval `push_interval`.
### Basic abstraction
The telemetry component abstracts three commonly used data types to ensure decoupling of concrete implementations.
The three types are:
Counter (Counter): An indicator used to describe one-way increments. Such as HTTP request count.
```php
interface CounterInterface
{
public function with(string ...$labelValues): self;
public function add(int $delta);
}
```
Gauge: An indicator used to describe an increase or decrease over time. Such as the number of available connections in the connection pool.
```php
interface GaugeInterface
{
public function with(string ...$labelValues): self;
public function set(float $value);
public function add(float $delta);
}
```
* Histogram: used to describe the statistical distribution produced by continuous observation of an event, usually expressed as percentiles or buckets. Such as HTTP request delay.
```php
interface HistogramInterface
{
public function with(string ...$labelValues): self;
public function put(float $sample);
}
```
### Configure middleware
After configuring the driver, you only need to configure the middleware to enable the request Histogram statistics function.
Open the `config/autoload/middlewares.php` file, the example is to enable middleware in `http` Server.
```php
<?php
declare(strict_types=1);
return [
'http' => [
\Hyperf\Metric\Middleware\MetricMiddleware::class,
],
];
```
> The statistics dimension in this middleware includes `request_status`, `request_path`, `request_method`. If your `request_path` is too large, it is recommended to rewrite this middleware to remove the `request_path` dimension, otherwise the high cardinality will cause memory overflow.
### Custom use
Telemetry via HTTP middleware is just the tip of the iceberg of what this component can do. You can inject the `Hyperf\Metric\Contract\MetricFactoryInterface` class to telemetry business data yourself. For example: the number of orders created, the number of clicks on ads, etc.
```php
<?php
declare(strict_types=1);
namespace App\Controller;
use App\Model\Order;
use Hyperf\Di\Annotation\Inject;
use Hyperf\Metric\Contract\MetricFactoryInterface;
class IndexController extends AbstractController
{
/**
* @var MetricFactoryInterface
*/
#[Inject]
private $metricFactory;
public function create(Order $order)
{
$counter = $this->metricFactory->makeCounter('order_created', ['order_type']);
$counter->with($order->type)->add(1);
// order logic...
}
}
```
`MetricFactoryInterface` contains the following factory methods to generate the corresponding three basic statistic types.
```php
public function makeCounter($name, $labelNames): CounterInterface;
public function makeGauge($name, $labelNames): GaugeInterface;
public function makeHistogram($name, $labelNames): HistogramInterface;
```
The above example is the generated metrics within the scope of the statistical request. Sometimes the indicators we need to count are for the complete life cycle, such as counting the length of asynchronous queues or the number of items in stock. In this scenario, you can listen to the `MetricFactoryReady` event.
```php
<?php
declare(strict_types=1);
namespace App\Listener;
use Hyperf\Event\Contract\ListenerInterface;
use Hyperf\Metric\Event\MetricFactoryReady;
use Psr\Container\ContainerInterface;
use Redis;
class OnMetricFactoryReady implements ListenerInterface
{
protected ContainerInterface $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function listen(): array
{
return [
MetricFactoryReady::class,
];
}
public function process(object $event)
{
$redis = $this->container->get(Redis::class);
$gauge = $event
->factory
->makeGauge('queue_length', ['driver'])
->with('redis');
while (true) {
$length = $redis->llen('queue');
$gauge->set($length);
sleep(1);
}
}
}
```
> In terms of engineering, it is not suitable to query the queue length directly from Redis. The queue length should be obtained through the `info()` method under the `DriverInterface` interface of the queue driver. Just a simple demonstration here. You can find a complete example in the `src/Listener` folder of the component's source code.
### Notes
You can use `@Counter(name="stat_name_here")` and `@Histogram(name="stat_name_here")` to count the invocation and running time of the aspect.
For the use of annotations, please refer to the [Annotation Chapter](zh-cn/annotation).
### Custom Histogram Bucket
> This section only applies to Prometheus drivers
When you are using Prometheus's Histogram, sometimes there is a need for a custom Bucket. Before starting the service, you can inject the dependency into the Registry and register the Histogram by yourself, and set the required Bucket . When you use it later, `MetricFactory` will call you to register the Histogram of the same name. An example is as follows:
```php
<?php
namespace App\Listener;
use Hyperf\Config\Annotation\Value;
use Hyperf\Event\Contract\ListenerInterface;
use Hyperf\Framework\Event\BeforeMainServerStart;
use Prometheus\CollectorRegistry;
class OnMainServerStart implements ListenerInterface
{
protected $registry;
public function __construct(CollectorRegistry $registry)
{
$this->registry = $registry;
}
public function listen(): array
{
return [
BeforeMainServerStart::class,
];
}
public function process(object $event)
{
$this->registry->registerHistogram(
config("metric.metric.prometheus.namespace"),
'test',
'help_message',
['labelName'],
[0.1, 1, 2, 3.5]
);
}
}
```
After that, when you use `$metricFactory->makeHistogram('test')`, the returned Histogram is your pre-registered Histogram.
### Custom report
> This section only applies to Prometheus drivers
After setting the component's Promethues driver working mode to the custom mode ( `Constants::CUSTOM_MODE` ), you can freely handle indicator reporting. In this section, we show how to store metrics in Redis, then add a new HTTP route to the Worker that returns Prometheus-rendered metrics.
#### Storing metrics with Redis
The storage medium for metrics is defined by the `Prometheus\Storage\Adapter` interface. Memory storage is used by default. We can change to Redis storage in `config/autoload/dependencies.php`.
```php
<?php
return [
Prometheus\Storage\Adapter::class => Hyperf\Metric\Adapter\Prometheus\RedisStorageFactory::class,
];
```
#### Add /metrics route to Worker
Add Prometheus routes in config/routes.php.
> Note that if you want to get metrics under Workers, you need to handle the state sharing between Workers yourself. One way is to store the state in Redis as described above.
```php
<?php
use Hyperf\HttpServer\Router\Router;
Router::get('/metrics', function(){
$registry = Hyperf\Utils\ApplicationContext::getContainer()->get(Prometheus\CollectorRegistry::class);
$renderer = new Prometheus\RenderTextFormat();
return $renderer->render($registry->getMetricFamilySamples());
});
```
## Create console in Grafana
> This section only applies to Prometheus drivers
If you have default metrics enabled, `Hyperf/Metric` prepares a Grafana console for you out of the box. Download the console [json file](https://cdn.jsdelivr.net/gh/hyperf/hyperf/src/metric/grafana.json), import it into Grafana and use it.
![grafana](imgs/grafana.png)
## Precautions
- To use this component to collect metrics in a `hyperf/command` custom command, you need to add the command line parameter: `--enable-event-dispatcher` when starting the command.

View File

@ -1,14 +1,14 @@
# Nacos #Nacos
一个 `Nacos``PHP` 协程客户端,与 `Hyperf` 的配置中心、微服务治理完美结合。 A `PHP` coroutine client of `Nacos`, perfectly combined with the configuration center and microservice governance of `Hyperf`.
## 安装 ## Install
```shell ```shell
composer require hyperf/nacos composer require hyperf/nacos
``` ```
### 发布配置文件 ### publish profile
```shell ```shell
php bin/hyperf.php vendor:publish hyperf/nacos php bin/hyperf.php vendor:publish hyperf/nacos
@ -20,7 +20,7 @@ php bin/hyperf.php vendor:publish hyperf/nacos
declare(strict_types=1); declare(strict_types=1);
return [ return [
// 无法使用 IP 端口形式的开发者,直接配置 url 即可 // Developers who cannot use the IP port form can directly configure the url
// 'url' => '', // 'url' => '',
'host' => '127.0.0.1', 'host' => '127.0.0.1',
'port' => 8848, 'port' => 8848,
@ -33,17 +33,17 @@ return [
``` ```
## 服务与实例 ## Services and instances
当前组件仍然保留了之前提供的服务注册功能。 The current component still retains the previously provided service registration functionality.
只需要安装 `hyperf/service-governance-nacos` 组件,然后配置以下监听器和自定义进程即可。 Just install the `hyperf/service-governance-nacos` component, then configure the following listeners and custom processes.
`Hyperf\ServiceGovernanceNacos\Listener\MainWorkerStartListener` `Hyperf\ServiceGovernanceNacos\Listener\MainWorkerStartListener`
`Hyperf\ServiceGovernanceNacos\Listener\OnShutdownListener` `Hyperf\ServiceGovernanceNacos\Listener\OnShutdownListener`
`Hyperf\ServiceGovernanceNacos\Process\InstanceBeatProcess` `Hyperf\ServiceGovernanceNacos\Process\InstanceBeatProcess`
然后增加如下配置,以监听 `Shutdown` 事件 Then add the following configuration to listen to the `Shutdown` event
- config/autoload/server.php - config/autoload/server.php

140
docs/en/nats.md Normal file
View File

@ -0,0 +1,140 @@
# NATS
NATS is an open source, lightweight, high-performance distributed messaging middleware that implements high scalability and an elegant `Publish` / `Subscribe` model, developed using the `Golang` language. The development philosophy of NATS believes that high-quality QoS should be built on the client side, so only `Request-Reply` is established, and it does not provide 1. Persistence 2. Transaction processing 3. Enhanced delivery mode 4. Enterprise-level queue.
## Install
```bash
composer require hyperf/nats
```
## use
### create consumer
```
php bin/hyperf.php gen:nats-consumer DemoConsumer
```
If `queue` is set, the same `subject` will only be consumed by one `queue`. If `queue` is not set, each consumer will receive the message.
```php
<?php
declare(strict_types=1);
namespace App\Nats\Consumer;
use Hyperf\Nats\AbstractConsumer;
use Hyperf\Nats\Annotation\Consumer;
use Hyperf\Nats\Message;
#[Consumer(subject: 'hyperf.demo', queue: 'hyperf.demo', name: 'DemoConsumer', nums: 1)]
class DemoConsumer extends AbstractConsumer
{
public function consume(Message $payload)
{
// Do something...
}
}
```
### Post message
Use publish to deliver messages.
```php
<?php
declare(strict_types=1);
namespace App\Controller;
use Hyperf\Di\Annotation\Inject;
use Hyperf\HttpServer\Annotation\AutoController;
use Hyperf\Nats\Driver\DriverInterface;
#[AutoController(prefix: "nats")]
class NatsController extends AbstractController
{
#[Inject]
protected DriverInterface $nats;
public function publish()
{
$res = $this->nats->publish('hyperf.demo', [
'id' => 'Hyperf',
]);
return $this->response->success($res);
}
}
```
Use request to deliver messages.
```php
<?php
declare(strict_types=1);
namespace App\Controller;
use Hyperf\Di\Annotation\Inject;
use Hyperf\HttpServer\Annotation\AutoController;
use Hyperf\Nats\Driver\DriverInterface;
use Hyperf\Nats\Message;
#[AutoController(prefix: "nats")]
class NatsController extends AbstractController
{
#[Inject]
protected DriverInterface $nats;
public function request()
{
$res = $this->nats->request('hyperf.reply', [
'id' => 'limx',
], function (Message $payload) {
var_dump($payload->getBody());
});
return $this->response->success($res);
}
}
```
Use requestSync to deliver messages.
```php
<?php
declare(strict_types=1);
namespace App\Controller;
use Hyperf\Di\Annotation\Inject;
use Hyperf\HttpServer\Annotation\AutoController;
use Hyperf\Nats\Driver\DriverInterface;
use Hyperf\Nats\Message;
#[AutoController(prefix: "nats")]
class NatsController extends AbstractController
{
#[Inject]
protected DriverInterface $nats;
public function sync()
{
/** @var Message $message */
$message = $this->nats->requestSync('hyperf.reply', [
'id' => 'limx',
]);
return $this->response->success($message->getBody());
}
}
```

337
docs/en/reactive-x.md Normal file
View File

@ -0,0 +1,337 @@
# ReactiveX integration
The [hyperf/reactive-x](https://github.com/hyperf/reactive-x) component provides ReactiveX integration in the Swoole/Hyperf environment.
## History of ReactiveX
ReactiveX is the abbreviation of Reactive Extensions, generally abbreviated as Rx. It was originally an extension of LINQ. It was developed by a team led by Microsoft architect Erik Meijer. It was open sourced in November 2012. Rx is a programming model. The goal is to provide consistent programming Interface to help developers handle asynchronous data streams more easily. Rx library supports .NET, JavaScript and C++. Rx has become more and more popular in recent years, and now it supports almost all popular programming languages. Most of Rx The language library is maintained by the ReactiveX organization, the more popular ones are RxJava/RxJS/Rx.NET, and the community website is [reactivex.io](http://reactivex.io).
## What is ReactiveX
Microsoft's definition is that Rx is a function library that allows developers to write asynchronous and event-based programs using observable sequences and LINQ-style query operators. Using Rx, developers can use Observables to represent asynchronous data streams, and LINQ Operators query asynchronous data streams, and use Schedulers to parameterize concurrent processing of asynchronous data streams. Rx can be defined as follows: Rx = Observables + LINQ + Schedulers.
The definition given by [Reactivex.io](http://reactivex.io) is that Rx is a programming interface for asynchronous programming using observable data streams. ReactiveX combines the essence of observer pattern, iterator pattern and functional programming .
> The above two sections are taken from [RxDocs](https://github.com/mcxiaoke/RxDocs).
## Please consider before using
### front
- By thinking of reactive programming, some complex asynchronous problems can be simplified.
- If you already have reactive programming experience in other languages (such as RxJS/RxJava), this component can help you port this experience to Hyperf.
- Although Swoole recommends writing asynchronous programs like synchronous programs through coroutines, Swoole still contains a large number of events, and handling events is the strength of Rx.
- Rx can also play an important role if your business includes stream processing like WebSocket, gRPC streaming, etc.
### Negative
- The way of thinking of reactive programming is quite different from the traditional object-oriented way of thinking, which requires developers to adapt.
- Rx just provides the way of thinking, no additional magic. Problems that can be solved by reactive programming can be solved by traditional means.
- RxPHP is not the best in the Rx family.
## Install
```bash
composer require hyperf/reactive-x
```
## Package
Let us introduce some encapsulations of this component with examples and demonstrate the powerful capabilities of Rx. All examples can be found in this component under `src/Example`.
### Observable::fromEvent
`Observable::fromEvent` converts PSR standard events into observable sequences.
The event listener for printing SQL statements is provided by default in the hyperf-skeleton skeleton package, and the default location is `app/Listener/DbQueryExecutedListener.php`. Let's make some optimizations to this monitor:
1. Only print SQL queries that take more than 100ms.
2. Each connection can print up to 1 time per second to avoid the hard disk being overrun by the problem program.
Without ReactiveX, question 1 would be fine, but question 2 would require some brainstorming. With ReactiveX, these requirements can be easily solved by means of the following sample code:
```php
<?php
declare(strict_types=1);
namespace Hyperf\ReactiveX\Example;
use Hyperf\Database\Events\QueryExecuted;
use Hyperf\Event\Contract\ListenerInterface;
use Hyperf\Framework\Event\BeforeWorkerStart;
use Hyperf\Logger\LoggerFactory;
use Hyperf\ReactiveX\Observable;
use Hyperf\Utils\Arr;
use Hyperf\Utils\Str;
use Psr\Container\ContainerInterface;
class SqlListener implements ListenerInterface
{
private $logger;
public function __construct(ContainerInterface $container)
{
$this->logger = $container->get(LoggerFactory::class)->get('sql');
}
public function listen(): array
{
return [
BeforeWorkerStart::class,
];
}
public function process(object $event)
{
Observable::fromEvent(QueryExecuted::class)
->filter(
function ($event) {
return $event->time > 100;
}
)
->groupBy(
function ($event) {
return $event->connectionName;
}
)
->flatMap(
function ($group) {
return $group->throttle(1000);
}
)
->map(
function ($event) {
$sql = $event->sql;
if (! Arr::isAssoc($event->bindings)) {
foreach ($event->bindings as $key => $value) {
$sql = Str::replaceFirst('?', "'{$value}'", $sql);
}
}
return [$event->connectionName, $event->time, $sql];
}
)->subscribe(
function ($message) {
$this->logger->info(sprintf('slow log: [%s] [%s] %s', ...$message));
}
);
}
}
```
### Observable::fromChannel
Turn the Channel in the Swoole coroutine into an observable sequence.
The Channel in the Swoole coroutine is one-to-one read and write. What if we want to do many-to-many subscriptions and publishing through Channels under ReactiveX?
See the example below.
```php
<?php
declare(strict_types=1);
use Hyperf\ReactiveX\Observable;
use Swoole\Coroutine\Channel;
$chan = new Channel(1);
$pub = Observable::fromChannel($chan)->publish();
$pub->subscribe(function ($x) {
echo 'First Subscription:' . $x . PHP_EOL;
});
$pub->subscribe(function ($x) {
echo 'Second Subscription:' . $x . PHP_EOL;
});
$pub->connect();
$chan->push('hello');
$chan->push('world');
// First Subscription: hello
// Second Subscription: hello
// First Subscription: world
// Second Subscription: world
```
### Observable::fromCoroutine
Create one or more coroutines and turn the execution results into an observable sequence.
We now let two functions compete in concurrent coroutines, and whichever finishes executing first returns the result. The effect is similar to `Promise.race` in JavaScript.
```php
<?php
declare(strict_types=1);
use Hyperf\ReactiveX\Observable;
use Swoole\Coroutine\Channel;
$result = new Channel(1);
$o = Observable::fromCoroutine([function () {
sleep(2);
return 1;
}, function () {
sleep(1);
return 2;
}]);
$o->take(1)->subscribe(
function ($x) use ($result) {
$result->push($x);
}
);
echo $result->pop(); // 2;
```
### Observable::fromHttpRoute
All HTTP requests are actually event-driven. So HTTP request routing can also be taken over with ReactiveX.
> Since we are going to add a route, it must be executed before the Server starts, such as in the `BootApplication` event listener.
Suppose we have an upload route with a lot of traffic, which needs to be buffered in memory and uploaded in batches after ten uploads.
```php
<?php
declare(strict_types=1);
namespace Hyperf\ReactiveX\Example;
use Hyperf\Database\Events\QueryExecuted;
use Hyperf\Event\Contract\ListenerInterface;
use Hyperf\Framework\Event\BootApplication;
use Hyperf\ReactiveX\Observable;
use Psr\Http\Message\RequestInterface;
class BatchSaveRoute implements ListenerInterface
{
public function listen(): array
{
return [
BootApplication::class,
];
}
/**
* @param QueryExecuted $event
*/
public function process(object $event)
{
Observable::fromHttpRoute(['POST', 'PUT'], '/save')
->map(
function (RequestInterface $request) {
return $request->getBody();
}
)
->bufferWithCount(10)
->subscribe(
function (array $bodies) {
echo count($bodies); //10
}
);
}
}
```
After taking over the route, if you need to control the returned Response, you can add a third parameter to fromHttpRoute, which is the same as the normal route, such as
```php
$observable = Observable::fromHttpRoute('GET', '/hello-hyperf', 'App\Controller\IndexController::hello');
```
At this point, `Observable` acts like middleware. After obtaining the observable sequence of the request object, it will continue to pass the request object to the real `Controller`.
### IpcSubject
Swoole's inter-process communication is also event-driven. This component additionally provides the corresponding cross-process Subject version on the basis of the four [Subject](https://mcxiaoke.gitbooks.io/rxdocs/content/Subject.html) provided by RxPHP, which can be used to share information between processes .
For example, we need to make a WebSocket-based chat room, the requirements are as follows:
1. Chat room messages need to be shared between `Worker processes`.
2. The last 5 messages are displayed when the user logs in for the first time.
We do this via a cross-process version of `ReplaySubject`.
```php
<?php
declare(strict_types=1);
namespace Hyperf\ReactiveX\Example;
use Hyperf\Contract\OnCloseInterface;
use Hyperf\Contract\OnMessageInterface;
use Hyperf\Contract\OnOpenInterface;
use Hyperf\ReactiveX\Contract\BroadcasterInterface;
use Hyperf\ReactiveX\IpcSubject;
use Rx\Subject\ReplaySubject;
use Swoole\Http\Request;
use Swoole\Server;
use Swoole\WebSocket\Frame;
use Swoole\WebSocket\Server as WebSocketServer;
class WebSocketController implements OnMessageInterface, OnOpenInterface, OnCloseInterface
{
private IpcSubject $subject;
private $subscriber = [];
public function __construct(BroadcasterInterface $broadcaster)
{
$relaySubject = make(ReplaySubject::class, ['bufferSize' => 5]);
// The first parameter is the original RxPHP Subject object.
// The second parameter is the broadcast mode, the default is the whole process broadcast
// The third parameter is the channel ID, each channel can only receive messages from the same channel.
$this->subject = new IpcSubject($relaySubject, $broadcaster, 1);
}
public function onMessage(WebSocketServer $server, Frame $frame): void
{
$this->subject->onNext($frame->data);
}
public function onClose(Server $server, int $fd, int $reactorId): void
{
$this->subscriber[$fd]->dispose();
}
public function onOpen(WebSocketServer $server, Request $request): void
{
$this->subscriber[$request->fd] = $this->subject->subscribe(function ($data) use ($server, $request) {
$server->push($request->fd, $data);
});
}
}
```
For convenience, this component uses `IpcSubject` to encapsulate a "message bus" `MessageBusInterface`. Just inject `MessageBusInterface` to send and receive information shared by all processes (including custom processes). Functions such as configuration center can be easily implemented through it.
```php
<?php
$bus = make(Hyperf\ReactiveX\MessageBusInterface::class);
// whole process broadcast information
$bus->onNext('Hello Hyperf');
// subscription info
$bus->subscribe(function($message){
echo $message;
});
```
> Since ReactiveX needs to use the event loop, please note that the ReactiveX related API must be called after the Swoole Server is started.
## References
* [Rx Chinese Documentation](https://mcxiaoke.gitbooks.io/rxdocs/content/)
* [Rx documentation in English](http://reactivex.io/)
* [RxPHP repository](https://github.com/ReactiveX/RxPHP)

View File

@ -2,14 +2,14 @@
## Life cycle ## Life cycle
| Version | Status | End of mainstream support | End of Security-fixes support | Release Date (or estimated date) | | Version | Status | End of mainstream support | End of Security-fixes support | Release Date (or estimated date) |
| ------- | ------------------ | ------------------------- | ----------------------------- | -------------------------------- | | ------- |-------------------------------------|---------------------------|-------------------------------|----------------------------------|
| 3.0 | Developing (RC1 has been released) | 2023-06-20 | 2023-12-31 | 2022-08-30~2022-09-30 | | 3.0 | Developing (RC13 has been released) | 2023-06-30 | 2023-12-31 | About 2022-12-31 |
| 2.2 | Mainstream Support | 2022-06-20 | 2022-12-31 | 2021-07-19 | | 2.2 | Security fixes support | 2022-06-20 | 2023-6-30 | 2021-07-19 |
| 2.1 | Deprecated | 2021-06-30 | 2021-12-31 | 2020-12-28 | | 2.1 | Deprecated | 2021-06-30 | 2021-12-31 | 2020-12-28 |
| 2.0 | Deprecated | 2020-12-28 | 2021-06-30 | 2020-06-22 | | 2.0 | Deprecated | 2020-12-28 | 2021-06-30 | 2020-06-22 |
| 1.1 | Deprecated | 2020-06-23 | 2020-12-31 | 2019-10-08 | | 1.1 | Deprecated | 2020-06-23 | 2020-12-31 | 2019-10-08 |
| 1.0 | Deprecated | 2019-10-08 | 2019-12-31 | 2019-06-20 | | 1.0 | Deprecated | 2019-10-08 | 2019-12-31 | 2019-06-20 |
* Mainstream support including BUG fixes, security fixes, function upgrade and new functions support in a regular iteration cycles; * Mainstream support including BUG fixes, security fixes, function upgrade and new functions support in a regular iteration cycles;
* Security fixes support only includes the fixes of security issues; * Security fixes support only includes the fixes of security issues;

330
docs/en/retry.md Normal file
View File

@ -0,0 +1,330 @@
# Retry
Network communication is inherently unstable, so in a distributed system, a good fault-tolerant design is required. Indiscriminate retry is very dangerous. When there is a problem with communication, each request is retried once, which is equivalent to a 100% increase in system IO load, which is easy to induce avalanche accidents. Retrying also considers the cause of the error. If it is a problem that cannot be solved by retrying, then retrying is just a waste of resources. In addition, if the retrying interface is not idempotent, it may also cause data inconsistency and other problems.
This component provides a rich retry mechanism to meet the retry requirements of various scenarios.
## Install
```bash
composer require hyperf/retry
```
## Hello World
Add the annotation `#[Retry]` to the method that needs to be retried.
```php
/**
* Retry the method on exception
*/
#[Retry]
public function foo()
{
// make a remote call
}
```
The default Retry strategy can meet most daily retry needs without excessive retries causing avalanches.
## Deep customization
This component achieves pluggability by combining multiple retry strategies. Each strategy focuses on different aspects of the retry process, such as retry judgment, retry interval, and result processing. By adjusting the strategy used in the annotation, you can configure the retry aspect suitable for any scenario.
It is recommended to construct your own annotation aliases according to specific business needs. Below we demonstrate how to make a new annotation with a maximum number of attempts of 3.
> In the default `Retry` annotation, you can control the maximum number of retries with `@Retry(maxAttempts=3)`. For the sake of demonstration, pretend it doesn't exist.
First you need to create an `annotation class` and inherit `\Hyperf\Retry\Annotations\AbstractRetry`.
```php
<?php
declare(strict_types=1);
namespace App\Annotation;
use Doctrine\Common\Annotations\Annotation\Target;
#[Attribute(Attribute::TARGET_METHOD)]
class MyRetry extends \Hyperf\Retry\Annotation\AbstractRetry
{
}
```
Override the `$policies` property according to your needs. To limit the number of retries, use `MaxAttemptsRetryPolicy` . `MaxAttemptsRetryPolicy` also needs a parameter, which is the limit of the maximum number of attempts, `$maxAttempts`. Add these two properties to the above class.
```php
<?php
declare(strict_types=1);
namespace App\Annotation;
use Doctrine\Common\Annotations\Annotation\Target;
#[Attribute(Attribute::TARGET_METHOD)]
class MyRetry extends \Hyperf\Retry\Annotation\AbstractRetry
{
public $policies = [
MaxAttemptsRetryPolicy::class,
];
public $maxAttempts = 3;
}
```
Now that the `@MyRetry` annotation will cause any method to be executed three times in a loop, we also need to add a new policy `ClassifierRetryPolicy` to control what errors can be retried. Adding `ClassifierRetryPolicy` will only retry after throwing `Throwable` by default.
```php
<?php
declare(strict_types=1);
namespace App\Annotation;
use Doctrine\Common\Annotations\Annotation\Target;
#[Attribute(Attribute::TARGET_METHOD)]
class MyRetry extends \Hyperf\Retry\Annotation\AbstractRetry
{
public $policies = [
MaxAttemptsRetryPolicy::class,
ClassifierRetryPolicy::class,
];
public $maxAttempts = 3;
}
```
You can continue to refine the annotation until it meets your customized needs. For example, configure to retry only user-defined `TimeoutException` , and use retry to sleep at least 100ms of variable length interval, as follows:
```php
<?php
declare(strict_types=1);
namespace App\Annotation;
use Doctrine\Common\Annotations\Annotation\Target;
#[Attribute(Attribute::TARGET_METHOD)]
class MyRetry extends \Hyperf\Retry\Annotation\Retry
{
public $policies = [
MaxAttemptsRetryPolicy::class,
ClassifierRetryPolicy::class,
SleepRetryPolicy::class,
];
public $maxAttempts = 3;
public $base = 100;
public $strategy = \Hyperf\Retry\BackoffStrategy::class;
public $retryThrowables = [\App\Exception\TimeoutException::class];
}
```
Just make sure the file is scanned by Hyperf, you can use the `@MyRetry` annotation in the method to retry timeout errors.
## default allocation
The full annotation default properties of `@Retry` are as follows:
```php
/**
* Array of retry policies. Think of these as stacked middlewares.
* @var string[]
*/
public $policies = [
FallbackRetryPolicy::class,
ClassifierRetryPolicy::class,
BudgetRetryPolicy::class,
MaxAttemptsRetryPolicy::class,
SleepRetryPolicy::class,
];
/**
* The algorithm for retry intervals.
*/
public string $sleepStrategyClass = SleepStrategyInterface::class;
/**
* Max Attampts.
*/
public int $maxAttempts = 10;
/**
* Retry Budget.
* ttl: Seconds of token lifetime.
* minRetriesPerSec: Base retry token generation speed.
* percentCanRetry: Generate new token at this ratio of the request volume.
*
* @var array|RetryBudgetInterface
*/
public $retryBudget = [
'ttl' => 10,
'minRetriesPerSec' => 1,
'percentCanRetry' => 0.2,
];
/**
* Base time inteval (ms) for each try. For backoff strategy this is the interval for the first try
* while for flat strategy this is the interval for every try.
*/
public int $base = 0;
/**
* Configures a Predicate which evaluates if an exception should be retried.
* The Predicate must return true if the exception should be retried, otherwise it must return false.
*
* @var callable|string
*/
public $retryOnThrowablePredicate = '';
/**
* Configures a Predicate which evaluates if an result should be retried.
* The Predicate must return true if the result should be retried, otherwise it must return false.
*
* @var callable|string
*/
public $retryOnResultPredicate = '';
/**
* Configures a list of Throwable classes that are recorded as a failure and thus are retried.
* Any Throwable matching or inheriting from one of the list will be retried, unless ignored via ignoreExceptions.
*
* Ignoring an Throwable has priority over retrying an exception.
*
* @var array<string|\Throwable>
*/
public $retryThrowables = [\Throwable::class];
/**
* Configures a list of error classes that are ignored and thus are not retried.
* Any exception matching or inheriting from one of the list will not be retried, even if marked via retryExceptions.
*
* @var array<string|\Throwable>
*/
public $ignoreThrowables = [];
/**
* The fallback callable when all attempts exhausted.
*
* @var callable|string
*/
public $fallback = '';
```
## optional strategies
### Maximum Attempts Policy `MaxAttemptsRetryPolicy`
| Parameters | Type | Description |
| ---------- | --- | --- |
| maxAttempts | int | Maximum number of attempts |
### Error classification policy `ClassifierRetryPolicy`
Pass the classifier to determine if the error can be retried.
| Parameters | Type | Description |
| ---------- | --- | --- |
| ignoreThrowables | array | `Throwable` class names to ignore. takes precedence over `retryThrowables` |
| retryThrowables | array | `Throwable` class names to retry. takes precedence over `retryOnThrowablePredicate` |
| retryOnThrowablePredicate | callable | Pass a function to determine if `Throwable` can be retried. Returns true if retry is possible, false otherwise. |
| retryOnResultPredicate | callable | Use a function to determine whether the return value can be retried. Returns true if it is possible to retry, false otherwise. |
### Fallback policy `FallbackRetryPolicy`
Execute alternate method after retrying resource exhaustion.
| Parameters | Type | Description |
| ---------- | --- | --- |
| fallback | callable | fallback method |
In addition to the code recognized by `is_callable`, `fallback` can also fill in the format of `class@method`, the framework will get the corresponding `class` from `Container`, and then execute its `method` method .
### Sleep policy `SleepRetryPolicy`
Provides two retry intermittent strategies. Equal retry interval (FlatStrategy) and variable retry interval (BackoffStrategy).
| Parameters | Type | Description |
| ---------- | --- | --- |
| base | int | Base sleep time (ms) |
| strategy | string | Any class name that implements `Hyperf\Retry\SleepStrategyInterface`, such as `Hyperf\Retry\BackoffStrategy` |
### Timeout policy `TimeoutRetryPolicy`
Exit the retry session after the total execution time exceeds the time.
| Parameters | Type | Description |
| ---------- | --- | --- |
| timeout | float | timeout (seconds) |
### Circuit breaker policy `CircuitBreakerRetryPolicy`
After the retry fails, the retry session is directly marked as circuit breaker for a period of time, and no more attempts will be made.
| Parameters | Type | Description |
| ---------- | --- | --- |
| circuitBreakerState.resetTimeout | float | Time required for recovery (seconds) |
### Budget Policy `BudgetRetryPolicy`
Each `@Retry` annotation will generate a corresponding token bucket, and whenever the annotation method is called, a token with an expiration time (ttl) is placed in the token bucket. If a retryable error occurs, the corresponding number of tokens (percentCanRetry) must be consumed before retrying, otherwise it will not be retried (the error continues to pass down). For example, when percentCanRetry=0.2, each retry consumes 5 tokens. In this way, when the peer is down, at most 20% of the additional retry consumption will be incurred, which should be acceptable for most systems.
To take care of some less frequently used methods, a certain number of "mini-guarantee" tokens (minRetriesPerSec) are also generated per second to ensure system stability.
| Parameters | Type | Description |
| ---------- | --- | --- |
| retryBudget.ttl | int | Recovery token expiration time (seconds) |
| retryBudget.minRetriesPerSec | int | Minimum number of retries per second for "mini-guarantee" |
| retryBudget.percentCanRetry | float | Retry times do not exceed the percentage of total requests |
> The token bucket of the retry component is not shared among workers, so the final number of retries is multiplied by the number of workers.
## Annotation alias
Because the retry annotation configuration is more complicated, some preset aliases are provided here for easy writing.
* `@RetryThrowable` only retry `Throwable`. Same as default `@Retry`.
* `@RetryFalsy` only retry errors whose return value is weakly equal to false ($result == false), not exceptions.
* `@BackoffRetryThrowable` A variable length retry interval version of `@RetryThrowable`, with a retry interval of at least 100ms.
* `@BackoffRetryFalsy` Variable length retry interval version of `@RetryFalsy`, retry interval is at least 100ms.
## Fluent chain call
In addition to using this component with annotated methods, you can also use it with regular PHP functions.
```php
<?php
$result = \Hyperf\Retry\Retry::with(
new \Hyperf\Retry\Policy\ClassifierRetryPolicy(), // Retry all Throwables by default
new \Hyperf\Retry\Policy\MaxAttemptsRetryPolicy(5) //Retry up to 5 times
)->call(function(){
if (rand(1, 100) >= 20){
return true;
}
throw new Exception;
});
```
To enhance readability, the following fluent writing can also be used.
```php
<?php
$result = \Hyperf\Retry\Retry::whenReturns(false) // Retry when false is returned
->max(3) // up to 3 times
->inSeconds(5) // up to 5 seconds
->sleep(1) // 1ms interval
->fallback(function(){return true;}) // fallback function
->call(function(){
if (rand(1, 100) >= 20){
return true;
}
return false;
});
```

View File

@ -1,18 +1,18 @@
# 基于多路复用的 RPC 组件 # Multiplexed based RPC components
本组件基于 `TCP` 协议,多路复用的设计借鉴于 `AMQP` 组件。 This component is based on the `TCP` protocol, and the multiplexing design is borrowed from the `AMQP` component.
## 安装 ## Install
``` ````
composer require hyperf/rpc-multiplex composer require hyperf/rpc-multiplex
``` ````
## Server 配置 ## Server configuration
修改 `config/autoload/server.php` 配置文件,以下配置删除了不相干的配置。 Modify the `config/autoload/server.php` configuration file, the following configuration deletes irrelevant configuration.
`settings` 设置中,分包规则不允许修改,只可以修改 `package_max_length`,此配置需要 `Server``Client` 保持一致。 In the `settings` setting, the subcontracting rules are not allowed to be modified, only `package_max_length` can be modified, this configuration needs to be consistent between `Server` and `Client`.
```php ```php
<?php <?php
@ -46,7 +46,7 @@ return [
``` ```
创建 `RpcService` Create `RpcService`
```php ```php
<?php <?php
@ -66,9 +66,9 @@ class CalculatorService implements CalculatorServiceInterface
``` ```
## 客户端配置 ## client configuration
修改 `config/autoload/services.php` 配置文件 Modify the `config/autoload/services.php` configuration file
```php ```php
<?php <?php
@ -83,7 +83,7 @@ return [
'id' => App\JsonRpc\CalculatorServiceInterface::class, 'id' => App\JsonRpc\CalculatorServiceInterface::class,
'protocol' => Hyperf\RpcMultiplex\Constant::PROTOCOL_DEFAULT, 'protocol' => Hyperf\RpcMultiplex\Constant::PROTOCOL_DEFAULT,
'load_balancer' => 'random', 'load_balancer' => 'random',
// 这个消费者要从哪个服务中心获取节点信息,如不配置则不会从服务中心获取节点信息 // Which service center does the consumer want to obtain node information from, if not configured, the node information will not be obtained from the service center
'registry' => [ 'registry' => [
'protocol' => 'consul', 'protocol' => 'consul',
'address' => 'http://127.0.0.1:8500', 'address' => 'http://127.0.0.1:8500',
@ -95,16 +95,16 @@ return [
'connect_timeout' => 5.0, 'connect_timeout' => 5.0,
'recv_timeout' => 5.0, 'recv_timeout' => 5.0,
'settings' => [ 'settings' => [
// 包体最大值,若小于 Server 返回的数据大小,则会抛出异常,故尽量控制包体大小 // The maximum value of the package body. If it is less than the data size returned by the Server, an exception will be thrown, so try to control the package body size as much as possible.
'package_max_length' => 1024 * 1024 * 2, 'package_max_length' => 1024 * 1024 * 2,
], ],
// 重试次数,默认值为 2 // number of retries, default is 2
'retry_count' => 2, 'retry_count' => 2,
// 重试间隔,毫秒 // retry interval, milliseconds
'retry_interval' => 100, 'retry_interval' => 100,
// 多路复用客户端数量 // Number of multiplexed clients
'client_count' => 4, 'client_count' => 4,
// 心跳间隔 非 numeric 表示不开启心跳 // Heartbeat interval non-numeric means no heartbeat
'heartbeat' => 30, 'heartbeat' => 30,
], ],
], ],
@ -113,9 +113,9 @@ return [
``` ```
### 注册中心 ### Registration Center
如果需要使用注册中心,则需要手动添加以下监听器 If you need to use the registry, you need to manually add the following listeners
```php ```php
<?php <?php

View File

@ -1,58 +1,58 @@
# RPN - 逆波兰表示法 # RPN - Reverse Polish Notation
![PHPUnit](https://github.com/hyperf/rpn-incubator/workflows/PHPUnit/badge.svg) ![PHPUnit](https://github.com/hyperf/rpn-incubator/workflows/PHPUnit/badge.svg)
`RPN` 是一种是由波兰数学家扬·武卡谢维奇 1920 年引入的数学表达式方式,在逆波兰记法中,所有操作符置于操作数的后面,因此也被称为后缀表示法。逆波兰记法不需要括号来标识操作符的优先级。 `RPN` is a mathematical expression method introduced by Polish mathematician Jan Vukasevich in 1920. In reverse Polish notation, all operators are placed after the operand, so it is also called postfix notation. . Reverse Polish notation does not require parentheses to identify operator precedence.
``` ```
composer require hyperf/rpn composer require hyperf/rpn
``` ```
## RPN 逻辑 ## RPN logic
基本逻辑 basic logic
- while 有输入 - while with input
- 读入下一个符号 X - read in the next symbol X
- IF X 是一个操作数 - IF X is an operand
- 入栈 - push the stack
- ELSE IF X 是一个操作符 - ELSE IF X is an operator
- 有一个先验的表格给出该操作符需要 n 个参数 - there is a table of a priori that the operator takes n arguments
- IF 堆栈中少于 n 个操作数 - IF less than n operands on stack
- (错误)用户没有输入足够的操作数 - (Error) User did not enter enough operands
- Elsen 个操作数出栈 - Else, pop n operands from stack
- 计算操作符。 - Computational operators.
- 将计算所得的值入栈 - push the calculated value onto the stack
- IF 栈内只有一个值 - There is only one value in the IF stack
- 这个值就是整个计算式的结果 - This value is the result of the entire calculation
- ELSE 多于一个值 - ELSE more than one value
- (错误)用户输入了多余的操作数 - (Error) User entered redundant operands
实例 Example
中缀表达式 `5 + ((1 + 2) * 4) - 3` 写作 The infix expression `5 + ((1 + 2) * 4) - 3` is written
`5 1 2 + 4 * + 3 -` `5 1 2 + 4 * + 3 -`
下表给出了该逆波兰表达式从左至右求值的过程,堆栈栏给出了中间值,用于跟踪算法。 The following table shows how this Reverse Polish expression is evaluated from left to right, with the intermediate values given in the stack bar, which is used to keep track of the algorithm.
| 输入 | 操作 | 堆栈 | 注释 | | input | action | stack | comment |
| ---- | -------- | ------- | -------------------------- | | ---- | -------- | ------- | ---------------------------- |
| 5 | 入栈 | 5 | | | 5 | Push | 5 | |
| 1 | 入栈 | 5, 1 | | | 1 | Push | 5, 1 | |
| 2 | 入栈 | 5, 1, 2 | | | 2 | Push | 5, 1, 2 | |
| + | 加法运算 | 5, 3 | 1, 2 出栈,将结果 3 入栈 | | + | Addition | 5, 3 | Pop 1, 2, push result 3 |
| 4 | 入栈 | 5, 3, 4 | | | 4 | Push | 5, 3, 4 | |
| * | 乘法运算 | 5, 12 | 3, 4 出栈,将结果 12 入栈 | | * | Multiplication | 5, 12 | 3, 4 pop, push result 12 |
| + | 加法运算 | 17 | 5, 12 出栈,将结果 17 入栈 | | + | Add operation | 17 | 5, 12 pop, push result 17 |
| 3 | 入栈 | 17, 3 | | | 3 | push | 17, 3 | |
| - | 减法运算 | 14 | 17, 3 出栈,将结果 14 入栈 | | - | Subtraction | 14 | 17, 3 pop, push result 14 |
计算完成时,栈内只有一个操作数,这就是表达式的结果:14 When the calculation is complete, there is only one operand on the stack, which is the result of the expression: 14
## 使用 ## use
直接计算 RPN 表达式 Evaluate RPN expressions directly
```php ```php
<?php <?php
@ -62,7 +62,7 @@ $calculator = new Calculator();
$calculator->calculate('5 1 2 + 4 * + 3 -', []); // '14' $calculator->calculate('5 1 2 + 4 * + 3 -', []); // '14'
``` ```
设置计算精度 Set calculation precision
```php ```php
<?php <?php
@ -72,7 +72,7 @@ $calculator = new Calculator();
$calculator->calculate('5 1 2 + 4 * + 3 -', [], 2); // '14.00' $calculator->calculate('5 1 2 + 4 * + 3 -', [], 2); // '14.00'
``` ```
设置变量 set variable
```php ```php
<?php <?php
@ -82,9 +82,9 @@ $calculator = new Calculator();
$calculator->calculate('[0] 1 2 + 4 * + [1] -', [5, 10]); // '7' $calculator->calculate('[0] 1 2 + 4 * + [1] -', [5, 10]); // '7'
``` ```
### 中缀表达式转化为后缀表达式 ### Convert infix expressions to postfix expressions
> 暂时不支持使用变量 > The use of variables is temporarily not supported
```php ```php
<?php <?php

View File

@ -1,29 +1,29 @@
# 模型全文检索 # Model full text search
## 前言 ## Preface
> [hyperf/scout](https://github.com/hyperf/scout) 衍生于 [laravel/scout](https://github.com/laravel/scout),我们对它进行了一些协程化改造,但保持了相同的 API。在这里感谢一下 Laravel 开发组,实现了如此强大好用的组件。本文档部分节选自 Laravel China 社区组织翻译的 Laravel 官方文档。 > [hyperf/scout](https://github.com/hyperf/scout) is derived from [laravel/scout](https://github.com/laravel/scout), we have made some coroutine transformation to it , but maintains the same API. I would like to thank the Laravel development team for implementing such a powerful and easy-to-use component. This document is partially excerpted from the official Laravel documentation translated by the Laravel China community organization.
Hyperf/Scout 为模型的全文搜索提供了一个简单的、基于驱动程序的解决方案。使用模型观察员Scout 会自动同步你的搜索索引和模型记录。 Hyperf/Scout provides a simple, driver-based solution for full-text search of models. Using model watchers, Scout automatically synchronizes your search index and model records.
目前Scout 自带了一个 Elasticsearch 驱动;而编写自定义驱动程序很简单,你可以自由地使用自己的搜索实现来扩展 Scout。 Currently, Scout comes with an Elasticsearch driver; writing a custom driver is simple, and you are free to extend Scout with your own search implementation.
## 安装 ## Install
### 引入组件包和 Elasticsearch 驱动 ### Introduce component package and Elasticsearch driver
```bash ```bash
composer require hyperf/scout composer require hyperf/scout
composer require hyperf/elasticsearch composer require hyperf/elasticsearch
``` ```
Scout 安装完成后,使用 vendor:publish 命令来生成 Scout 配置文件。这个命令将在你的 config 目录下生成一个 scout.php 配置文件。 After Scout is installed, use the vendor:publish command to generate the Scout configuration file. This command will generate a scout.php configuration file in your config directory.
```bash ```bash
php bin/hyperf.php vendor:publish hyperf/scout php bin/hyperf.php vendor:publish hyperf/scout
``` ```
最后,在你要做搜索的模型中添加 Hyperf\Scout\Searchable trait。这个 trait 会注册一个模型观察者来保持模型和所有驱动的同步: Finally, add the Hyperf\Scout\Searchable trait to the model you want to search. This trait registers a model observer to keep the model in sync with all drivers:
```php ```php
<?php <?php
@ -38,17 +38,17 @@ class Post extends Model
use Searchable; use Searchable;
} }
``` ```
## 配置 ## Configure
### 配置文件 ### Config file
生成配置文件 Generate configuration file
``` ```
php bin/hyperf.php vendor:publish hyperf/scout php bin/hyperf.php vendor:publish hyperf/scout
``` ```
配置文件 Configuration file
```php ```php
<?php <?php
@ -67,7 +67,7 @@ return [
'engine' => [ 'engine' => [
'elasticsearch' => [ 'elasticsearch' => [
'driver' => Hyperf\Scout\Provider\ElasticsearchProvider::class, 'driver' => Hyperf\Scout\Provider\ElasticsearchProvider::class,
// 如果 index 设置为 null则每个模型会对应一个索引反之每个模型对应一个类型 // If index is set to null, each model corresponds to an index, otherwise each model corresponds to a type
'index' => null, 'index' => null,
'hosts' => [ 'hosts' => [
env('ELASTICSEARCH_HOST', 'http://127.0.0.1:9200'), env('ELASTICSEARCH_HOST', 'http://127.0.0.1:9200'),
@ -77,10 +77,10 @@ return [
]; ];
``` ```
### 配置模型索引 ### Configure model index
每个模型与给定的搜索「索引」同步,这个「索引」包含该模型的所有可搜索记录。换句话说,你可以把每一个「索引」设想为一张 MySQL 数据表。默认情况下,每个模型都会被持久化到与模型的「表」名(通常是模型名称的复数形式)相匹配的索引。你也可以通过覆盖模型上的 `searchableAs` 方法来自定义模型的索引:
Each model is synchronized with a given search "index" that contains all searchable records for that model. In other words, you can think of each "index" as a MySQL table. By default, each model is persisted to an index that matches the model's "table" name (usually the plural of the model name). You can also customize the model's index by overriding the `searchableAs` method on the model:
```php
<?php <?php
namespace App; namespace App;
@ -102,13 +102,14 @@ return [
return 'posts_index'; return 'posts_index';
} }
} }
```
<a name="configuring-searchable-data"></a> <a name="configuring-searchable-data"></a>
### 配置可搜索的数据 ### Configure searchable data
默认情况下,「索引」会从模型的 `toArray` 方法中读取数据来做持久化。如果要自定义同步到搜索索引的数据,可以覆盖模型上的 `toSearchableArray` 方法:
By default, "index" will read data from the model's `toArray` method for persistence. If you want to customize the data synced to the search index, you can override the `toSearchableArray` method on the model:
```php
<?php <?php
namespace App; namespace App;
@ -134,136 +135,150 @@ return [
return $array; return $array;
} }
} }
```
<a name="indexing"></a> <a name="indexing"></a>
## 索引 ## index
<a name="batch-import"></a> <a name="batch-import"></a>
### 批量导入 ### Batch Import
如果你想要将 Scout 安装到现有的项目中,你可能已经有了想要导入搜索驱动的数据库记录。使用 Scout 提供的命令 `import` 把所有现有记录导入到搜索索引里:
If you want to install Scout into an existing project, you probably already have database records that you want to import into search-driven. Import all existing records into the search index using the `import` command provided by Scout:
```bash
php bin/hyperf.php scout:import "App\Post" php bin/hyperf.php scout:import "App\Post"
```
<a name="adding-records"></a> <a name="adding-records"></a>
### 添加记录 ### Add record
当你将 Trait `Hyperf\Scout\Searchable` 添加到模型中,你需要做的是 `save` 一个模型实例,它就会自动添加到你的搜索索引。更新索引操作将会在协程结束时进行,不会堵塞请求。
When you add the Trait `Hyperf\Scout\Searchable` to a model, all you need to do is `save` a model instance and it will be automatically added to your search index. The update index operation will be done at the end of the coroutine and will not block the request.
```php
$order = new App\Order; $order = new App\Order;
// ... // ...
$order->save(); $order->save();
```
#### 批量添加 #### Bulk add
如果你想通过模型查询构造器将模型集合添加到搜索索引中,你也可以在模型查询构造器上链式调用 `searchable` 方法。`searchable` 会把构造器的查询结果分块并且将记录添加到你的搜索索引里。 If you want to add a collection of models to the search index via the model query builder, you can also chain the `searchable` method on the model query builder. `searchable` will chunk the query result of the constructor and add the record to your search index.
```php
// 使用模型查询构造器增加... // Use the Model Query Builder to add...
App\Order::where('price', '>', 100)->searchable(); App\Order::where('price', '>', 100)->searchable();
// 使用模型关系增加记录... // Adding records using model relationships...
$user->orders()->searchable(); $user->orders()->searchable();
// 使用集合增加记录... // Adding records using collections...
$orders->searchable(); $orders->searchable();
```
`searchable` 方法可以被看做是「更新插入」的操作。换句话说,如果模型记录已经在你的索引里了,它就会被更新。如果搜索索引中不存在,则将其添加到索引中。 The `searchable` method can be thought of as an "upsert" operation. In other words, if the model record is already in your index, it will be updated. If it doesn't exist in the search index, add it to the index.
<a name="updating-records"></a> <a name="updating-records"></a>
### 更新记录 ### update record
要更新可搜索的模型,只需要更新模型实例的属性并将模型 `save` 到数据库。Scout 会自动将更新同步到你的搜索索引中:
To update a searchable model, simply update the properties of the model instance and `save` the model to the database. Scout will automatically sync updates to your search index:
```php
$order = App\Order::find(1); $order = App\Order::find(1);
// 更新 order... // 更新 order...
$order->save(); $order->save();
```
你也可以在模型查询语句上使用 `searchable` 方法来更新一个模型的集合。如果这个模型不存在你检索的索引里,就会被创建: You can also use the `searchable` method on a model query statement to update a collection of models. If the model doesn't exist in the index you're retrieving, it will be created:
```php
// 使用模型查询语句更新... // Update with model query statement...
App\Order::where('price', '>', 100)->searchable(); App\Order::where('price', '>', 100)->searchable();
// 你也可以使用模型关系更新... // You can also use model relational updates...
$user->orders()->searchable(); $user->orders()->searchable();
// 你也可以使用集合更新... // You can also use collection update...
$orders->searchable(); $orders->searchable();
```
<a name="removing-records"></a> <a name="removing-records"></a>
### 删除记录 ### Delete Record
简单地使用 `delete` 从数据库中删除该模型就可以移除索引里的记录。这种删除形式甚至与软删除的模型兼容:
Simply delete the model from the database using `delete` to remove the record in the index. This form of deletion is even compatible with the soft-deleted model:
```php
$order = App\Order::find(1); $order = App\Order::find(1);
$order->delete(); $order->delete();
```
如果你不想在删除记录之前检索模型,可以在模型查询实例或集合上使用 `unsearchable` 方法: If you don't want to retrieve the model before deleting the record, you can use the `unsearchable` method on the model query instance or collection:
```php
// 通过模型查询删除... // Delete via model query...
App\Order::where('price', '>', 100)->unsearchable(); App\Order::where('price', '>', 100)->unsearchable();
// 通过模型关系删除... // Delete via model relationship...
$user->orders()->unsearchable(); $user->orders()->unsearchable();
// 通过集合删除... // Delete by Collection...
$orders->unsearchable(); $orders->unsearchable();
```
<a name="pausing-indexing"></a> <a name="pausing-indexing"></a>
### 暂停索引 ### Pause indexing
你可能需要在执行一批模型操作的时候,不同步模型数据到搜索索引。此时你可以使用协程安全的 `withoutSyncingToSearch` 方法来执行此操作。这个方法接受一个立即执行的回调。该回调中所有的操作都不会同步到模型的索引:
You may need to perform a batch of model operations without syncing model data to the search index. At this point you can use the coroutine-safe `withoutSyncingToSearch` method to do this. This method accepts a callback that executes immediately. All operations in this callback will not be synchronized to the model's index:
```php
App\Order::withoutSyncingToSearch(function () { App\Order::withoutSyncingToSearch(function () {
// 执行模型动作... // Execute model actions...
}); });
```
<a name="searching"></a> <a name="searching"></a>
## 搜索 ## search
你可以使用 `search` 方法来搜索模型。`search` 方法接受一个用于搜索模型的字符串。你还需在搜索查询上链式调用 `get` 方法,才能用给定的搜索语句查询与之匹配的模型模型:
You can use the `search` method to search for models. The `search` method accepts a string to search for the model. You also need to chain the `get` method on the search query to query the matching model with a given search statement:
```php
$orders = App\Order::search('Star Trek')->get(); $orders = App\Order::search('Star Trek')->get();
Scout 搜索返回模型模型的集合,因此你可以直接从路由或控制器返回结果,它们会被自动转换成 JSON 格式: ```
Scout searches return collections of model models, so you can return results directly from routes or controllers, and they will be automatically converted to JSON:
```php
Route::get('/search', function () { Route::get('/search', function () {
return App\Order::search([])->get(); return App\Order::search([])->get();
}); });
```
如果你想在它们返回模型模型前得到原结果,你应该使用`raw` 方法: If you want raw results before they are returned to the model, you should use the `raw` method:
```php
$orders = App\Order::search('Star Trek')->raw(); $orders = App\Order::search('Star Trek')->raw();
```
搜索查询通常会在模型的 [`searchableAs`](#configuring-model-indexes) 方法指定的索引上执行。当然,你也可以使用 `within` 方法指定应该搜索的自定义索引: Search queries are usually executed on the indexes specified by the model's [`searchableAs`](#configuring-model-indexes) method. Of course, you can also use the `within` method to specify a custom index that should be searched:
```php
$orders = App\Order::search('Star Trek') $orders = App\Order::search('Star Trek')
->within('tv_shows_popularity_desc') ->within('tv_shows_popularity_desc')
->get(); ->get();
```
<a name="where-clauses"></a> <a name="where-clauses"></a>
### Where 语句 ### Where Statement
Scout 允许你在搜索查询中增加简单的「where」语句。目前这些语句只支持基本的数值等式检查并且主要是用于根据拥有者的 ID 进行的范围搜索查询。由于搜索索引不是关系型数据库因此当前不支持更高级的「where」语句
Scout allows you to add simple "where" clauses to your search queries. Currently, these statements only support basic numerical equality checks, and are primarily used for range search queries based on the owner's ID. Since search indexes are not relational databases, more advanced "where" statements are currently not supported:
```php
$orders = App\Order::search('Star Trek')->where('user_id', 1)->get(); $orders = App\Order::search('Star Trek')->where('user_id', 1)->get();
```
<a name="pagination"></a> <a name="pagination"></a>
### 分页 ### Pagination
除了检索模型的集合,你也可以使用 `paginate` 方法对搜索结果进行分页。这个方法会返回一个就像 [传统的模型查询分页](/zh-cn/db/paginator) 一样的 `Paginator` 实例:
In addition to retrieving a collection of models, you can also use the `paginate` method to paginate search results. This method will return a `Paginator` instance like [traditional model query pagination](/en/db/paginator):
```php
$orders = App\Order::search('Star Trek')->paginate(); $orders = App\Order::search('Star Trek')->paginate();
```
你可以通过将数量作为第一个参数传递给 `paginate` 方法来指定每页检索多少个模型: You can specify how many models to retrieve per page by passing the number as the first argument to the `paginate` method:
```php
$orders = App\Order::search('Star Trek')->paginate(15); $orders = App\Order::search('Star Trek')->paginate(15);
```
获取到检索结果后,就可以使用喜欢的模板引擎来渲染分页链接从而显示结果,就像传统的模型查询分页一样: After obtaining the retrieval results, you can use your favorite template engine to render the pagination links to display the results, just like traditional model query pagination:
```php
<div class="container"> <div class="container">
@foreach ($orders as $order) @foreach ($orders as $order)
{{ $order->price }} {{ $order->price }}
@ -271,14 +286,14 @@ Scout 允许你在搜索查询中增加简单的「where」语句。目前
</div> </div>
{{ $orders->links() }} {{ $orders->links() }}
```
<a name="custom-engines"></a> <a name="custom-engines"></a>
## 自定义引擎 ## custom engine
#### 写引擎 #### write engine
如果内置的 Scout 搜索引擎不能满足你的需求,你可以写自定义的引擎并且将它注册到 Scout。你的引擎需要继承 `Hyperf\Scout\Engine\Engine` 抽象类,这个抽象类包含了你自定义的引擎必须要实现的五种方法:
If the built-in Scout search engine does not meet your needs, you can write a custom engine and register it with Scout. Your engine needs to inherit the `Hyperf\Scout\Engine\Engine` abstract class, which contains five methods that your custom engine must implement:
```php
use Hyperf\Scout\Builder; use Hyperf\Scout\Builder;
abstract public function update($models); abstract public function update($models);
@ -286,12 +301,12 @@ Scout 允许你在搜索查询中增加简单的「where」语句。目前
abstract public function search(Builder $builder); abstract public function search(Builder $builder);
abstract public function paginate(Builder $builder, $perPage, $page); abstract public function paginate(Builder $builder, $perPage, $page);
abstract public function map($results, $model); abstract public function map($results, $model);
```
It will be helpful to see these methods in the `Hyperf\Scout\Engine\ElasticsearchEngine` class. This class will provide a good starting point for you to learn how to implement these methods in your custom engine.
`Hyperf\Scout\Engine\ElasticsearchEngine` 类里查看这些方法会对你有较大的帮助。这个类会为你在学习如何在自定义引擎中实现这些方法提供一个好的起点。 #### Registration engine
#### 注册引擎 Once you have written your custom engine, you can specify the engine in the configuration file. For example, if you have written a `MySqlSearchEngine`, you can write this in the configuration file:
一旦你写好了自定义引擎,您就可以在配置文件中指定引擎了。举个例子,如果你写好了一个 `MySqlSearchEngine`,您就可以在配置文件中这样写:
```php ```php
<?php <?php
return [ return [
@ -307,16 +322,7 @@ return [
]; ];
``` ```
## 与 laravel/scout 不同之处 ## Differences from laravel/scout
- Hyperf/Scout 是使用协程来高效同步搜索索引和模型记录的,无需依赖队列机制。
- Hyperf/Scout 默认提供的是开源的 Elasticsearch 引擎,而不是闭源的 Algolia。
- Hyperf/Scout uses coroutines to efficiently synchronize search indexes and model records without relying on queue mechanisms.
- Hyperf/Scout provides the open source Elasticsearch engine by default instead of the closed source Algolia.

106
docs/en/sdks/wechat.md Normal file
View File

@ -0,0 +1,106 @@
#EasyWechat
[EasyWeChat](https://www.easywechat.com/) is an open source WeChat SDK (not WeChat official SDK).
> If you are using Swoole 4.7.0 and above, and have the native curl option turned on, you may not follow this document.
> Because the component uses `Curl` by default, we need to modify the corresponding `GuzzleClient` as a coroutine client, or modify the constant [SWOOLE_HOOK_FLAGS](/zh-cn/coroutine?id=swoole-runtime-hook-level)
## replace `Handler`
The following takes the public account as an example,
```php
<?php
use Hyperf\Utils\ApplicationContext;
use EasyWeChat\Factory;
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use Hyperf\Guzzle\CoroutineHandler;
$container = ApplicationContext::getContainer();
$app = Factory::officialAccount($config);
$handler = new CoroutineHandler();
// Set HttpClient, some interfaces use http_client directly.
$config = $app['config']->get('http', []);
$config['handler'] = $stack = HandlerStack::create($handler);
$app->rebind('http_client', new Client($config));
// Some interfaces will reset the Handler according to guzzle_handler when requesting data
$app['guzzle_handler'] = $handler;
// If you are using OfficialAccount, you also need to set the following parameters
$app->oauth->setGuzzleOptions([
'http_errors' => false,
'handler' => $stack,
]);
```
## Modify `SWOOLE_HOOK_FLAGS`
Reference [SWOOLE_HOOK_FLAGS](/en/coroutine?id=swoole-runtime-hook-level)
## How to use EasyWeChat
`EasyWeChat` is designed for `PHP-FPM` architecture, so it needs to be modified in some places to be used under Hyperf. Let's take the payment callback as an example to explain.
1. `EasyWeChat` comes with `XML` parsing, so we can get the original `XML`.
```php
$xml = $this->request->getBody()->getContents();
```
2. Put XML data into `Request` of `EasyWeChat`.
```php
<?php
use Symfony\Component\HttpFoundation\HeaderBag;
use Symfony\Component\HttpFoundation\Request;
$get = $this->request->getQueryParams();
$post = $this->request->getParsedBody();
$cookie = $this->request->getCookieParams();
$uploadFiles = $this->request->getUploadedFiles() ?? [];
$server = $this->request->getServerParams();
$xml = $this->request->getBody()->getContents();
$files = [];
/** @var \Hyperf\HttpMessage\Upload\UploadedFile $v */
foreach ($uploadFiles as $k => $v) {
$files[$k] = $v->toArray();
}
$request = new Request($get, $post, [], $cookie, $files, $server, $xml);
$request->headers = new HeaderBag($this->request->getHeaders());
$app->rebind('request', $request);
// Do something...
```
3. Server Configuration
If you need to use the server configuration function of the WeChat public platform, you can use the following code.
> The following `$response` is `Symfony\Component\HttpFoundation\Response` not `Hyperf\HttpMessage\Server\Response`
> So just return the `Body` content directly to pass WeChat verification.
```php
$response = $app->server->serve();
return $response->getContent();
```
## How to replace the cache
`EasyWeChat` uses `file cache` by default, but the actual scenario is that `Redis` cache is mostly used, so this can be replaced with the `hyperf/cache` cache component provided by `Hyperf`, if you do not currently install this component, please execute `composer Introduced by require hyperf/cache`, the usage example is as follows:
```php
<?php
use Psr\SimpleCache\CacheInterface;
use Hyperf\Utils\ApplicationContext;
use EasyWeChat\Factory;
$app = Factory::miniProgram([]);
$app['cache'] = ApplicationContext::getContainer()->get(CacheInterface::class);
```

121
docs/en/signal.md Normal file
View File

@ -0,0 +1,121 @@
# Signal handler
The signal handler will listen to the `Worker` process and the `custom` process and automatically register with the signal manager after it starts.
## Install
```
composer require hyperf/signal
```
## Publish configuration
You can publish the default configuration file to your project with the following command:
```bash
php bin/hyperf.php vendor:publish hyperf/signal
```
## Add handler
Below we listen to the `SIGTERM` signal of the `Worker` process, and print the signal value when the signal is received.
```php
<?php
declare(strict_types=1);
namespace App\Signal;
use Hyperf\Signal\Annotation\Signal;
use Hyperf\Signal\SignalHandlerInterface;
#[Signal]
class TermSignalHandler implements SignalHandlerInterface
{
public function listen(): array
{
return [
[SignalHandlerInterface::WORKER, SIGTERM],
];
}
public function handle(int $signal): void
{
var_dump($signal);
}
}
```
Because the SIGTERM signal received by the Worker process is captured, it cannot exit normally, so the user can directly `Ctrl + C` to exit, or modify the `config/autoload/signal.php` configuration as follows:
> WorkerStopHandler is not suitable for CoroutineServer, please implement it yourself if necessary
```php
<?php
declare(strict_types=1);
return [
'handlers' => [
Hyperf\Signal\Handler\WorkerStopHandler::class => PHP_INT_MIN
],
'timeout' => 5.0,
];
```
After the `WorkerStopHandler` is triggered, it will close the current process after the set [max_wait_time](https://wiki.swoole.com/#/server/setting?id=max_wait_time) configuration time.
## Coroutine style service listener configuration example
> The above default listeners are all adapted to asynchronous style services. If you need to use them in coroutine style services, you can customize the configuration as follows
```php
<?php
declare(strict_types=1);
namespace App\Kernel\Signal;
use Hyperf\AsyncQueue\Driver\Driver;
use Hyperf\Contract\ConfigInterface;
use Hyperf\Process\ProcessManager;
use Hyperf\Server\ServerManager;
use Hyperf\Signal\SignalHandlerInterface;
use Psr\Container\ContainerInterface;
class CoroutineServerStopHandler implements SignalHandlerInterface
{
protected ContainerInterface $container;
protected ConfigInterface $config;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
$this->config = $container->get(ConfigInterface::class);
}
public function listen(): array
{
// There is only one Worker process in the coroutine style, so you only need to monitor the WORKER here.
return [
[self::WORKER, SIGTERM],
[self::WORKER, SIGINT],
];
}
public function handle(int $signal): void
{
ProcessManager::setRunning(false);
foreach (ServerManager::list() as [$type, $server]) {
// Cyclically close open services
$server->shutdown();
}
}
}
```

195
docs/en/task.md Normal file
View File

@ -0,0 +1,195 @@
# Task
At this stage, `Swoole` has no way to `hook` all blocking functions, which means that some functions will still cause `process blocking`, which will affect the scheduling of coroutines. At this time, we can simulate coroutines by using the `Task` component. In order to achieve the purpose of calling blocking functions without blocking the process, in essence, it is still multi-process running blocking functions, so the performance will be obviously inferior to the native coroutine, depending on the number of `Task Worker`.
## Install
```bash
composer require hyperf/task
```
## Configure
Because Task is not the default component, you need to add `Task` related configuration to `server.php` when using it.
```php
<?php
declare(strict_types=1);
use Hyperf\Server\Event;
return [
// Other irrelevant configuration items are omitted here
'settings' => [
// Number of Task Workers, configure the appropriate number based on your server configuration
'task_worker_num' => 8,
// Because `Task` mainly deals with methods that cannot be coroutined, it is recommended to set `false` here to avoid data confusion under coroutines
'task_enable_coroutine' => false,
],
'callbacks' => [
// Task callbacks
Event::ON_TASK => [Hyperf\Framework\Bootstrap\TaskCallback::class, 'onTask'],
Event::ON_FINISH => [Hyperf\Framework\Bootstrap\FinishCallback::class, 'onFinish'],
],
];
```
## use
The Task component provides two usage methods: `active method delivery` and `annotation delivery`.
### Active method delivery
```php
<?php
use Hyperf\Utils\Coroutine;
use Hyperf\Utils\ApplicationContext;
use Hyperf\Task\TaskExecutor;
use Hyperf\Task\Task;
class MethodTask
{
public function handle($cid)
{
return [
'worker.cid' => $cid,
// Returns -1 when task_enable_coroutine is false, otherwise returns the corresponding coroutine ID
'task.cid' => Coroutine::id(),
];
}
}
$container = ApplicationContext::getContainer();
$exec = $container->get(TaskExecutor::class);
$result = $exec->execute(new Task([MethodTask::class, 'handle'], [Coroutine::id()]));
```
### Using annotations
It is not particularly intuitive to use `active method delivery`. Here we implement the corresponding `@Task` annotation and rewrite the method call through `AOP`. When in the `Worker` process, it is automatically delivered to the `Task` process, and the coroutine waits for the data to return.
```php
<?php
use Hyperf\Utils\Coroutine;
use Hyperf\Utils\ApplicationContext;
use Hyperf\Task\Annotation\Task;
class AnnotationTask
{
#[Task]
public function handle($cid)
{
return [
'worker.cid' => $cid,
// Returns -1 when task_enable_coroutine=false, otherwise returns the corresponding coroutine ID
'task.cid' => Coroutine::id(),
];
}
}
$container = ApplicationContext::getContainer();
$task = $container->get(AnnotationTask::class);
$result = $task->handle(Coroutine::id());
```
> `use Hyperf\Task\Annotation\Task;` is required when using the `@Task` annotation
The annotation supports the following parameters
| Configuration | Type | Default | Remarks |
| :------: | :---: | :----: | :-------------------------------------- ----------------------: |
| timeout | int | 10 | Task execution timeout |
| workerId | int | -1 | Specifies the process ID of the task to be delivered (-1 means random delivery to an idle process) |
## Appendix
Swoole does not have a list of coroutine functions for the time being
- mysql, the bottom layer uses libmysqlclient, which is not recommended, it is recommended to use pdo_mysql/mysqli that has already implemented coroutines
- mongo, the bottom layer uses mongo-c-client
- pdo_pgsql
- pdo_ori
- pdo_odbc
- pdo_firebird
### MongoDB
> Because `MongoDB` has no way to be `hook`, we can call it through `Task`. The following is a brief introduction to how to call `MongoDB` through annotations.
Below we implement two methods `insert` and `query`. It should be noted that the `manager` method cannot use `Task`,
Because `Task` will be processed in the corresponding `Task process`, and then return the data from the `Task process` to the `Worker process`.
Therefore, the input and output parameters of the `Task method` should not carry any `IO`, such as returning an instantiated `Redis` and so on.
```php
<?php
declare(strict_types=1);
namespace App\Task;
use Hyperf\Task\Annotation\Task;
use MongoDB\Driver\BulkWrite;
use MongoDB\Driver\Manager;
use MongoDB\Driver\Query;
use MongoDB\Driver\WriteConcern;
class MongoTask
{
public Manager $manager;
#[Task]
public function insert(string $namespace, array $document)
{
$writeConcern = new WriteConcern(WriteConcern::MAJORITY, 1000);
$bulk = new BulkWrite();
$bulk->insert($document);
$result = $this->manager()->executeBulkWrite($namespace, $bulk, $writeConcern);
return $result->getUpsertedCount();
}
#[Task]
public function query(string $namespace, array $filter = [], array $options = [])
{
$query = new Query($filter, $options);
$cursor = $this->manager()->executeQuery($namespace, $query);
return $cursor->toArray();
}
protected function manager()
{
if ($this->manager instanceof Manager) {
return $this->manager;
}
$uri = 'mongodb://127.0.0.1:27017';
return $this->manager = new Manager($uri, []);
}
}
```
Use as follows
```php
<?php
use App\Task\MongoTask;
use Hyperf\Utils\ApplicationContext;
$client = ApplicationContext::getContainer()->get(MongoTask::class);
$client->insert('hyperf.test', ['id' => rand(0, 99999999)]);
$result = $client->query('hyperf.test', [], [
'sort' => ['id' => -1],
'limit' => 5,
]);
```
## Other options
If the Task mechanism cannot meet the performance requirements, you can try another open source project under the Hyperf organization [GoTask](https://github.com/hyperf/gotask). GoTask starts the Go process as the Swoole main process sidecar through the Swoole process management function, and uses the process communication to deliver the task to the sidecar for processing and receive the return value. It can be understood as the Go version of Swoole TaskWorker.

135
docs/en/tcp-server.md Normal file
View File

@ -0,0 +1,135 @@
# TCP/UDP service
The framework provides the ability to create `TCP/UDP` services by default. You only need to perform a simple configuration, you can use it.
## Using TCP service
### Create TcpServer class
```php
<?php
declare(strict_types=1);
namespace App\Controller;
use Hyperf\Contract\OnReceiveInterface;
class TcpServer implements OnReceiveInterface
{
public function onReceive($server, int $fd, int $reactorId, string $data): void
{
$server->send($fd, 'recv:' . $data);
}
}
```
### Create corresponding configuration
```php
<?php
declare(strict_types=1);
use Hyperf\Server\Server;
use Hyperf\Server\Event;
return [
// The following has removed other irrelevant configuration items
'servers' => [
[
'name' => 'tcp',
'type' => Server::SERVER_BASE,
'host' => '0.0.0.0',
'port' => 9504,
'sock_type' => SWOOLE_SOCK_TCP,
'callbacks' => [
Event::ON_RECEIVE => [App\Controller\TcpServer::class, 'onReceive'],
],
'settings' => [
// Configure on demand
],
],
],
];
```
### Implement the client
```php
<?php
$client = new \Swoole\Client(SWOOLE_SOCK_TCP);
$client->connect('127.0.0.1', 9504);
$client->send('Hello World.');
$ret = $client->recv(); // recv:Hello World.
```
## Using UDP service
### Create UdpServer class
> If there is no OnPacketInterface interface file, you can not implement this interface, and the running result is consistent with the implemented interface, as long as the configuration is correct.
```php
<?php
declare(strict_types=1);
namespace App\Controller;
use Hyperf\Contract\OnPacketInterface;
class UdpServer implements OnPacketInterface
{
public function onPacket($server, $data, $clientInfo): void
{
var_dump($clientInfo);
$server->sendto($clientInfo['address'], $clientInfo['port'], 'Server' . $data);
}
}
```
### Create corresponding configuration
```php
<?php
declare(strict_types=1);
use Hyperf\Server\Server;
use Hyperf\Server\Event;
return [
// The following has removed other irrelevant configuration items
'servers' => [
[
'name' => 'udp',
'type' => Server::SERVER_BASE,
'host' => '0.0.0.0',
'port' => 9505,
'sock_type' => SWOOLE_SOCK_UDP,
'callbacks' => [
Event::ON_PACKET => [App\Controller\UdpServer::class, 'onPacket'],
],
'settings' => [
// Configure on demand
],
],
],
];
```
## event
| Events | Notes |
| :---------------: | :---------------: |
| Event::ON_CONNECT | Listen for connection incoming events |
| Event::ON_RECEIVE | Monitor data reception event |
| Event::ON_CLOSE | Listen for connection close events |
| Event::ON_PACKET | UDP data receiving event |

157
docs/en/translation.md Normal file
View File

@ -0,0 +1,157 @@
# 国际化
Hyperf's internationalization support is very friendly, allowing your project to support multiple languages.
# Install
```bash
composer require hyperf/translation
```
> This component is an independent component with no framework-related dependencies, and can be independently reused for other projects or frameworks.
# Language file
The language files of Hyperf are placed under `storage/languages` by default, you can also change the folder of language files in `config/autoload/translation.php`, each language corresponds to a subfolder, such as `en ` refers to the English language file, `zh_CN` refers to the simplified Chinese language file, you can create a new language folder and the language file in it according to your actual needs. An example is as follows:
```
/storage
/languages
/en
messages.php
/zh_CN
messages.php
```
All language files return an array whose keys are strings:
```php
<?php
// storage/languages/en/messages.php
return [
'welcome' => 'Welcome to our application',
];
```
## Configure locale
### Configure default locale
The relevant configuration of the internationalization component is set in the `config/autoload/translation.php` configuration file, you can modify it according to your actual needs.
```php
<?php
// config/autoload/translation.php
return [
// default language
'locale' => 'zh_CN',
// Fallback language, when the language text of the default language is not provided, the corresponding language text of the fallback language will be used
'fallback_locale' => 'en',
// Folder where language files are stored
'path' => BASE_PATH . '/storage/languages',
];
```
### Configure a temporary locale
```php
<?php
use Hyperf\Di\Annotation\Inject;
use Hyperf\Contract\TranslatorInterface;
class FooController
{
#[Inject]
private TranslatorInterface $translator;
public function index()
{
// Only valid for the current request or coroutine lifetime
$this->translator->setLocale('zh_CN');
}
}
```
# Translate string
## Translate via TranslatorInterface
String translation can be done directly by injecting `Hyperf\Contact\TranslatorInterface` and calling the instance's `trans` method:
```php
<?php
use Hyperf\Di\Annotation\Inject;
use Hyperf\Contract\TranslatorInterface;
class FooController
{
#[Inject]
private TranslatorInterface $translator;
public function index()
{
return $this->translator->trans('messages.welcome', [], 'zh_CN');
}
}
```
## Translate via global function
You can also translate strings through the global functions `__()` or `trans()`.
The first parameter of the function takes the form of `key` (referring to the key using the translation string as the key) or `file.key`.
```php
echo __('messages.welcome');
echo trans('messages.welcome');
```
# Define placeholders in translation strings
You can also define placeholders in language strings, all placeholders are prefixed with `:`. For example, using the username as a placeholder:
```php
<?php
// storage/languages/en/messages.php
return [
'welcome' => 'Welcome :name',
];
```
Replace the placeholder using the second parameter of the function:
```php
echo __('messages.welcome', ['name' => 'Hyperf']);
```
If the placeholder is all capital letters, or the first letter is capitalized. Then the translated string will also be in the corresponding uppercase form:
```php
'welcome' => 'Welcome, :NAME', // Welcome, HYPERF
'goodbye' => 'Goodbye, :Name', // Goodbye, HYPERF
```
# Handle complex numbers
Plural rules are different in different languages, which may not be of great concern in Chinese, but when translating other languages, we need to deal with plural forms of words. We can use the `"pipe"` character, which can be used to distinguish singular and plural forms of strings:
```php
'apples' => 'There is one apple|There are many apples',
```
You can also specify a range of numbers to create more complex plural rules:
```php
'apples' => '{0} There are none|[1,19] There are some|[20,*] There are many',
```
Using the `"pipe"` character, once the plural rules have been defined, the global function `trans_choice` can be used to obtain a string literal for a given `"amount"`. In the following example, since the number is greater than `1`, the plural form of the translation string is returned:
```php
echo trans_choice('messages.apples', 10);
```
Of course, in addition to the global function `trans_choice()`, you can also use the `transChoice` method of `Hyperf\Contract\TranslatorInterface`.

View File

@ -0,0 +1,124 @@
# Alibaba Cloud Log Service
Collecting logs can be a troublesome problem when deploying a project in a `Docker cluster`, but Alibaba Cloud provides a very useful `log collection system`. This document briefly introduces how to use Alibaba Cloud log collection.
* [Docker Swarm cluster building](zh-cn/tutorial/docker-swarm.md)
## Enable log service
The first step is to activate the `Log Service` on Alibaba Cloud.
[Log Service Documentation](https://help.aliyun.com/product/28958.html)
The following tutorial is a sequential, step-by-step guide on how to use the log service.
## Install the Logtail container
[Standard Docker log collection process document](https://help.aliyun.com/document_detail/66659.html)
| Parameters | Description |
| :-----------------------------------: | :------------ -------------------------------: |
| ${your_region_name} | Region ID For example, the East China 1 region is cn-hangzhou |
| ${your_aliyun_user_id} | User ID, please replace it with your Alibaba Cloud primary account user ID. |
| ${your_machine_group_user_defined_id} | The machine group custom ID of the cluster The following uses Hyperf |
````
docker run -d -v /:/logtail_host:ro -v /var/run/docker.sock:/var/run/docker.sock \
--env ALIYUN_LOGTAIL_CONFIG=/etc/ilogtail/conf/${your_region_name}/ilogtail_config.json \
--env ALIYUN_LOGTAIL_USER_ID=${your_aliyun_user_id} \
--env ALIYUN_LOGTAIL_USER_DEFINED_ID=${your_machine_group_user_defined_id} \
registry.cn-hangzhou.aliyuncs.com/log-service/logtail
````
## Configure log collection
### Create Project
Login to Alibaba Cloud Log Service, click `Create Project`, and fill in the following information
| Parameters | Fill in the example |
| :------------: | :------------------: |
| Project name | hyperf |
| Comments | For log system demonstration |
| Region | East China 1 (Hangzhou) |
| Activate service | Detailed log |
| Log Storage Location | Current Project |
### Create Logstore
Except for the following parameters, fill in as needed, others can use the default
| Parameters | Fill in the example |
| :------------: | :-------------: |
| Logstore name | hyperf-demo-api |
| save permanently | false |
| Data retention time | 60 |
### Access data
1. Select the Docker file
2. Create a machine group
If you have already created a machine group, you can skip this step
| Parameters | Fill in the example |
| :------------: | :------------: |
| Machine Group Name | Hyperf |
| Machine group ID | User-defined ID |
| User Defined Logo | Hyperf |
3. Configure the machine group
Apply the machine group you just created
4. Configure Logtail
`Label` whitelist, here you can fill in as needed, the following is configured according to the project name, and the project name will be set when the Docker container is running.
| Parameters | Fill in the example | Fill in the example |
| :------------: | :-------------------------------- ----------------: | :-------------: |
| Configuration Name | hyperf-demo-api | |
| Log Path | /opt/www/runtime/logs | *.log |
| Label whitelist | app.name | hyperf-demo-api |
| Pattern | Full Regular Pattern | |
| single-line mode | false | |
| Sample log | `[2019-03-07 11:58:57] hyperf.WARNING: xxx` | |
| First line regular expression | `\[\d+-\d+-\d+\s\d+:\d+:\d+\]\s.*` | |
| Extract fields | true | |
| Regular Expression | `\[(\d+-\d+-\d+\s\d+:\d+:\d+)\]\s(\w+)\.(\w+):(.*)` | |
| Log extraction content | time name level content | |
5. Query analysis configuration
field index property
| Field name | Type | Alias | Chinese word segmentation | Open statistics |
| :------: | :---: | :-----: | :------: | :------: |
| name | text | name | false | true |
| level | text | level | false | true |
| time | text | time | false | false |
| content | text | content | true | false |
### Run the image
When running the image, all you need to do is set the Container `labels`.
| name | value |
| :------: | :-------------: |
| app.name | hyperf-demo-api |
For example the following Dockerfile
```Dockerfile
# Default Dockerfile
FROM hyperf/hyperf:7.4-alpine-v3.11-swoole
LABEL maintainer="Hyperf Developers <group@hyperf.io>" version="1.0" license="MIT" app.name="hyperf-demo-api"
#Other content omitted
````
## Precautions
- Docker storage driver limitation: Currently, only `overlay` and `overlay2` are supported. For other storage drivers, you need to `mount` the directory where the logs are located, and then collect the logs from the host `~/logtail_host/your_path` instead.

View File

@ -1,44 +1,44 @@
# DaoCloud Devops搭建教程 # DaoCloud Devops build tutorial
作为个人开发者,使用自建 `Gitlab``Docker Swarm 集群` 显然成本是无法接受的。这里介绍一个 `Devops` 服务 `DaoCloud` As an individual developer, the cost of using self-built `Gitlab` and `Docker Swarm cluster` is obviously unacceptable. Here is a `Devops` service `DaoCloud`.
推荐理由很简单,因为它免费,而且还能正常使用。 The reason for the recommendation is simple, because it is free and works well.
[DaoCloud](https://dashboard.daocloud.io) [DaoCloud](https://dashboard.daocloud.io)
## 如何使用 ## how to use
大家只需要关注 `项目``应用` 和 `集群管理` 三个切页即可。 You only need to pay attention to the three pages of `project`, `application` and `cluster management`.
### 创建项目 ### Create project
首先我们需要在 `项目` 里新建一个项目。DaoCloud 支持多种镜像仓库,这个可以按需选择。 First we need to create a new project in `projects`. DaoCloud supports a variety of mirror repositories, which can be selected as needed.
这里我以 [hyperf-demo](https://github.com/limingxinleo/hyperf-demo) 仓库为例配置。当创建成功后,在对应 `Github仓库``WebHooks` 下面就会有对应的url。 Here I use the [hyperf-demo](https://github.com/limingxinleo/hyperf-demo) repository as an example to configure. When the creation is successful, there will be a corresponding url under the `WebHooks` corresponding to the `Github repository`.
接下来我们修改一下仓库里的 `Dockerfile`,在 `apk add` 下面增加 `&& apk add wget \`。这里具体原因不是很清楚,如果不更新 `wget`, 使用时就会有问题。但是自建 Gitlab CI 就没有任何问题。 Next, let's modify the `Dockerfile` in the repository and add `&& apk add wget \` under `apk add`. The specific reason here is not very clear, if you do not update `wget`, there will be problems when using it. But there is no problem with self-built Gitlab CI.
当提交代码后,`DaoCloud` 就会执行对应的打包操作了。 When the code is submitted, `DaoCloud` will perform the corresponding packaging operation.
### 创建集群 ### Create cluster
然后我们到 `集群管理` 中,创建一个 `集群`,然后添加 `主机` Then we go to `cluster management`, create a `cluster`, and add `hosts`.
这里就不详述了,按照上面的步骤一步一步来就行。 I won't go into details here, just follow the steps above.
### 创建应用 ### Create application
点击 应用 -> 创建应用 -> 选择刚刚的项目 -> 部署 Click Apply -> Create Application -> Select the project just now -> Deploy
按照指示操作,主机端口用户可以自主选择一个未使用的端口,因为 `DaoCloud` 没有 `Swarm``Config` 功能,所以我们主动把 `.env` 映射到 容器里。 According to the instructions, the host port user can choose an unused port, because `DaoCloud` does not have the `Config` function of `Swarm`, so we actively map `.env` to the container.
添加 `Volume`,容器目录 `/opt/www/.env`,主机目录 使用你存放 `.env` 文件的地址,是否可写 为不可写。 Add `Volume`, container directory `/opt/www/.env`, host directory Use the address where you store the `.env` file, whether it is writable or not.
然后点击 立即部署。 Then click Deploy Now.
### 测试 ### test
到宿主机里访问刚刚的端口号,就可以看到 `Hyperf` 的欢迎接口数据了。 Go to the host to access the port number just now, and you can see the welcome interface data of `Hyperf`.
``` ```
$ curl http://127.0.0.1:9501 $ curl http://127.0.0.1:9501

View File

@ -1,6 +1,6 @@
# Docker Swarm集群搭建教程 # Docker Swarm cluster building tutorial
现阶段Docker容器技术已经相当成熟就算是中小型公司也可以基于 Gitlab、Aliyun镜像服务、Docker Swarm 轻松搭建自己的 Docker集群服务。 At this stage, the Docker container technology is quite mature, and even small and medium-sized companies can easily build their own Docker cluster services based on Gitlab, Aliyun image service, and Docker Swarm.
## Installation Docker ## Installation Docker
@ -8,29 +8,29 @@
curl -sSL https://get.daocloud.io/docker | sh curl -sSL https://get.daocloud.io/docker | sh
``` ```
## 搭建自己的Gitlab ## Build your own Gitlab
### InstallationGitlab ### Installation Gitlab
首先我们修改一下端口号,把 `sshd` 服务的 `22` 端口改为 `2222`,让 `gitlab` 可以使用 `22` 端口。 First, let's modify the port number and change the `22` port of the `sshd` service to `2222`, so that `gitlab` can use the `22` port.
``` ```
$ vim /etc/ssh/sshd_config $ vim /etc/ssh/sshd_config
# 默认 Port 改为 2222 # Default Port changed to 2222
Port 2222 Port 2222
# 重启服务 # restart the service
$ systemctl restart sshd.service $ systemctl restart sshd.service
``` ```
重新登录机器 Re-login to the machine
``` ```
ssh -p 2222 root@host ssh -p 2222 root@host
``` ```
安装 Gitlab Install Gitlab
``` ```
sudo docker run -d --hostname gitlab.xxx.cn \ sudo docker run -d --hostname gitlab.xxx.cn \
@ -41,20 +41,20 @@ sudo docker run -d --hostname gitlab.xxx.cn \
gitlab/gitlab-ce:latest gitlab/gitlab-ce:latest
``` ```
首次登录 `Gitlab` 会重置密码,用户名是 `root` Logging into `Gitlab` for the first time will reset the password, and the username is `root`.
### Installationgitlab-runner ### Installation gitlab-runner
[官方地址](https://docs.gitlab.com/runner/install/linux-repository.html) [Official address](https://docs.gitlab.com/runner/install/linux-repository.html)
`CentOS` 为例 Take `CentOS` as an example
``` ```
curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.rpm.sh | sudo bash curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.rpm.sh | sudo bash
yum install gitlab-runner yum install gitlab-runner
``` ```
当然,可以用 `curl https://setup.ius.io | sh` 命令,更新为最新的 `git` 源,然后直接使用 yum 安装 git 和 gitlab-runner。 Of course, you can use the `curl https://setup.ius.io | sh` command, update to the latest `git` source, and then install git and gitlab-runner directly using yum.
``` ```
$ curl https://setup.ius.io | sh $ curl https://setup.ius.io | sh
@ -63,10 +63,10 @@ $ git version
$ yum install gitlab-runner $ yum install gitlab-runner
``` ```
### 注册 gitlab-runner ### Register gitlab-runner
``` ```
$ gitlab-runner register --clone-url http://内网ip/ $ gitlab-runner register --clone-url http://intranet-ip/
Please enter the gitlab-ci coordinator URL (e.g. https://gitlab.com/): Please enter the gitlab-ci coordinator URL (e.g. https://gitlab.com/):
http://gitlab.xxx.cc/ http://gitlab.xxx.cc/
@ -80,14 +80,14 @@ Please enter the executor: docker-ssh, shell, docker+machine, docker-ssh+machine
shell shell
``` ```
## 初始化 Swarm 集群 ## Initialize the Swarm cluster
登录另外一台机器,初始化集群 Login to another machine and initialize the cluster
``` ```
$ docker swarm init $ docker swarm init
``` ```
创建自定义 Overlay 网络 Create a custom overlay network
``` ```
docker network create \ docker network create \
@ -98,22 +98,22 @@ docker network create \
default-network default-network
``` ```
加入集群 Join the cluster
``` ```
# 显示manager节点的TOKEN # Display the token of the manager node
$ docker swarm join-token manager $ docker swarm join-token manager
# 加入manager节点到集群 # Add the manager node to the cluster
$ docker swarm join --token <token> ip:2377 $ docker swarm join --token <token> ip:2377
# 显示worker节点的TOKEN # Display the token of the worker node
$ docker swarm join-token worker $ docker swarm join-token worker
# 加入worker节点到集群 # Join the worker node to the cluster
$ docker swarm join --token <token> ip:2377 $ docker swarm join --token <token> ip:2377
``` ```
然后配置发布用的 gitlab-runner Then configure the gitlab-runner for publishing
> 其他与 builder 一致,但是 tag 却不能一样。线上环境可以设置为 tags测试环境设置为 test > Others are the same as builder, but tag cannot be the same. The online environment can be set to tags, and the test environment can be set to test
## Installation Portainer ## Installation Portainer
@ -130,18 +130,18 @@ docker service create \
portainer/portainer portainer/portainer
``` ```
## 创建一个Demo项目 ## Create a demo project
登录 Gitlab 创建一个 Demo 项目。并导入我们的项目 [hyperf-skeleton](https://github.com/hyperf-cloud/hyperf-skeleton) Login to Gitlab to create a demo project. and import our project [hyperf-skeleton](https://github.com/hyperf-cloud/hyperf-skeleton)
## 配置镜像仓库 ## Configure the mirror repository
> 我们直接使用阿里云的即可 > We can use Alibaba Cloud directly
首先创建一个命名空间 test_namespace然后创建一个镜像仓库 demo并使用本地仓库。 First create a namespace test_namespace, then create a mirror warehouse demo, and use the local warehouse.
然后到我们直接打包用的服务器中,登录阿里云 Docker Registry Then go to the server we use directly for packaging and login to Alibaba Cloud Docker Registry
``` ```
usermod -aG docker gitlab-runner usermod -aG docker gitlab-runner
@ -149,7 +149,7 @@ su gitlab-runner
docker login --username=your_name registry.cn-shanghai.aliyuncs.com docker login --username=your_name registry.cn-shanghai.aliyuncs.com
``` ```
修改我们项目里的 .gitlab-ci.yml Modify .gitlab-ci.yml in our project
``` ```
variables: variables:
@ -157,7 +157,7 @@ variables:
REGISTRY_URL: registry.cn-shanghai.aliyuncs.com/test_namespace REGISTRY_URL: registry.cn-shanghai.aliyuncs.com/test_namespace
``` ```
还有 deploy.test.yml需要仔细对比以下文件哦。 There is also deploy.test.yml, you need to compare the following files carefully.
```yml ```yml
version: '3.7' version: '3.7'
@ -192,7 +192,7 @@ networks:
external: true external: true
``` ```
然后在我们的 portainer 中,创建对应的 Config demo_v1.0。当然以下参数需要根据实际情况调整因为我们的Demo中没有任何IO操作所以填默认的即可。 Then in our portainer, create the corresponding Config demo_v1.0. Of course, the following parameters need to be adjusted according to the actual situation, because there is no IO operation in our Demo, so fill in the default ones.
``` ```
APP_NAME=demo APP_NAME=demo
@ -213,19 +213,19 @@ REDIS_PORT=6379
REDIS_DB=0 REDIS_DB=0
``` ```
因为我们配置的 gitlab-ci.yml 会检测 test 分支和 tags所以我们把修改的内容合并到test分支然后推到gitlab上。 Because the gitlab-ci.yml we configured will detect the test branch and tags, we merge the modified content into the test branch, and then push it to gitlab.
接下来我们就可以访问集群任意一台机器的 9501 端口。进行测试了 Next we can access port 9501 of any machine in the cluster.
``` ```
curl http://127.0.0.1:9501/ curl http://127.0.0.1:9501/
``` ```
## 意外情况 ## Accidents
### fatal: git fetch-pack: expected shallow list ### fatal: git fetch-pack: expected shallow list
这种情况是 `gitlab-runner` 使用的 `git` 版本过低,更新 `git` 版本即可。 In this case, the version of `git` used by `gitlab-runner` is too low, and the version of `git` can be updated.
``` ```
$ curl https://setup.ius.io | sh $ curl https://setup.ius.io | sh
@ -233,6 +233,6 @@ $ yum remove -y git
$ yum -y install git2u $ yum -y install git2u
$ git version $ git version
# 重新安装 gitlab-runner 并重新注册 gitlab-runner # Reinstall gitlab-runner and re-register gitlab-runner
$ yum install gitlab-runner $ yum install gitlab-runner
``` ```

70
docs/en/tutorial/nginx.md Normal file
View File

@ -0,0 +1,70 @@
# Nginx reverse proxy
[Nginx](http://nginx.org/) is a high-performance `HTTP` and reverse proxy server, the code is completely implemented with `C`, based on its high performance and many advantages, we can set it as The front-end server of `hyperf`, which implements load balancing or HTTPS front-end server, etc.
## Configure HTTP proxy
```nginx
# At least one Hyperf node is required, multiple configuration lines
upstream hyperf {
# IP and port of Hyperf HTTP Server
server 127.0.0.1:9501;
server 127.0.0.1:9502;
}
server {
# listening port
listen 80;
# Bound domain name, fill in your domain name
server_name proxy.hyperf.io;
location / {
# Forward the client's Host and IP information to the corresponding node
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# Forward cookies, set SameSite
proxy_cookie_path / "/; secure; HttpOnly; SameSite=strict";
# Execute proxy access to real server
proxy_pass http://hyperf;
}
}
```
## Configure Websocket proxy
```nginx
# At least one Hyperf node is required, multiple configuration lines
upstream hyperf_websocket {
# Set the load balancing mode to IP Hash algorithm mode, so that each request from different clients will interact with the same node
ip_hash;
# IP and port of Hyperf WebSocket Server
server 127.0.0.1:9503;
server 127.0.0.1:9504;
}
server {
listen 80;
server_name websocket.hyperf.io;
location / {
# WebSocket Header
proxy_http_version 1.1;
proxy_set_header Upgrade websocket;
proxy_set_header Connection "Upgrade";
# Forward the client's Host and IP information to the corresponding node
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
# The connection between the client and the server is automatically disconnected after 60s of no interaction, please set according to the actual business scenario
proxy_read_timeout 60s ;
# Execute proxy access to real server
proxy_pass http://hyperf_websocket;
}
}
```

View File

@ -1,49 +1,49 @@
# Supervisor 部署应用 # Supervisor deploy application
[Supervisor](http://www.supervisord.org/) `Linux/Unix` 系统下的一个进程管理工具。可以很方便的监听、启动、停止和重启一个或多个进程。通过 [Supervisor](http://www.supervisord.org/) 管理的进程,当进程意外被 `Kill` 时,[Supervisor](http://www.supervisord.org/) 会自动将它重启,可以很方便地做到进程自动恢复的目的,而无需自己编写 `shell` 脚本来管理进程。 [Supervisor](http://www.supervisord.org/) is a process management tool under `Linux/Unix` system. One or more processes can be easily monitored, started, stopped and restarted. Processes managed by [Supervisor](http://www.supervisord.org/), when the process is accidentally `Kill`, [Supervisor](http://www.supervisord.org/) will automatically restart it, It is very convenient to achieve the purpose of automatic process recovery without having to write a `shell` script to manage the process.
## Installation Supervisor ## Installation Supervisor
这里仅举例 `CentOS` 系统下的安装方式: Here is just an example of the installation method under the `CentOS` system:
```bash ```bash
# 安装 epel 源,如果此前安装过,此步骤跳过 # Install the epel source, if it has been installed before, skip this step
yum install -y epel-release yum install -y epel-release
yum install -y supervisor yum install -y supervisor
``` ```
## 创建一个配置文件 ## create a configuration file
```bash ```bash
cp /etc/supervisord.conf /etc/supervisord.d/supervisord.conf cp /etc/supervisord.conf /etc/supervisord.d/supervisord.conf
``` ```
编辑新复制出来的配置文件 `/etc/supervisord.d/supervisord.conf`,并在文件结尾处添加以下内容后保存文件: Edit the newly copied configuration file `/etc/supervisord.d/supervisord.conf` and save the file after adding the following at the end of the file:
```ini ```ini
# 新建一个应用并设置一个名称,这里设置为 hyperf # Create a new application and set a name, here is set to hyperf
[program:hyperf] [program:hyperf]
# 这里为您要管理的项目的启动命令,对应您的项目的真实路径 # Here is the startup command of the project you want to manage, corresponding to the real path of your project
command=php /var/www/hyperf/bin/hyperf.php start command=php /var/www/hyperf/bin/hyperf.php start
# 以哪个用户来运行该进程 # Which user to run the process as
user=root user=root
# supervisor 启动时自动该应用 # automatically the app when supervisor starts
autostart=true autostart=true
# 进程退出后自动重启进程 # Automatically restart the process after the process exits
autorestart=true autorestart=true
# 重试间隔秒数 # retry interval in seconds
startsecs=5 startsecs=5
# 重试次数 # number of retries
startretries=3 startretries=3
# stderr 日志输出位置 # stderr log output location
stderr_logfile=/var/www/hyperf/runtime/stderr.log stderr_logfile=/var/www/hyperf/runtime/stderr.log
# stdout 日志输出位置 # stdout log output location
stdout_logfile=/var/www/hyperf/runtime/stdout.log stdout_logfile=/var/www/hyperf/runtime/stdout.log
``` ```
## 启动 Supervisor ## Start Supervisor
运行下面的命令基于配置文件启动 Supervisor 程序: Run the following command to start the Supervisor program based on the configuration file:
```bash ```bash
supervisord -c /etc/supervisord.d/supervisord.conf supervisord -c /etc/supervisord.d/supervisord.conf
@ -52,12 +52,12 @@ supervisord -c /etc/supervisord.d/supervisord.conf
## 使用 supervisorctl 管理项目 ## 使用 supervisorctl 管理项目
```bash ```bash
# 启动 hyperf 应用 # start the hyperf application
supervisorctl start hyperf supervisorctl start hyperf
# 重启 hyperf 应用 # restart hyperf application
supervisorctl restart hyperf supervisorctl restart hyperf
# 停止 hyperf 应用 # stop hyperf application
supervisorctl stop hyperf supervisorctl stop hyperf
# 查看所有被管理项目运行状态 # View the running status of all managed projects
supervisorctl status supervisorctl status
``` ```

View File

@ -1,31 +1,31 @@
# 1.1 升级指南 # 1.1 Upgrade Guide
1.1 版新增了很多的功能,但一些改动也涉及到了对 Skeleton 骨架的调整,以及配置项的结构调整,如果您已经投入了业务使用的项目且是基于官方提供的 Skeleton 项目创建的 1.0 应用项目,那么可以根据下面的内容点来调整您的骨架项目,如果您是一个新的项目,按照文档通过 `composer create-project hyperf/hyperf-skeleton` 命令创建新的项目即可使用新的 skeleton 结构。 Version 1.1 has added a lot of functions, but some changes also involve the adjustment of the Skeleton skeleton and the structural adjustment of the configuration items. If you have already invested in a project for business use and created a 1.0 application project based on the officially provided Skeleton project , then you can adjust your skeleton project according to the following content points. If you are a new project, follow the documentation to create a new project with the `composer create-project hyperf/hyperf-skeleton` command to use the new skeleton structure.
## 升级 Swoole 到 4.4+ ## Upgrade Swoole to 4.4+
1.1 版将最低的 Swoole 版本要求从 4.3+ 提升到了 4.4+这两个版本之间有一些使用上的细节问题Hyperf 已经在较早的版本便已适配了,对于 Hyperf 的用户而言无需理会这之间的差异,我们提升最低 Swoole 版本要求主要是为了减少我们的历史负担,而 Swoole 4.4 作为 Swoole 的 LTS(长期支持版本) 也意味着更加的稳定可靠。 Version 1.1 raised the minimum Swoole version requirement from 4.3+ to 4.4+. There are some usage details between these two versions. Hyperf has already been adapted in earlier versions, so there is no need for Hyperf users. Ignoring the differences, we have increased the minimum Swoole version requirements mainly to reduce our historical burden, and Swoole 4.4 as Swoole's LTS (Long Term Support Version) also means more stable and reliable.
Hyperf 在启动时会进行 Swoole 版本检测,但为了更好的统一各处对 Swoole 版本的依赖约束,我们建议您将 `composer.json` 内对 Swoole 的依赖条件改为 `"ext-swoole": ">=4.4"` Hyperf will perform Swoole version detection at startup, but in order to better unify the dependencies on Swoole versions everywhere, we recommend that you change the dependency on Swoole in `composer.json` to `"ext-swoole": " >=4.4"`.
## 增加 SWOOLE_HOOK_FLAGS 常量 ## Add SWOOLE_HOOK_FLAGS constant
在应用的入口文件 `bin/hyperf.php` 以及单测的入口文件 `test/bootstrap.php` 里增加一行常量定义如下: Add a line of constant definitions to the entry file `bin/hyperf.php` of the application and the entry file `test/bootstrap.php` of the unit test as follows:
```php ```php
! defined('SWOOLE_HOOK_FLAGS') && define('SWOOLE_HOOK_FLAGS', SWOOLE_HOOK_ALL); ! 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) Reference: [Entry file reference](https://github.com/hyperf/hyperf-skeleton/blob/70062b7bbf29e23cda2f30680e02aa3b26ebd6f7/bin/hyperf.php#L11) [Single test entry file reference](https://github.com/ hyperf/hyperf-skeleton/blob/70062b7bbf29e23cda2f30680e02aa3b26ebd6f7/test/bootstrap.php#L20)
## 移动 config/dependencies.php 文件并调整文件结构 ## Move the config/dependencies.php file and adjust the file structure
移动 `config/dependencies.php``config/autoload/dependencies.php`,并去除配置文件中的第一层 `dependencies`,如下: Move `config/dependencies.php``config/autoload/dependencies.php`, and remove the first level of `dependencies` in the configuration file, as follows:
1.0 的文件结构: 1.0 file structure:
```php ```php
<?php <?php
// config/dependencies.php 文件 // config/dependencies.php document
return [ return [
'dependencies' => [ 'dependencies' => [
@ -34,21 +34,21 @@ return [
]; ];
``` ```
1.1 的文件结构: 1.1 The file structure of:
```php ```php
<?php <?php
// config/autoload/dependencies.php 文件 // config/autoload/dependencies.php document
return [ return [
FooInterface::class => Foo::class FooInterface::class => Foo::class
]; ];
``` ```
## 调整 config/container.php 文件的内容 ## Adjust the content of the config/container.php file
由于 1.1 版本调整了 `dependencies.php` 文件的位置和结构,所处我们还需要调整一下 `config/container.php` 文件,以便依赖注入容器能够正确的运行,与此同时,我们也为 `config/container.php` 提供了更加简便的写法,`DefinitionSourceFactory` 将很多默认的行为聚合了起来,您只需将 `config/container.php` 文件的内容更换成下面的内容即可: Since version 1.1 adjusted the location and structure of the `dependencies.php` file, we also need to adjust the `config/container.php` file so that the dependency injection container can run correctly. /container.php` provides a simpler way of writing, `DefinitionSourceFactory` aggregates many default behaviors, you just need to replace the content of the `config/container.php` file with the following content:
> 默认开启注解扫描缓存功能,可修改 `DefinitionSourceFactory` 入参的第一个参数来关闭此功能 > The annotation scanning cache function is enabled by default. You can modify the first parameter of the `DefinitionSourceFactory` parameter to disable this function
```php ```php
<?php <?php
@ -69,9 +69,9 @@ if (! $container instanceof ContainerInterface) {
return ApplicationContext::setContainer($container); return ApplicationContext::setContainer($container);
``` ```
## 调整 WebSocket 控制器 ## Adjust the WebSocket controller
由于 1.1 版本调整了 `onMessage``onOpen` 的入参约束,所以需要手动修改其为 `Swoole\WebSocket\Server`,具体代码如下 Since version 1.1 adjusted the input constraints of `onMessage` and `onOpen`, you need to manually modify it to `Swoole\WebSocket\Server`, the specific code is as follows
```php ```php
<?php <?php
@ -97,11 +97,11 @@ class WebSocketController implements OnMessageInterface, OnOpenInterface
} }
``` ```
## 调整自定义组件的 ConfigProvider ## Adjust the ConfigProvider of the custom component
1.0 版本中 `scan.path` 在 1.1 版本中调整为 `annotations.scan.path`,您需要修改所有自定义组件的 ConfigProvider 类来适配此变更,如您的自定义组件不涉及到注解扫描的功能配置,则可忽略此调整,如下所示: In version 1.0, `scan.path` was adjusted to `annotations.scan.path` in version 1.1. You need to modify the ConfigProvider class of all custom components to adapt to this change. For example, your custom components do not involve annotation scanning. function configuration, this adjustment can be ignored, as follows:
1.0 的 ConfigProvider 文件结构: ConfigProvider file structure for 1.0:
```php ```php
class ConfigProvider class ConfigProvider
{ {
@ -119,7 +119,7 @@ class ConfigProvider
} }
``` ```
1.1 的 ConfigProvider 文件结构: ConfigProvider file structure for 1.1:
```php ```php
class ConfigProvider class ConfigProvider
{ {
@ -139,18 +139,18 @@ class ConfigProvider
} }
``` ```
## 调整默认的本地化语言 ## Adjust the default localization language
如果您在之前有使用 [hyperf/translation](https://github.com/hyperf/translation) 组件,那么您需要检查一下 `config/autoload/translation.php` 文件内的 `locale` 配置项,如为 `zh-CN`,则需要改为 `zh_CN`,在 1.1 版本,我们统一了这个配置的值。 If you have used the [hyperf/translation](https://github.com/hyperf/translation) component before, then you need to check the `locale` configuration item in the `config/autoload/translation.php` file, such as If it is `zh-CN`, it needs to be changed to `zh_CN`. In version 1.1, we unified the value of this configuration.
## 调整 composer.json 的依赖 ## Adjust the dependencies of composer.json
由于要升级到 1.1 版本的组件,而原来 skeleton 项目默认情况下是依赖 1.0.x 版本的组件的,所以我们需要对依赖的约束条件进行一些调整,将原来所有 Hyperf 组件的依赖 `~1.0.0` 修改为 `~1.1.0`,修改完后需运行 `composer update` 来将依赖项升级到 1.1 版本。 Since we want to upgrade to the 1.1 version of the component, and the original skeleton project depends on the 1.0.x version of the component by default, we need to make some adjustments to the constraints of the dependency, and change the dependencies of all the original Hyperf components to `~1.0.0 ` is modified to `~1.1.0`. After modification, you need to run `composer update` to upgrade the dependencies to version 1.1.
必须将所有 Hyperf 依赖都升级到 1.1 版本才可用,因为 1.1 调整了组件适配的 ConfigProvider 机制。 All Hyperf dependencies must be upgraded to version 1.1 to be available, because 1.1 adjusts the ConfigProvider mechanism for component adaptation.
## 完成升级 ## complete the upgrade
至此1.1 升级即已完成,但由于 Hyperf 的各个底层文件都是可以通过 DI 来实现重写的,如您重写了某些本次升级调整到了的框架内部文件,您仍需再根据您的实际情况进行一定的调整。 At this point, the 1.1 upgrade has been completed, but since each underlying file of Hyperf can be rewritten through DI, if you rewrite some internal files of the framework adjusted in this upgrade, you still need to rewrite it according to your Make some adjustments to the actual situation.
如您在升级上或升级后遇到任何的问题,请前往 [Github Issue](https://github.com/hyperf/hyperf/issues) 提交您的 issue说明您遇到的问题我们会尽快帮助您解决。 If you encounter any problems during or after the upgrade, please go to [Github Issue](https://github.com/hyperf/hyperf/issues) to submit your issue, explain the problem you have encountered, and we will try to fix it as soon as possible help you out.

View File

@ -1,14 +1,14 @@
# 2.0 升级指南 # 2.0 Upgrade Guide
2.0 版本新增了不少强大的功能,如果您已经投入了业务使用的项目且是基于官方提供的 Skeleton 项目创建的 1.1 版本的应用项目,那么可以根据下面的内容点来调整您的 Skeleton 项目,如果您是一个新的项目,按照文档通过 `composer create-project hyperf/hyperf-skeleton` 命令创建新的项目即可使用新的 2.0 版本的 skeleton 代码,如果您当前使用的版本是低于 1.1 的版本,那么需要您先升级到 1.1 后再根据此升级指南升级到 2.0 版本。 Version 2.0 has added many powerful functions. If you have already invested in a project for business use and created an application project of version 1.1 based on the officially provided Skeleton project, you can adjust your Skeleton project according to the following points: If you are a new project, follow the documentation to create a new project through the `composer create-project hyperf/hyperf-skeleton` command to use the new 2.0 version of the skeleton code, if you are currently using a version lower than 1.1 , then you need to upgrade to 1.1 first and then upgrade to 2.0 according to this upgrade guide.
## 升级 Swoole 到 4.5+ ## Upgrade Swoole to 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 的升级。 Version 2.0 has raised the minimum Swoole version requirement from 4.4+ to 4.5+. There are some differences in usage details between the two versions. Hyperf has adapted to this version difference in earlier versions, so you can ignore it. The difference details, upgrading the Swoole version to 4.5+ is mainly to reduce the long-term impact of the historical baggage on Hyperf. You can check the Swoole version in the current environment by executing `php --ri swoole`. You can complete the upgrade of Swoole according to the [Swoole documentation](https://wiki.swoole.com/#/environment) .
## 入口文件添加 ClassLoader 初始化 ## Entry file adds ClassLoader initialization
2.0 改变了 AOP 的底层逻辑,所以需要您在框架入口文件 `bin/hyperf.php` 中添加一行初始化的代码,您需要在入口匿名函数内的第一行添加代码 `Hyperf\Di\ClassLoader::init();`,如下所示: 2.0 has changed the underlying logic of AOP, so you need to add a line of initialization code in the framework entry file `bin/hyperf.php`, you need to add the code `Hyperf\Di\ClassLoader:: in the first line of the entry anonymous function init();`, as follows:
```php ```php
<?php <?php
@ -37,7 +37,7 @@ require BASE_PATH . '/vendor/autoload.php';
``` ```
与此同时PHPUnit 的入口文件也许做同样的处理,文件位于 `tests/bootstrap.php`,如下所示: Meanwhile, the PHPUnit entry file might do the same, located in `tests/bootstrap.php`, like this:
```php ```php
<?php <?php
@ -62,9 +62,9 @@ $container->get(Hyperf\Contract\ApplicationInterface::class);
``` ```
## 调整 `composer.json` ## Adjust `composer.json`
因为 2.0 版本 AOP 底层逻辑的调整,故移除了 `init-proxy.sh` 脚本,所以需要您从 `composer.json` 中去掉 `scripts.post-autoload-dump` 内的 `"init-proxy.sh"` 执行语句,并修改 `post-autoload-dump` 内的命令为 `rm -rf runtime/container` 语句。 Due to the adjustment of the underlying logic of AOP in version 2.0, the `init-proxy.sh` script has been removed, so you need to remove the `"init-proxy' in `scripts.post-autoload-dump` from `composer.json`. sh"` to execute the statement, and modify the command in `post-autoload-dump` to the `rm -rf runtime/container` statement.
```json ```json
{ {
@ -84,13 +84,13 @@ $container->get(Hyperf\Contract\ApplicationInterface::class);
``` ```
## 调整 composer.json 的依赖版本 ## Adjust the dependency version of composer.json
由于要升级到 2.0 版本的组件,而原来 skeleton 项目默认情况下是依赖 1.1.x 版本的组件的,所以我们需要对依赖的约束条件进行一些调整,将原来所有 Hyperf 组件的依赖 `~1.1.0` 修改为 `~2.0.0`,同时您还需要将 `phpstan/phpstan` 版本依赖修改为 `^0.12`,修改完后需运行 `composer update` 来将依赖项升级到 2.0 版本。 Since we want to upgrade to the 2.0 version of the component, and the original skeleton project relies on the 1.1.x version of the component by default, we need to make some adjustments to the constraints of the dependency, and change the dependencies of all the original Hyperf components to `~1.1.0 ` is modified to `~2.0.0`, and you also need to modify the `phpstan/phpstan` version dependency to `^0.12`, after the modification, you need to run `composer update` to upgrade the dependencies to version 2.0.
## 调整 Dockerfile ## Adjust Dockerfile
在 Docker 镜像的打包过程中,主动执行 `php bin/hyperf.php` 命令会帮助提前创建所有需要生成的代理类和注解扫描缓存,这样在生产环境运行启动时便无需再次的扫描,这样可以极大的优化生产环境启动的时间和内存使用量。以下示例不包含未修改的 Dockerfile 代码。 During the packaging process of the Docker image, actively executing the `php bin/hyperf.php` command will help to create all the proxy classes and annotation scanning caches that need to be generated in advance, so that there is no need to scan again when the production environment is started, which can be extremely Greatly optimized production startup time and memory usage. The following examples do not contain unmodified Dockerfile code.
```dockerfile ```dockerfile
ENV TIMEZONE=${timezone:-"Asia/Shanghai"} \ ENV TIMEZONE=${timezone:-"Asia/Shanghai"} \
@ -105,28 +105,28 @@ EXPOSE 9501
ENTRYPOINT ["php", "/opt/www/bin/hyperf.php", "start"] ENTRYPOINT ["php", "/opt/www/bin/hyperf.php", "start"]
``` ```
`Docker` 部署的用户,需要注意的是,在重新启动服务之前,最好先执行一次 `php bin/hyperf.php` 后再重新启动服务,以减少重新启动时的耗时。 For users who are not deployed by `Docker`, it should be noted that before restarting the service, it is best to execute `php bin/hyperf.php` and then restart the service to reduce the time-consuming restart.
## 调整 config/config.php 配置文件 ## Adjust the config/config.php configuration file
您需要在 `config/config.php` 配置中添加 `app_env``scan_cacheable` 两个配置项,下面的代码示例不包含其它无关的配置内容,如下所示: You need to add two configuration items `app_env` and `scan_cacheable` to the `config/config.php` configuration. The following code example does not contain other irrelevant configuration content, as shown below:
```php ```php
<?php <?php
return [ return [
// 生产环境使用 prod 值 // The production environment uses the prod value
'app_env' => env('APP_ENV', 'dev'), 'app_env' => env('APP_ENV', 'dev'),
// 是否使用注解扫描缓存 // Whether to scan the cache with annotations
'scan_cacheable' => env('SCAN_CACHEABLE', false), 'scan_cacheable' => env('SCAN_CACHEABLE', false),
]; ];
``` ```
`scan_cacheable` 配置用于控制应用启动时是否使用注解扫描缓存,以上 `Dockerfile``config/config.php` 中都有相关的修改。当这个配置的值为 `true` 时,项目启动时则会认为所有类都已经完成了扫描并正确生成了对应的缓存和代理,则会跳过扫描阶段以便优化启动时间和减少内存开销。 The `scan_cacheable` configuration is used to control whether to use annotations to scan the cache when the application starts. There are related modifications in the above `Dockerfile` and `config/config.php`. When the value of this configuration is `true`, when the project starts, it will consider that all classes have been scanned and the corresponding caches and proxies have been correctly generated, and the scanning phase will be skipped to optimize startup time and reduce memory overhead.
## 修改 `config/autoload/logger.php` ## Modify `config/autoload/logger.php`
因为 2.0 版本提高了对 Monolog 依赖的版本,在高版本的 Monolog 中,默认的日志格式发生了变化,如果对于日志的格式有要求,比如需要根据日志格式与日志系统对接等,可修改 `config/autoload/logger.php` 配置文件的 `dateFormat` 配置项,以保持与之前版本的一致。 Because version 2.0 has improved the version that depends on Monolog, in the higher version of Monolog, the default log format has changed. If there are requirements for the log format, such as the need to connect with the log system according to the log format, you can modify `config/ The `dateFormat` configuration item of the autoload/logger.php` configuration file to keep it consistent with the previous version.
```php ```php
<?php <?php
@ -157,9 +157,9 @@ return [
``` ```
## 修改 `config/autoload/exceptions.php` ## Modify `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` 配置的前面,以下配置示例省略了无关的配置,如下所示: Version 2.0 adjusted the processing logic of HTTP routing exception behaviors such as route not found (404), request method mismatch (405), etc., and changed to throw a sub-exception class of `Hyperf\HttpMessage\Exception\HttpException`, and then ExceptionHandler is used to uniformly manage these exceptions and handle corresponding responses, so that users can also get a consistent response return experience by throwing corresponding exceptions, but since ExceptionHandler is a user-managed mechanism, the default in version 1.1 Skeleton configures an `App\Exception\Handler\AppExceptionHandler` class to handle the exception and return it to the client with a 500 status code, so you need to use the `Hyperf\HttpServer provided by version 2.0 to handle HttpException \Exception\Handler\HttpExceptionHandler` is configured in the `config/autoload/exceptions.php` configuration file, and make sure to be in front of the `App\Exception\Handler\AppExceptionHandler` configuration, the following configuration example omits extraneous configuration, as shown below :
```php ```php
<?php <?php
@ -172,25 +172,25 @@ return [
]; ];
``` ```
当您完成了 ExceptionHandler 的配置后,可以通过直接访问一个不存在的路由,如果响应的状态码为 `404` 即可理解为完成了此项配置的修改。 After you complete the configuration of ExceptionHandler, you can directly access a route that does not exist. If the response status code is `404`, it can be understood that the modification of this configuration is completed.
## 修改 gRPC 客户端 ## Modify the gRPC client
为了避免和 gRPC 实际业务的命名冲突2.0 版本对 gRPC 客户端的基类函数命名进行了调整。 In order to avoid naming conflicts with the actual business of gRPC, version 2.0 has adjusted the naming of the base class functions of the gRPC client.
* `simpleRequest` -> `_simpleRequest` * `simpleRequest` -> `_simpleRequest`
* `clientStreamRequest` -> `_clientStreamRequest` * `clientStreamRequest` -> `_clientStreamRequest`
* `getGrpcClient` -> `_getGrpcClient` * `getGrpcClient` -> `_getGrpcClient`
除此之外,一些不应该暴露的方法变成了私有方法,如您的 gRPC 客户端涉及到以上方法的调用,请进行命名上的调整。 In addition, some methods that should not be exposed have become private methods. If your gRPC client involves the invocation of the above methods, please adjust the naming.
## 移除 DI 懒加载监听器 ## Remove DI lazy load listener
如果您的项目中有使用到 DI 组件的懒加载功能,此前您需要注册一个 `Hyperf\Di\Listener\LazyLoaderBootApplicationListener` 监听器,而在 2.0 版本,这一监听器被移除了,您可以直接使用该功能,故如果您此前有使用到该功能,您需要在 `config/autoload/listeners.php` 中移除该监听器的注册; If your project uses the lazy loading function of DI components, you need to register a `Hyperf\Di\Listener\LazyLoaderBootApplicationListener` listener, but in version 2.0, this listener has been removed, you can use it directly This function, so if you have used this function before, you need to remove the registration of the listener in `config/autoload/listeners.php`;
## 绑定 NormalizerInterface 关系 ## Bind the NormalizerInterface relationship
当您使用了 JSONRPC 功能并使用了 `symfony/serializer` 库来提供序列化功能时,由于 2.0 版本不再自动映射 `Hyperf\Contract\NormalizerInterface` 的实现类,所以您需要手动添加该映射关系,如下: When you use the JSONRPC function and use the `symfony/serializer` library to provide serialization functions, since version 2.0 no longer automatically maps the implementation class of `Hyperf\Contract\NormalizerInterface`, you need to manually add the mapping relationship, as follows :
```php ```php
use Hyperf\Utils\Serializer\SerializerFactory; use Hyperf\Utils\Serializer\SerializerFactory;
@ -201,21 +201,21 @@ return [
]; ];
``` ```
## 调整 Hyperf\Contract\ProcessInterface 的实现类 ## Adjust the implementation class of Hyperf\Contract\ProcessInterface
`Hyperf\Contract\ProcessInterface` 中的 `isEnable` 方法增加了一个 `$server` 参数,即 `isEnable($server): bool`,所有 ProcessInterface 的实现类都需要您对该方法进行一些调整。 The `isEnable` method in `Hyperf\Contract\ProcessInterface` adds a `$server` parameter, `isEnable($server): bool`, all implementation classes of ProcessInterface require you to make some adjustments to this method.
## 检查 config/autoload/aspects.php 文件 ## Check the config/autoload/aspects.php file
如果您此前对 Skeleton 进行过一些精简操作,需要检查 `config/autoload/aspects.php` 文件是否存在,如不存在需要增加一个文件并返回一个空数组. If you have done some thinning operations on Skeleton before, you need to check whether the `config/autoload/aspects.php` file exists, if not, you need to add a file and return an empty array.
> 这个问题在 2.0.1 会被修正,后续可无需做此检查。 > This problem will be fixed in 2.0.1, so this check is not required in the future.
## 检查自定义注解的收集器 ## Check the collector for custom annotations
如果您使用了自定义注解且使用了自定义收集器 `Collector` 来收集注解元数据,则需要将对应的 `Collector` 配置到 `annotations.scan.collectors` 中,因为开发模式下,会根据文件的修改时间判断文件是否修改,然后决定是否重新收集对应的注解元数据。所以,当没有配置 `annotations.scan.collectors` 时,就会导致注解只在首次启动 `server` 时可以生效。 If you use custom annotations and use a custom collector `Collector` to collect annotation metadata, you need to configure the corresponding `Collector` in `annotations.scan.collectors`, because in development mode, it will be based on the file The modification time determines whether the file is modified, and then decides whether to re-collect the corresponding annotation metadata. So, when `annotations.scan.collectors` is not configured, it will cause the annotations to take effect only when the `server` is started for the first time.
如在应用层,该配置位于 `config/autoload/annotations.php` 文件,如下: As in the application layer, the configuration is located in the `config/autoload/annotations.php` file, as follows:
```php ```php
<?php <?php
@ -229,7 +229,7 @@ return [
]; ];
``` ```
如在组件,该配置则由 ConfigProvider 提供,如下: For components, the configuration is provided by ConfigProvider, as follows:
```php ```php
<?php <?php
@ -245,14 +245,14 @@ return [
]; ];
``` ```
## 启动服务并测试访问接口 ## Start the service and test the access interface
使用 Swoole 4.5 版本和 view 组件如果出现接口 404 的问题,可以尝试删除 `config/autoload/server.php` 文件中的 `static_handler_locations` 配置项。 When using Swoole 4.5 and the view component, if there is a problem with interface 404, you can try to delete the `static_handler_locations` configuration item in the `config/autoload/server.php` file.
此配置下的路径都会被认为是静态文件路由,所以如果配置了`/`,就会导致所有接口都会被认为是文件路径,导致接口 404。 Paths under this configuration will be considered static file routes, so if `/` is configured, it will cause all interfaces to be considered file paths, resulting in interface 404.
## 完成升级 ## complete the upgrade
至此2.0 版本升级即已完成,但由于 Hyperf 的各个底层文件都是可以通过 DI 来实现重写的,如您重写了某些本次升级调整到了的框架内部文件,您仍需再根据您的实际情况进行一定的调整。 At this point, the upgrade of version 2.0 has been completed, but since each underlying file of Hyperf can be rewritten through DI, if you rewrite some internal files of the framework adjusted in this upgrade, you still need to rewrite it according to your to make some adjustments to the actual situation.
如您在升级上或升级后遇到任何的问题,请前往 [Github Issue](https://github.com/hyperf/hyperf/issues) 提交您的 issue说明您遇到的问题我们会尽快帮助您解决。 If you encounter any problems during or after the upgrade, please go to [Github Issue](https://github.com/hyperf/hyperf/issues) to submit your issue, explain the problem you have encountered, and we will try to fix it as soon as possible help you out.

View File

@ -1,13 +1,13 @@
# 2.1 升级指南 # 2.1 Upgrade Guide
- 2.1 版本主要增加了底层驱动 [hyperf/engine](https://github.com/hyperf/engine),允许 `Hyperf` 框架跑在 `Swoole``Swow` 之上。 - Version 2.1 mainly adds the underlying driver [hyperf/engine](https://github.com/hyperf/engine), allowing the `Hyperf` framework to run on top of `Swoole` or `Swow`.
- PHP 最低版本由 7.2 修改为 7.3 - Minimum PHP version changed from 7.2 to 7.3
> Swow 暂为预览版本,请谨慎使用。 > Swow is temporarily a preview version, please use it with caution.
## 修改 Hyperf 组件版本 ## Modify Hyperf component version
直接将 `composer.json` 中的 `hyperf/*` 统一修改为 `2.1.*` 即可。 Simply change `hyperf/*` in `composer.json` to `2.1.*`.
```json ```json
{ {
@ -54,12 +54,12 @@
} }
``` ```
后面只需要执行 `composer update -o`,就可以正常完成升级了。 After that, you only need to execute `composer update -o`, and the upgrade can be completed normally.
## 增加 optimize-autoloader 配置 ## Add optimize-autoloader configuration
因为 `Hyperf` 自从 `2.0` 开始,需要使用到 `composer` 生成的 `class_map`,这就要求用户每次更新依赖都要使用 `-o` 进行优化,但很多用户从来没有这个习惯。 Because `Hyperf` needs to use `class_map` generated by `composer` since `2.0`, which requires users to use `-o` for optimization every time they update dependencies, but many users never have this habit.
于是我们建议在 `composer.json` 中增加对应配置,以满足这个需要。 Therefore, we recommend adding corresponding configuration to `composer.json` to meet this need.
```json ```json
{ {
@ -70,11 +70,11 @@
} }
``` ```
## 修改 SwooleEvent ## Modify SwooleEvent
`Hyperf\Server\SwooleEvent``2.1` 中已经更名为 `Hyperf\Server\Event`,所以我们需要在 `server.php` 配置中修改对应代码。 The class `Hyperf\Server\SwooleEvent` has been renamed to `Hyperf\Server\Event` in `2.1`, so we need to modify the corresponding code in the `server.php` configuration.
> SwooleEvent 会在 3.0 中正式被移除,请尽快修改为 Event > SwooleEvent will be officially removed in 3.0, please change it to Event as soon as possible
```php ```php
<?php <?php
@ -107,25 +107,25 @@ return [
]; ];
``` ```
## 分页器 ## Paginator
因为组件 `hyperf/paginator` 已从 `hyperf/database` 依赖中移除。所以在 database 中使用到分页器的同学,还需要额外引入 `hyperf/paginator` 组件。 Because the component `hyperf/paginator` has been removed from the `hyperf/database` dependency. Therefore, students who use the pager in the database also need to introduce the `hyperf/paginator` component.
## 修改 DBAL 版本 ## Modify DBAL version
倘若使用了 `doctrine/dbal` 组件,则需要升级到 `^3.0` 版本。 If you use the `doctrine/dbal` component, you need to upgrade to the `^3.0` version.
## 移除组件 doctrine/common ## remove component doctrine/common
`doctrine/common` 组件与 `hyperf/utils` 存在依赖冲突。故需要从 `composer.json` 中移除此组件。 The `doctrine/common` component has a dependency conflict with `hyperf/utils`. So this component needs to be removed from `composer.json`.
```bash ```bash
# 移除组件 # remove component
composer remove doctrine/common composer remove doctrine/common
# 更新 # update
composer update "hyperf/*" -o composer update "hyperf/*" -o
``` ```
## 注意事项 ## Precautions
- 尽量不要将老项目的引擎修改为 Swow如果想要使用 Swow请尽量在新项目中尝试。因为 Swow 并不是 Swoole 的替代品,所以并不是所有 Swoole 的场景,都能在 Swow 中找到对应的替代方案。 - Try not to change the engine of the old project to Swow, if you want to use Swow, please try it in the new project. Because Swow is not a substitute for Swoole, not all Swoole scenarios can find corresponding alternatives in Swow.

View File

@ -1,20 +1,20 @@
# 2.2 升级指南 # 2.2 Upgrade Guide
2.2 版本主要增加了 `PHP 8` 的适配,支持原生注解。 Version 2.2 mainly adds the adaptation of `PHP 8` and supports native annotations.
## 修改 Hyperf 组件版本 ## Modify Hyperf component version
直接将 `composer.json` 中的 `hyperf/*` 统一修改为 `2.2.*` 即可。 Simply change `hyperf/*` in `composer.json` to `2.2.*`.
> hyperf/engine 不跟随框架版本号,故不需要修改 > hyperf/engine does not follow the framework version number, so no modification is required
另外,我们可以执行 `composer require "hyperf/ide-helper:2.2.*" --dev` 安装 `hyperf/ide-helper`,此组件可以帮助我们在使用原生注解时,提示注解可以设置的参数。 In addition, we can execute `composer require "hyperf/ide-helper:2.2.*" --dev` to install `hyperf/ide-helper`, this component can help us to prompt the parameters that annotations can set when using native annotations.
后面只需要执行 `composer update -o`,就可以正常完成升级了。 After that, you only need to execute `composer update -o`, and the upgrade can be completed normally.
## 修改单测脚本 ## Modify the single test script
增加选项 `--prepend test/bootstrap.php` Added option `--prepend test/bootstrap.php`
```json ```json
{ {
@ -24,9 +24,9 @@
} }
``` ```
## 安装 pcntl 扩展 ## Install the pcntl extension
新版本的注解扫描使用了 `pcntl` 扩展,所以请先确保您的 `PHP` 安装了此扩展。 The new version of annotation scanning uses the `pcntl` extension, so make sure your `PHP` has this extension installed first.
```shell ```shell
php --ri pcntl php --ri pcntl
@ -36,13 +36,13 @@ pcntl
pcntl support => enabled pcntl support => enabled
``` ```
> 当开启 `grpc` 的时候,需要添加 `grpc.enable_fork_support= 1;``php.ini` 中,以支持开启子进程。 > When enabling `grpc`, you need to add `grpc.enable_fork_support= 1;` to `php.ini` to support opening child processes.
## AMQP ## AMQP
> 使用到 AMQP 的用户请注意,没有的可忽略此小节。 > Attention to users who use AMQP, this section can be ignored if there is none.
因为 `AMQP` 组件全线升级,支持多路复用,所以配置上也有一定更改。请按照以下最新的配置,酌情修改。 Because the `AMQP` component has been upgraded across the board and supports multiplexing, there are certain changes in the configuration. Please follow the latest configuration below and modify as appropriate.
```php ```php
<?php <?php
@ -58,8 +58,8 @@ return [
'limit' => 1, 'limit' => 1,
], ],
'pool' => [ 'pool' => [
// 同时开启的连接数 // Number of connections opened at the same time
// 因为新版本连接是支持多路复用的,所以可以用极少的连接数达到很高的并发 // Because the new version of the connection supports multiplexing, it can achieve high concurrency with a very small number of connections
'connections' => 2, 'connections' => 2,
], ],
'params' => [ 'params' => [
@ -74,7 +74,7 @@ return [
'heartbeat' => 3, 'heartbeat' => 3,
'channel_rpc_timeout' => 0.0, 'channel_rpc_timeout' => 0.0,
'close_on_destruct' => false, 'close_on_destruct' => false,
// 多路复用中闲置 Channel 的最大值,超过这个数量后,会关闭多余的限制 Channel // The maximum value of idle channels in multiplexing. After this number is exceeded, the redundant limit channels will be closed.
'max_idle_channels' => 10, 'max_idle_channels' => 10,
], ],
], ],
@ -82,31 +82,31 @@ return [
``` ```
## 配置中心 ## Configuration Center
> 使用到 配置中心 的用户请注意,没有的可忽略此小节。 > Users who use the Configuration Center, please note that this section can be ignored if there is none.
配置中心在该版本进行了完全的重构,请务必仔细重新阅读对应的文档。 The configuration center has been completely refactored in this version, please be sure to re-read the corresponding documentation carefully.
统一都需要引入 `hyperf/config-center` 组件,命令如下: Unity needs to introduce the `hyperf/config-center` component, the command is as follows:
```shell ```shell
composer require "hyperf/config-center:~2.2.0" composer require "hyperf/config-center:~2.2.0"
``` ```
并根据使用的驱动引入对应的驱动依赖组件,如使用到 `Apollo` 则需要引入 `hyperf/config-apollo` 组件,其余驱动类似。 And introduce the corresponding driver dependent components according to the driver used. If you use `Apollo`, you need to import the `hyperf/config-apollo` component, and the rest of the drivers are similar.
同时配置中心相关的所有配置信息已全部集合到了 `config/autoload/config_center.php` 中,请根据新的配置结构进行对应的配置,没有该文件可以通过执行 `php bin/hyperf.php vendor:publish hyperf/config-center` 命令来创建。 At the same time, all configuration information related to the configuration center has been collected into `config/autoload/config_center.php`, please configure according to the new configuration structure, without this file, you can execute `php bin/hyperf.php vendor:publish hyperf/config-center` command to create.
## 服务中心 ## Service Center
使用 `hyperf/service-gonvernace` 组件的用户,因 `consul` 适配器已经从此组件中剥离,新版本下需额外引入 `hyperf/service-governance-consul` 组件,命令如下: For users who use the `hyperf/service-gonvernace` component, because the `consul` adapter has been stripped from this component, the new version needs to add the `hyperf/service-governance-consul` component. The command is as follows:
```shell ```shell
composer require "hyperf/service-governance-consul:~2.2.0" composer require "hyperf/service-governance-consul:~2.2.0"
``` ```
使用到 `nacos` 作为服务中心驱动的用户则需要引入 `hyperf/service-governance-nacos` 组件,命令如下: Users who use `nacos` as a service center driver need to introduce the `hyperf/service-governance-nacos` component, the command is as follows:
```shell ```shell
composer require "hyperf/service-governance-nacos:~2.2.0" composer require "hyperf/service-governance-nacos:~2.2.0"
@ -114,17 +114,17 @@ composer require "hyperf/service-governance-nacos:~2.2.0"
## php-cs-fixer ## php-cs-fixer
如果不需要升级 `php-cs-fixer``3.0` 版本,则可以忽略此小节 If you do not need to upgrade `php-cs-fixer` to `3.0` version, you can ignore this section
1. 修改版本号 1. Modify the version number
``` ```
"friendsofphp/php-cs-fixer": "^3.0" "friendsofphp/php-cs-fixer": "^3.0"
``` ```
2. 重名命 .php_cs 文件 2. Rename the .php_cs file
重名命为 `.php-cs-fixer.php` 并根据以下变更记录,修改对应代码 Rename it to `.php-cs-fixer.php` and modify the corresponding code according to the following change records
```diff ```diff
- return PhpCsFixer\Config::create() - return PhpCsFixer\Config::create()
@ -134,31 +134,31 @@ composer require "hyperf/service-governance-nacos:~2.2.0"
+ 'comment_type' => 'PHPDoc', + 'comment_type' => 'PHPDoc',
``` ```
## PHP 7.3 版本对 DI 的兼容性有所下降 ## PHP 7.3 version has decreased DI compatibility
> 使用高版本 PHP 的用户可以忽略此小节。 > Users of advanced versions of PHP can ignore this section.
`2.0` - `2.1` 版本时,为了实现 `AOP` 作用于非 `DI` 管理的对象(如 `new` 关键词实例化的对象时),底层实现采用了 `BetterReflection` 组件来实现相关功能,带来新的编程体验的同时,也带来了一些很难攻克的问题,如下: In `2.0` - `2.1` versions, in order to implement `AOP` to act on objects not managed by `DI` (such as objects instantiated by the `new` keyword), the underlying implementation uses the `BetterReflection` component to implement related While bringing a new programming experience, it also brings some difficult problems to overcome, as follows:
- 无扫描缓存时项目启动很慢 - slow project startup without scan cache
- 特殊场景下 `Inject``Value` 不生效 - `Inject` and `Value` do not take effect in special cases
- `BetterReflection` 尚未支持 PHP 8 (截止 2.2 发版时) - `BetterReflection` does not yet support PHP 8 (as of 2.2 release)
在新的版本里,弃用了 `BetterReflection` 的应用,采用了 `子进程扫描` 的方式来解决以上这些痛点,但在低版本的 `PHP` 中也有一些不兼容的情况: In the new version, the application of `BetterReflection` is deprecated, and the method of `subprocess scanning` is adopted to solve the above pain points, but there are also some incompatibilities in the lower version of `PHP`:
使用 `PHP 7.3` 启动应用后遇到类似如下错误: After starting the application with `PHP 7.3` I get an error similar to the following:
```bash ```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 '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 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` 则不会。 This problem is due to the fact that in `PHP 7.3`, the reflection is obtained by `subprocess scanning`, and a non-existing `Interface` is implemented in a certain class, which will cause an exception of `Interface not found` to be thrown. Higher versions of `PHP` do not.
解决方法为创建对应的 `Interface` 并正常引入。上文中的报错解决方法为安装对应所依赖的组件即可。 The solution is to create the corresponding `Interface` and import it normally. The solution to the error reported above is to install the corresponding dependent components.
> 当然,最好还是可以升级到 7.4 或者 8.0 版本 > Of course, it is better to upgrade to version 7.4 or 8.0
```bash ```bash
composer require hyperf/signal composer require hyperf/signal
@ -166,47 +166,47 @@ composer require hyperf/signal
composer require symfony/serializer composer require symfony/serializer
``` ```
## 文件系统 ## File system
> 使用到 文件系统 的用户请注意,没有的可忽略此小节。 > For users who use the file system, please note that if there is no, you can ignore this section.
Hyperf 从 v2.2 版本开始,将同时支持使用 `League\Flysystem` 组件 `v1.0``v2.0` 版本。 Starting from v2.2, Hyperf will support both `League\Flysystem` components `v1.0` and `v2.0`.
如果您使用了 `League\Flysystem``v2.0` 版本,对应的适配器也需要按照下述列表进行调整,具体文档请参考 `文件系统` 章节。 If you use the `v2.0` version of `League\Flysystem`, the corresponding adapters also need to be adjusted according to the following list, please refer to the `File system` chapter for specific documentation.
- 阿里云 OSS 适配器 - Alibaba Cloud OSS adapter
```shell ```shell
composer require hyperf/flysystem-oss composer require hyperf/flysystem-oss
``` ````
- S3 适配器 - S3 adapter
```shell ```shell
composer require "league/flysystem-aws-s3-v3:^2.0" composer require "league/flysystem-aws-s3-v3:^2.0"
composer require hyperf/guzzle composer require hyperf/guzzle
``` ```
- 七牛适配器 - Qiniu Adapter
```shell ```shell
composer require "overtrue/flysystem-qiniu:^2.0" composer require "overtrue/flysystem-qiniu:^2.0"
``` ```
- 内存适配器 - memory adapter
```shell ```shell
composer require "league/flysystem-memory:^2.0" composer require "league/flysystem-memory:^2.0"
``` ```
- 腾讯云 COS 适配器 - Tencent Cloud COS Adapter
> 本适配器基础配置与 `overtrue/flysystem-cos` 组件 `v2.0` 版本略有不同,请根据最新版本配置进行适当修改。 > The basic configuration of this adapter is slightly different from the `v2.0` version of the `overtrue/flysystem-cos` component, please make appropriate modifications according to the latest version configuration.
```shell ```shell
composer require "overtrue/flysystem-cos:^4.0" composer require "overtrue/flysystem-cos:^4.0"
``` ```
## 其他可能导致 BC 的修改 ## Other modifications that may result in BC
[CHANGED](https://github.com/hyperf/hyperf/blob/2.2/CHANGELOG-2.2.md#changed) [CHANGED](https://github.com/hyperf/hyperf/blob/2.2/CHANGELOG-2.2.md#changed)

130
docs/en/upgrade/3.0.md Normal file
View File

@ -0,0 +1,130 @@
# 3.0 Upgrade Guide
- Version 3.0 mainly modifies `PHP`, the minimum version is `8.0`
- The framework removes `Doctrine Annotations` and uses `PHP8 Attributes` instead
- The framework adds a large number of member variable type restrictions
## Convert all annotations
**Note: This step can only be performed under version 2.2**
The following script will convert all `Doctrine Annotations` to `PHP8 Attributes`.
```shell
composer require hyperf/code-generator
php bin/hyperf.php code:generate -D app
```
## Modify Hyperf component version
Simply change `hyperf/*` in `composer.json` to `3.0.*`.
> hyperf/engine does not follow the framework version number, so no modification is required
After that, you only need to execute `composer update -o`, and the upgrade can be completed normally.
## Upgrade the model
Because the model base class adds type support for member variables, you need to use the following script to upgrade it to the new version.
```shell
composer require hyperf/code-generator
php vendor/bin/regenerate-models.php $PWD/app/Model
```
## Logger
`monolog/monolog` 3.x version uses the new features of PHP8.1, so some classes need to be specially modified
Modify `array $record` to `array|LogRecord $record` to be compatible with version 3.x, the sample code is as follows
```php
<?php
declare(strict_types=1);
namespace App\Kernel\Log;
use Hyperf\Context\Context;
use Hyperf\Utils\Coroutine;
use Monolog\LogRecord;
use Monolog\Processor\ProcessorInterface;
class AppendRequestIdProcessor implements ProcessorInterface
{
public const REQUEST_ID = 'log.request.id';
public function __invoke(array|LogRecord $record)
{
$record['extra']['request_id'] = Context::getOrSet(self::REQUEST_ID, uniqid());
$record['extra']['coroutine_id'] = Coroutine::id();
return $record;
}
}
```
##Command
After the `3.0` version, the command line has an event listener enabled by default, so when a listener listens to the `Command` event and performs `AMQP` or other multiplexing logic, the process will not be able to exit.
Solution
- method one:
When executing the command, add the option `--disable-event-dispatcher`
- Method Two:
add listener
```php
<?php
declare(strict_types=1);
namespace App\Listener;
use Hyperf\Command\Event\AfterExecute;
use Hyperf\Coordinator\Constants;
use Hyperf\Coordinator\CoordinatorManager;
use Hyperf\Event\Annotation\Listener;
use Hyperf\Event\Contract\ListenerInterface;
#[Listener]
class ResumeExitCoordinatorListener implements ListenerInterface
{
public function listen(): array
{
return [
AfterExecute::class,
];
}
public function process(object $event): void
{
CoordinatorManager::until(Constants::WORKER_EXIT)->resume();
}
}
```
## start the service
Next, you only need to start the service, you can see the incompatibility, and you can modify it one by one.
- AMQP Consumer and Producer
member variable added type
- Listener listener
`process` method adds `void` return type
- Annotation CircuitBreaker
Changed `$timeout` parameter to `$options.timeout`
## GRPC
The framework modifies the `Http status` returned by `GRPC Server` to be 200 according to the `GRPC` specification, and the `GRPC Server` returns the corresponding `status code`. Upgrade to the `3.x` version, otherwise the peer end cannot parse the error normally when the request is abnormal.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 152 KiB

View File

@ -2,14 +2,14 @@
## 版本生命周期 ## 版本生命周期
| 版本 | 状态 | 积极支持截止时间 | 安全维护截止时间 | 发布或预计发布时间 | | 版本 | 状态 | 积极支持截止时间 | 安全维护截止时间 | 发布或预计发布时间 |
| ---- |--------|------------|------------|------------| | ---- |----------------|------------|------------|---------------|
| 3.0 | 研发中 (RC1 已发布) | 2023-06-20 | 2023-12-31 | 2022-08-30~2022-09-30 | | 3.0 | 研发中 (RC13 已发布) | 2023-06-30 | 2023-12-31 | 大概 2022-12-31 |
| 2.2 | 积极支持中 | 2022-06-20 | 2022-12-31 | 2021-07-19 | | 2.2 | 安全维护 | 2022-06-20 | 2023-06-30 | 2021-07-19 |
| 2.1 | 停止维护 | 2021-06-30 | 2021-12-31 | 2020-12-28 | | 2.1 | 停止维护 | 2021-06-30 | 2021-12-31 | 2020-12-28 |
| 2.0 | 停止维护 | 2020-12-28 | 2021-06-30 | 2020-06-22 | | 2.0 | 停止维护 | 2020-12-28 | 2021-06-30 | 2020-06-22 |
| 1.1 | 停止维护 | 2020-06-23 | 2020-12-31 | 2019-10-08 | | 1.1 | 停止维护 | 2020-06-23 | 2020-12-31 | 2019-10-08 |
| 1.0 | 停止维护 | 2019-10-08 | 2019-12-31 | 2019-06-20 | | 1.0 | 停止维护 | 2019-10-08 | 2019-12-31 | 2019-06-20 |
* 积极支持将包含常规迭代周期的 BUG 修复、安全问题修复、功能迭代和功能新增; * 积极支持将包含常规迭代周期的 BUG 修复、安全问题修复、功能迭代和功能新增;
* 安全维护仅包含安全问题的修复; * 安全维护仅包含安全问题的修复;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 152 KiB