feat:docs:up 3.0

This commit is contained in:
liujian 2021-09-09 14:48:48 +08:00
parent 563c2963d5
commit 0aa75496af
28 changed files with 3205 additions and 76 deletions

2
docs/3.0/_navbar.md Normal file
View File

@ -0,0 +1,2 @@
* 多语言/Translations
* [简体中文](zh-cn/)

View File

@ -61,7 +61,7 @@
'https://github.com/mix-php/mix/blob/master/docs/' +
vm.route.file;
}
var editHtml = '<p style="text-align: right"><a href="https://github.com/mix-php/mix/blob/master/docs/3.0/zh-cn/README.md" target="_blank" rel="noopener"><img class="emoji" src="https://github.githubassets.com/images/icons/emoji/memo.png" alt="memo"> Edit Document</a></p>\n\n';
var editHtml = '<p style="text-align: right"><a href="https://github.com/mix-php/mix/blob/master/docs/3.0/zh-cn/README.md" target="_blank" rel="noopener"><img class="emoji" src="https://github.githubassets.com/images/icons/emoji/memo.png" alt="memo"> Edit on github</a></p>\n\n';
return (
editHtml +
html
@ -73,5 +73,6 @@
</script>
<!-- Docsify v4 -->
<script src="//cdn.jsdelivr.net/npm/docsify@4"></script>
<script src="//cdn.jsdelivr.net/npm/docsify/lib/plugins/search.min.js"></script>
</body>
</html>

View File

@ -10,7 +10,7 @@
<h1 align="center">Mix PHP</h1>
中文 | [English](README_EN.md)
## 简介
MixPHP 是一个 PHP 命令行模式开发框架;基于 `Vega` 驱动的 HTTP 可以同时支持 Swoole、WorkerMan、FPM、CLI-Server 生态,并且可以无缝切换;`V3` 是一个高度解耦的版本,整体代码基于多个独立的模块构建,即便用户不使用我们的脚手架,也可以使用这些独立模块,并且全部模块都支持原生开发。例如:你可以只使用 mix/vega 来搭配 laravel orm 使用;可以在任意环境中使用 mix/database 和 mix/redis可以使用 mix/grpc 原生代码编写 gRPC所有的模块你可以像搭积木一样随意组合。
@ -18,79 +18,28 @@ MixPHP 是一个 PHP 命令行模式开发框架;基于 `Vega` 驱动的 HTTP
核心模块全部可独立使用,并且都支持原生代码开发。
- [mix/vega](src/vega) PHP 编写的 CLI 模式 HTTP 网络框架,支持 Swoole、WorkerMan、FPM、CLI-Server
- [mix/database](src/database) 可在各种环境中使用的轻量数据库,支持 FPM、CLI、Swoole、WorkerMan可选的连接池 (协程)
- [mix/redis](src/redis) 可在各种环境中使用的 PHP Redis支持 FPM、CLI、Swoole、WorkerMan可选的连接池 (协程)
- [mix/redis-subscriber](src/redis-subscriber) 基于 Swoole 协程的 Redis 原生协议订阅库
- [mix/grpc](src/grpc) 基于 Swoole 协程的 PHP gRPC 库,包含 protoc 代码生成器、服务器、客户端
- [mix/websocket](src/websocket) 基于 Swoole 协程的 PHP WebSocket 服务器与客户端
- [mix/cli](src/cli) PHP 命令行交互指挥官
- [mix/worker-pool](src/worker-pool) 基于 Swoole 的协程池、工作池库
- [mix/validator](src/validator) 基于 PSR-7 的验证库
- [mix/event](src/event) 基于 PSR-14 标准的事件调度库
- [mix/vega](zh-cn/mix-vega) PHP 编写的 CLI 模式 HTTP 网络框架,支持 Swoole、WorkerMan、FPM、CLI-Server
- [mix/database](zh-cn/mix-database) 可在各种环境中使用的轻量数据库,支持 FPM、CLI、Swoole、WorkerMan可选的连接池 (协程)
- [mix/redis](zh-cn/mix-redis) 可在各种环境中使用的 PHP Redis支持 FPM、CLI、Swoole、WorkerMan可选的连接池 (协程)
- [mix/redis-subscriber](zh-cn/mix-redis-subscriber) 基于 Swoole 协程的 Redis 原生协议订阅库
- [mix/grpc](zh-cn/mix-grpc) 基于 Swoole 协程的 PHP gRPC 库,包含 protoc 代码生成器、服务器、客户端
- [mix/websocket](zh-cn/mix-websocket) 基于 Swoole 协程的 PHP WebSocket 服务器与客户端
- [mix/cli](zh-cn/mix-cli) PHP 命令行交互指挥官
- [mix/worker-pool](zh-cn/mix-worker-pool) 基于 Swoole 的协程池、工作池库
- [mix/validator](zh-cn/mix-validator) 基于 PSR-7 的验证库
- [mix/event](zh-cn/mix-event) 基于 PSR-14 标准的事件调度库
## 服务器
支持多种服务器驱动,并且可以无缝切换。
- [PHP Built-in CLI-Server](examples/api-skeleton/composer.json#L8) `零扩展依赖` `热更新` `适合本机开发`
- [PHP-FPM](examples/api-skeleton/public/index.php) `热更新` `适合共享开发` `适合 admin 开发`
- [Swoole](examples/api-skeleton/composer.json#L9) `常驻内存` `兼容 composer 生态`
- [Swoole Coroutine](examples/api-skeleton/composer.json#L10) `常驻内存` `协程性能强劲`
- [WorkerMan](examples/api-skeleton/composer.json#L11) `常驻内存` `兼容 composer 生态`
- [PHP Built-in CLI-Server](zh-cn/server-cli-server.md) `零扩展依赖` `热更新` `适合本机开发`
- [PHP-FPM](zh-cn/server-php-fpm.md) `热更新` `适合共享开发` `适合 admin 开发`
- [Swoole](zh-cn/server-swoole.md) `常驻内存` `兼容 composer 生态`
- [Swoole Coroutine](zh-cn/server-swoole-coroutine.md) `常驻内存` `协程性能强劲`
- [WorkerMan](zh-cn/server-workerman.md) `常驻内存` `兼容 composer 生态`
## 快速开始
提供了现成的脚手架,快速创建项目,立即产出。
- [编写一个 CLI 程序](examples/cli-skeleton#readme)
```
composer create-project --prefer-dist mix/cli-skeleton cli
```
- [编写一个 API 接口](examples/api-skeleton#readme)
```
composer create-project --prefer-dist mix/api-skeleton api
```
- [编写一个 Web 页面](examples/web-skeleton#readme)
```
composer create-project --prefer-dist mix/web-skeleton web
```
- [编写一个 WebSocket 服务](examples/websocket-skeleton#readme)
```
composer create-project --prefer-dist mix/websocket-skeleton websocket
```
- [编写一个 gRPC 接口](examples/grpc-skeleton#readme)
```
composer create-project --prefer-dist mix/grpc-skeleton grpc
```
## 性能测试
- [Web Frameworks Benchmark](https://web-frameworks-benchmark.netlify.app/result?l=php)
![web-frameworks-benchmark.png](web-frameworks-benchmark.png)
## 推荐阅读
- [MixPHP V3 开发流程体验 Swoole, Workerman, FPM, CLI-Server 多种运行模式介绍](https://zhuanlan.zhihu.com/p/398381870)
- [MixPHP V3 增加了 PHP-FPM、CLI-Server 的支持](https://zhuanlan.zhihu.com/p/394059925)
- [MixPHP V3 发布前的感想, 有哪些变化和特点](https://zhuanlan.zhihu.com/p/392558932)
## 技术交流
知乎https://www.zhihu.com/people/onanying
官方QQ群[284806582](https://shang.qq.com/wpa/qunwpa?idkey=b3a8618d3977cda4fed2363a666b081a31d89e3d31ab164497f53b72cf49968a), [825122875](http://shang.qq.com/wpa/qunwpa?idkey=d2908b0c7095fc7ec63a2391fa4b39a8c5cb16952f6cfc3f2ce4c9726edeaf20) 敲门暗号phper
## Golang 框架
## MixGo 框架
OpenMix 同时还有 Golang 生态的框架
@ -102,7 +51,3 @@ OpenMix 同时还有 Golang 生态的框架
- `V2.1` https://www.kancloud.cn/onanying/mixphp2-1/content
- `V2.0` https://www.kancloud.cn/onanying/mixphp2/content
- `V1.*` https://www.kancloud.cn/onanying/mixphp1/content
## License
Apache License Version 2.0, http://www.apache.org/licenses/

View File

@ -0,0 +1,2 @@
* 多语言/Translations
* [简体中文](zh-cn/)

View File

@ -0,0 +1,5 @@
## Web Frameworks Benchmark
- [Web Frameworks Benchmark](https://web-frameworks-benchmark.netlify.app/result?l=php)
<img width="60%" src="https://gitee.com/mix-php/mix/raw/master/web-frameworks-benchmark.png" data-origin="https://github.com/mix-php/mix/raw/master/web-frameworks-benchmark.png" alt="web-frameworks-benchmark.png">

211
docs/3.0/zh-cn/mix-cli.md Normal file
View File

@ -0,0 +1,211 @@
## Mix CLI
PHP 命令行交互指挥官
> go 版本https://github.com/mix-go/xcli
## Overview
一个命令行交互与指挥管理工具,它可以让单个 CLI 可执行多种功能,同时它还包括命令行参数获取、全局异常捕获与处理等命令行开发常用功能。
## Installation
```
composer require mix/cli
```
## Quick start
```php
Mix\Cli\Cli::setName('app')->setVersion('0.0.0-alpha');
$cmd = new Mix\Cli\Command([
'name' => 'hello',
'short' => 'Echo demo',
'run' => function () {
$name = Mix\Cli\Flag::match('n', 'name')->string('default');
// do something
}
]);
$opt = new Mix\Cli\Option([
'names' => ['n', 'name'],
'usage' => 'Your name'
]);
$cmd->addOption($opt);
Mix\Cli\Cli::addCommand($cmd)->run();
```
上面是采用闭包,也可以使用对象
```php
class FooCommand implements Mix\Cli\RunInterface
{
public function main(): void
{
// do something
}
}
$cmd = new Mix\Cli\Command([
'name' => 'hello',
'short' => 'Echo demo',
'run' => new FooCommand(),
]);
```
查看整个命令行程序的帮助
```
$ php app.php
Usage: app.php [OPTIONS] COMMAND [ARG...]
Commands:
hello Echo demo
Global Options:
-h, --help Print usage
-v, --version Print version information
Run 'app.php COMMAND --help' for more information on a command.
Developed with Mix PHP framework. (openmix.org/mix-php)
```
查看命令行程序的版本信息
```
$ php app.php -v
app 0.0.0-alpha
```
查看 `hello` 命令的帮助
```
$ php app.php hello --help
Usage: app.php hello [ARG...]
Command Options:
-n, --name Your name
Developed with Mix PHP framework. (openmix.org/mix-php)
```
执行 `hello` 命令
```
$ php app.php hello
```
## Flag 参数获取
参数规则 (部分UNIX风格+GNU风格)
```
php /examples/app.php home -d -rf --debug -v vvv --page 23 -s=test --name=john arg0
```
- 命令:
- 第一个参数,可以为空:`home`
- 选项:
- 短选项:一个中杠,如 `-d`、`-rf`
- 长选项:二个中杠,如:`--debug`
- 选项值:
- 无值:`-d`、`-rf`、 `--debug`
- 有值(空格)`-v vvv`、`--page 23`
- 有值(等号)`-s=test`、`--name=john`
- 参数:
- 没有定义 `-` 的参数:`arg0`
获取选项,可以获取 `string`、`bool`、`int`、`float` 多种类型,也可以指定默认值。
```php
$name = Mix\Cli\Flag::match('n', 'name')->string('Xiao Ming');
```
获取第一个参数
```php
$arg0 = Mix\Cli\Flag::arguments()->first()->string();
```
获取全部参数
```php
foreach (Mix\Cli\Flag::arguments()->values() as $k => $v) {
// do something
}
```
## Handle panic 错误处理
使用中间件处理异常,也可以单独对某个命令配置中间件
```php
$h = function ($next) {
try {
$next();
} catch (\Throwable $ex){
if ($ex instanceof Mix\Cli\Exception\NotFoundException) {
throw $ex;
}
// handle panic
}
};
$cmd = new Mix\Cli\Command([
'name' => 'hello',
'short' => 'Echo demo',
'run' => function () {
// do something
}
]);
Mix\Cli\Cli::use($h)->addCommand($cmd)->run();
```
## Application
我们在编写代码时,可能会要用到 App 中的一些信息。
```
// 获取基础路径(入口文件所在目录路径)
Mix\Cli\Cli::app()->basePath
// App名称
Mix\Cli\Cli::app()->name
// App版本号
Mix\Cli\Cli::app()->version
// 是否开启debug
Mix\Cli\Cli::app()->debug
```
## Singleton 单命令
当我们的 CLI 只有一个命令时,只需要配置一下 `Singleton`
~~~php
$cmd = new Mix\Cli\Command([
'name' => 'hello',
'short' => 'Echo demo',
'run' => function () {
// do something
},
'singleton' => true,
]);
~~~
命令的 Options 将会在 `-h/--help` 中打印
~~~
$ php app.php
Usage: app.php [OPTIONS] COMMAND [ARG...]
Command Options:
-n, --name Your name
Global Options:
-h, --help Print usage
-v, --version Print version information
Run 'app.php COMMAND --help' for more information on a command.
Developed with Mix PHP framework. (openmix.org/mix-php)
~~~

View File

@ -0,0 +1,350 @@
## Mix Database
可在各种环境中使用的轻量数据库,支持 FPM、CLI、Swoole、WorkerMan可选的连接池 (协程)
## 安装
```
composer require mix/database
```
## 快速开始
```php
$db = new Mix\Database\Database('mysql:host=127.0.0.1;port=3306;charset=utf8;dbname=test', 'username', 'password');
```
创建
```php
$db->insert('users', [
'name' => 'foo',
'balance' => 0,
]);
```
查询
```php
$db->table('users')->where('id = ?', 1)->first();
```
更新
```php
$db->table('users')->where('id = ?', 1)->update('name', 'foo1');
```
删除
```php
$db->table('users')->where('id = ?', 1)->delete();
```
## 启动连接池 Pool
`Swoole` 协程环境中,启动连接池
```php
$maxOpen = 50; // 最大开启连接数
$maxIdle = 20; // 最大闲置连接数
$maxLifetime = 3600; // 连接的最长生命周期
$waitTimeout = 0.0; // 从池获取连接等待的时间, 0为一直等待
$db->startPool($maxOpen, $maxIdle, $maxLifetime, $waitTimeout);
Swoole\Runtime::enableCoroutine(); // 必须放到最后,防止触发协程调度导致异常
```
连接池统计
```php
$db->poolStats(); // array, fields: total, idle, active
```
## 创建 Insert
创建
```php
$data = [
'name' => 'foo',
'balance' => 0,
];
$db->insert('users', $data);
```
获取 InsertId
```php
$data = [
'name' => 'foo',
'balance' => 0,
];
$insertId = $db->insert('users', $data)->lastInsertId();
```
替换创建
```php
$data = [
'name' => 'foo',
'balance' => 0,
];
$db->insert('users', $data, 'REPLACE INTO');
```
批量创建
```php
$data = [
[
'name' => 'foo',
'balance' => 0,
],
[
'name' => 'foo1',
'balance' => 0,
]
];
$db->batchInsert('users', $data);
```
使用函数创建
```php
$data = [
'name' => 'foo',
'balance' => 0,
'add_time' => new Mix\Database\Expr('CURRENT_TIMESTAMP()'),
];
$db->insert('users', $data);
```
## 查询 Select
### 获取结果
| 方法名称 | 描述 |
| ---- | ---- |
| get(): array | 获取多行 |
| first(): array or object | 获取第一行 |
| value(string $field): mixed | 获取第一行某个字段 |
| statement(): \PDOStatement | 获取原始结果集 |
### Where
#### AND
```php
$db->table('users')->where('id = ? AND name = ?', 1, 'foo')->get();
```
```php
$db->table('users')->where('id = ?', 1)->where('name = ?', 'foo')->get();
```
#### OR
```php
$db->table('users')->where('id = ? OR id = ?', 1, 2)->get();
```
```php
$db->table('users')->where('id = ?', 1)->or('id = ?', 2)->get();
```
#### IN
```php
$db->table('users')->where('id IN (?)', [1, 2])->get();
```
```php
$db->table('users')->where('id NOT IN (?)', [1, 2])->get();
```
### Select
```php
$db->table('users')->select('id, name')->get();
```
```php
$db->table('users')->select('id', 'name')->get();
```
```php
$db->table('users')->select('name AS n')->get();
```
### Order
```php
$db->table('users')->order('id', 'desc')->get();
```
```php
$db->table('users')->order('id', 'desc')->order('name', 'asc')->get();
```
### Limit
```php
$db->table('users')->limit(5)->get();
```
```php
$db->table('users')->offset(10)->limit(5)->get();
```
### Group & Having
```php
$db->table('news')->select('uid, COUNT(*) AS total')->group('uid')->having('COUNT(*) > ?', 0)->get();
```
```php
$db->table('news')->select('uid, COUNT(*) AS total')->group('uid')->having('COUNT(*) > ? AND COUNT(*) < ?', 0, 10)->get();
```
### Join
```php
$db->table('news AS n')->select('n.*, u.name')->join('users AS u', 'n.uid = u.id')->get();
```
```php
$db->table('news AS n')->select('n.*, u.name')->leftJoin('users AS u', 'n.uid = u.id AND u.balance > ?', 10)->get();
```
## 更新 Update
更新单个字段
```php
$db->table('users')->where('id = ?', 1)->update('name', 'foo1');
```
获取影响行数
```php
$rowsAffected = $db->table('users')->where('id = ?', 1)->update('name', 'foo1')->rowCount();
```
更新多个字段
```php
$data = [
'name' => 'foo1',
'balance' => 100,
];
$db->table('users')->where('id = ?', 1)->updates($data);
```
使用表达式更新
```php
$db->table('users')->where('id = ?', 1)->update('balance', new Mix\Database\Expr('balance + ?', 1));
```
```php
$data = [
'balance' => new Mix\Database\Expr('balance + ?', 1),
];
$db->table('users')->where('id = ?', 1)->updates($data);
```
使用函数更新
```php
$db->table('users')->where('id = ?', 1)->update('add_time', new Mix\Database\Expr('CURRENT_TIMESTAMP()'));
```
```php
$data = [
'add_time' => new Mix\Database\Expr('CURRENT_TIMESTAMP()'),
];
$db->table('users')->where('id = ?', 1)->updates($data);
```
## 删除 Delete
删除
```php
$db->table('users')->where('id = ?', 1)->delete();
```
获取影响行数
```php
$rowsAffected = $db->table('users')->where('id = ?', 1)->delete()->rowCount();
```
## 原生 Raw
```php
$db->raw('SELECT * FROM users WHERE id = ?', 1)->first();
```
```php
$db->exec('DELETE FROM users WHERE id = ?', 1)->rowCount();
```
## 事务 Transaction
手动事务
```php
$tx = $db->beginTransaction();
try {
$data = [
'name' => 'foo',
'balance' => 0,
];
$tx->insert('users', $data);
$tx->commit();
} catch (\Throwable $ex) {
$tx->rollback();
throw $ex;
}
```
自动事务,执行异常自动回滚并抛出异常
```php
$db->transaction(function (Mix\Database\Transaction $tx) {
$data = [
'name' => 'foo',
'balance' => 0,
];
$tx->insert('users', $data);
});
```
## 调试 Debug
```php
$db->debug(function (Mix\Database\ConnectionInterface $conn) {
var_dump($conn->queryLog()); // array, fields: time, sql, bindings
})
->table('users')
->where('id = ?', 1)
->get();
```
## 日志 Logger
日志记录器配置后可打印全部SQL信息
```php
$db->setLogger($logger);
```
`$logger` 需实现 `Mix\Database\LoggerInterface`
```php
interface LoggerInterface
{
public function trace(float $time, string $sql, array $bindings, int $rowCount, ?\Throwable $exception): void;
}
```

View File

@ -0,0 +1,65 @@
## Mix Event
基于 PSR-14 标准的事件调度库
## 安装
```
composer require mix/event
```
## 定义一个事件
事件可以为任意类,我们以 SQL 执行事件调度来举例
```php
class DatabaseEvent
{
public $time = 0;
public $sql = '';
public $bindings = [];
}
```
## 定义一个监听器
监听器是用户编写处理事件逻辑代码的地方,`events` 方法返回一个要监听的事件类的数组,当这些事件触发时,会调用 `process` 方法
```php
class DatabaseListener implements Mix\Event\ListenerInterface
{
public function events(): array
{
// 要监听的事件数组,可监听多个事件
return [
DatabaseEvent::class,
];
}
public function process(object $event): void
{
// 事件触发后,会执行该方法
}
}
```
## 创建调度器
创建调度器,并传入监听器,可传入多个
```php
$dispatcher = new Mix\Event\EventDispatcher(new DatabaseListener());
```
## 触发事件
在事件产生的位置触发事件,当后面需要对该事件扩展业务逻辑时,只需在监听器中增加代码即可,达到不污染正常业务流程的目的
```php
$event = new DatabaseEvent();
$event->time = 10;
$event->sql = 'select * from users';
$dispatcher->dispatch($event);
```

230
docs/3.0/zh-cn/mix-grpc.md Normal file
View File

@ -0,0 +1,230 @@
## Mix gRPC
基于 Swoole 协程的 PHP gRPC 库,包含 protoc 代码生成器、服务器、客户端
## 简介
由于 PHP-FPM 的特殊生命周期,导致 PHP 的 gRPC 官方代码生成器只能生成数据结构和客户端代码,无法像 golang/node.js/python 一样能同时生成服务器代码;传统方式如果要搭建 PHP gRPC 服务器只能借助
nginx+h2+phpfpm 来搭建,这样就不需要 server 代码了,但是短生命周期又无法很好的支持服务注册,因为这些原因导致 PHP 在 gRPC 中一直都是充当 Client 的角色Mix gRPC 提供了基于 Swoole
的方案:
- 使用 [Swoole](https://github.com/swoole/swoole-src) 作为 gRPC Server
- 使用 Golang 打造的 protoc-gen-mix 来生成 service 的 server/client 代码
- 完全独立,可在任何 CLI 模式的 php 代码中执行,任何框架的 CLI 模式中执行 Laravel、ThinkPHP、MixPHP 等都可以
- 同时为了降低门槛,我已经将 protoc、protoc-gen-mix 文件编译好了 win、linux、macOS 三个系统的二进制文件,直接下载即可
让 PHP 编写 gRPC 和 Golang 一样方便快捷,同时性能强劲
**推荐搭配以下数据库使用 (支持协程和连接池)**
- https://github.com/mix-php/database
- https://github.com/mix-php/redis
## 安装
- Swoole >= 4.4.4: https://wiki.swoole.com/#/environment
- 需要开启 `--enable-http2`
```
composer require mix/grpc
```
## 下载 protoc 与相关 plugin
- [protoc](https://github.com/protocolbuffers/protobuf) 是 protobuf 数据结构代码生成器,负责将 .proto 数据结构文件生成为对应语言的 class、struct
供程序使用,
- [protoc-gen-mix](https://github.com/mix-php/grpc/tree/master/protoc-gen-mix) 是 mix 开发的 protoc 插件,用来生成 service 的
server/client 代码。
以上 2 个二进制文件,我都帮你们编译好了,包含多个常用 OS 类型,直接下载即可:
- [下载 protoc_mix_plugin](https://github.com/mix-php/grpc/releases/tag/binary-210714) `win/macos/linux`
下载完成后 linux、macOS 将二进制文件放入系统 `/usr/local/bin` 目录win 放入 `C:\WINDOWS\system32`
## 通过 .proto 生成 PHP 代码
首先我们编写一个 proto 文件:
```
syntax = "proto3";
package php.micro.grpc.greeter;
service Say {
rpc Hello(Request) returns (Response) {}
}
message Request {
string name = 1;
}
message Response {
string msg = 1;
}
```
然后使用 protoc 生成代码:
```
protoc --php_out=. --mix_out=. greeter.proto
```
执行命令后将在当前目录生成以下文件:
```
|-- GPBMetadata
| `-- Greeter.php
|-- Php
| `-- Micro
| `-- Grpc
| `-- Greeter
| |-- Request.php
| |-- Response.php
| |-- SayClient.php
| `-- SayInterface.php
`-- greeter.proto
```
其中 Request.php、Response.php 为 `--php_out` 生成SayClient.php SayInterface.php 由 `--mix_out` 生成。
接下来我们将生成的文件加入到 composer autoload 中,我们修改 composer.json
```
"autoload-dev": {
"psr-4": {
"GPBMetadata\\": "protodir/GPBMetadata/",
"Php\\": "protodir/Php/"
}
}
```
修改后执行 `composer dump-aotoload` 使其生效。
## 编写一个 gRPC 服务
我们用原生 PHP 代码来编写一个 gRPC 服务器:
```php
// 编写一个服务,实现 protoc-gen-mix 生成的接口
class SayService implements Php\Micro\Grpc\Greeter\SayInterface
{
public function Hello(Mix\Grpc\Context $context, Php\Micro\Grpc\Greeter\Request $request): Php\Micro\Grpc\Greeter\Response
{
$response = new Php\Micro\Grpc\Greeter\Response();
$response->setMsg(sprintf('hello, %s', $request->getName()));
return $response;
}
}
$grpc = new Mix\Grpc\Server();
$grpc->register(SayService::class); // or $grpc->register(new SayService());
```
Swoole 多进程 (异步) 中使用
```php
$http = new Swoole\Http\Server('0.0.0.0', 9595);
$http->on('Request', $grpc->handler());
$http->set([
'worker_num' => 4,
'open_http2_protocol' => true,
'http_compression' => false,
]);
$http->start();
```
开启多进程协程
```php
$http->on('Request', $grpc->handler());
$http->on('WorkerStart', function ($server, $workerId) {
// 协程初始化
// 比如:启动 mix/database mix/redis 的连接池
});
$http->set([
'enable_coroutine' => true,
'worker_num' => 4,
'open_http2_protocol' => true,
'http_compression' => false,
]);
```
Swoole 单进程 (协程) 中使用
```php
Swoole\Coroutine\run(function () use ($grpc) {
$server = new Swoole\Coroutine\Http\Server('0.0.0.0', 9595, false);
$server->handle('/', $grpc->handler());
$server->set([
'open_http2_protocol' => true,
'http_compression' => false,
]);
$server->start();
});
```
## 客户端调用一个 gRPC 服务
通过 IP 端口调用 gRPC 服务
```php
Swoole\Coroutine\run(function () {
$client = new Mix\Grpc\Client('127.0.0.1', 9595);
$say = new Php\Micro\Grpc\Greeter\SayClient($client);
$request = new Php\Micro\Grpc\Greeter\Request();
$request->setName('xiaoming');
$ctx = new Mix\Grpc\Context();
$response = $say->Hello($ctx, $request);
var_dump($response->getMsg());
});
```
设置 `header`
```php
$ctx->withHeader('foo', 'bar');
$response = $say->Hello($ctx, $request);
```
设置 `timeout`
```php
$ctx->withTimeout(5.0);
$response = $say->Hello($ctx, $request);
```
## FPM 如何调用 gRPC 服务
像我们传统 PHP FPM 模式中,我们作为客户端调用 gRPC 比 Mix gRPC 提供的客户端要复杂很多,但是我们也经常需要用到,比如在 thinkphp laravel 中调用 Mix gRPC 或者 Mix Go 编写的 gRPC
服务,推荐阅读以下文章:
- [gRPC入坑记](https://www.cnblogs.com/52fhy/p/11110704.html#php%E7%9B%B8%E5%85%B3%E6%94%AF%E6%8C%81)
- [PHP中使用gRPC客户端](https://bbs.huaweicloud.com/blogs/135609)
网上的文章都缺少重要的一环,就是:
```
protoc --php_out=. greeter.proto
```
命令执行时,只会生成数据结构的 class 文件,不会生成 grpc 服务的客户端 class 文件
```
service Say {
rpc Hello(Request) returns (Response) {}
}
```
以上服务没有被处理,没有生成出 `SayClient.php` ,需要修改编译命令
```
protoc --php_out=. --grpc_out=. --plugin=protoc-gen-grpc=/path/grpc_php_plugin greeter.proto
```
命令中指定了一个 `grpc_php_plugin` 文件是由 [grpc/grpc](https://github.com/grpc/grpc/tree/master/src/php) 提供的源码,官方没有像 `protoc`
一样提供编译好的二进制可以下载,只能自己编译。然而这个库依赖的大量的子仓库,在国内几乎无法拉取成功,其次 win 的 cmake 编译很多人不会弄,导致大量的人无法编译出这个文件,因此我这里直接提供编译好的二进制供大家下载。
- [下载 protoc_grpc_plugin](https://github.com/mix-php/grpc/releases/tag/binary-210714) `win/macos/linux`

View File

@ -0,0 +1,59 @@
## Mix Redis Subscriber
基于 Swoole 协程的 Redis 原生协议订阅库
使用 Socket 直接连接 Redis 服务器,不依赖 phpredis 扩展,该订阅器有如下优点:
- 平滑修改:可随时增加、取消订阅通道,实现无缝切换通道的需求。
- 跨协程安全关闭:可在任意时刻关闭订阅。
- 通道获取消息:该库封装风格参考 golang 语言 [go-redis](https://github.com/go-redis/redis) 库封装,通过 channel 获取订阅的消息。
## 安装
- Swoole >= 4.4
```
composer require mix/redis-subscriber
```
## 订阅频道
- 连接、订阅失败会抛出异常
```php
$sub = new \Mix\Redis\Subscriber\Subscriber('127.0.0.1', 6379, '', 5); // 连接失败将抛出异常
$sub->subscribe('foo', 'bar'); // 订阅失败将抛出异常
$chan = $sub->channel();
while (true) {
$data = $chan->pop();
if (empty($data)) { // 手动close与redis异常断开都会导致返回false
if (!$sub->closed) {
// redis异常断开处理
var_dump('Redis connection is disconnected abnormally');
}
break;
}
var_dump($data);
}
```
接收到订阅消息:
```
object(Mix\Redis\Subscriber\Message)#8 (2) {
["channel"]=>
string(2) "foo"
["payload"]=>
string(4) "test"
}
```
## 全部方法
| 方法 | 描述 |
| --- | --- |
| subscribe(string ...$channels) : void | 增加订阅 |
| unsubscribe(string ...$channels) : void | 取消订阅 |
| channel() : Swoole\Coroutine\Channel | 获取消息通道 |
| close() : void | 关闭订阅 |

View File

@ -0,0 +1,88 @@
## Mix Redis
可在各种环境中使用的 PHP Redis支持 FPM、CLI、Swoole、WorkerMan可选的连接池 (协程)
## Installation
```
composer require mix/redis
```
## Quick start
```php
$rds = new Mix\Redis\Redis('127.0.0.1', 6379, 'password', 0);
$rds->set('foo', 'bar');
$value = $rds->get('foo');
```
## Start Pool
`Swoole` 协程环境中,启动连接池
```php
$maxOpen = 50; // 最大开启连接数
$maxIdle = 20; // 最大闲置连接数
$maxLifetime = 3600; // 连接的最长生命周期
$waitTimeout = 0.0; // 从池获取连接等待的时间, 0为一直等待
$rds->startPool($maxOpen, $maxIdle, $maxLifetime, $waitTimeout);
Swoole\Runtime::enableCoroutine(); // 必须放到最后,防止触发协程调度导致异常
```
连接池统计
```php
$rds->poolStats(); // array, fields: total, idle, active
```
## Transaction Multi & Pipeline
Multi
事务块内的多条命令会按照先后顺序被放进一个队列当中最后由exec命令原子性(atomic)地执行。
```php
$tx = $rds->multi();
$tx->set('foo', 'bar');
$tx->set('foo1', 'bar1');
$ret = $tx->exec();
```
Pipeline
客户端将执行的命令写入到缓冲中最后由exec命令一次性发送给redis执行返回。
```php
$tx = $rds->pipeline();
$tx->set('foo', 'bar');
$tx->set('foo1', 'bar1');
$ret = $tx->exec();
```
## Transaction Watch
监听值的变化,如果执行时有变化则事务失败,无变化则事务成功。
```php
$tx = $rds->watch('foo');
$tx->incr('foo');
$ret = $tx->exec();
```
## Logger
日志记录器配置后可打印全部SQL信息
```php
$db->setLogger($logger);
```
`$logger` 需实现 `Mix\Redis\LoggerInterface`
```php
interface LoggerInterface
{
public function trace(float $time, string $cmd, array $args, ?\Throwable $exception): void;
}
```

View File

@ -0,0 +1,186 @@
## Mix Validator
基于 PSR-7 标准的验证器
## 安装
```
composer require mix/validator
```
## 创建表单
继承 `Mix\Validator\Validator` 并定义:
- `public $name` 字段名
- `rules()` 验证规则
- `scenarios()` 验证场景
- `messages()` 错误消息
```php
<?php
namespace App\Forms;
use Mix\Validator\Validator;
class UserForm extends Validator
{
/**
* @var string
*/
public $name;
/**
* @var string
*/
public $age;
/**
* @var string
*/
public $email;
/**
* @return array
*/
public function rules(): array
{
return [
'name' => ['string', 'maxLength' => 25, 'filter' => ['trim']],
'age' => ['integer', 'unsigned' => true, 'min' => 1, 'max' => 120],
'email' => ['email'],
];
}
/**
* @return array
*/
public function scenarios(): array
{
return [
'create' => ['required' => ['name'], 'optional' => ['email', 'age']],
];
}
/**
* @return array
*/
public function messages(): array
{
return [
'name.required' => '名称不能为空.',
'name.maxLength' => '名称最多不能超过25个字符.',
'age.integer' => '年龄必须是数字.',
'age.unsigned' => '年龄不能为负数.',
'age.min' => '年龄不能小于1.',
'age.max' => '年龄不能大于120.',
'email' => '邮箱格式错误.',
];
}
}
```
## 在控制器中验证
```php
// 使用表单验证
$form = new UserForm($request->getAttributes());
if (!$form->scenario('create')->validate()) {
$data = ['code' => 1, 'message' => $form->error()];
$ctx->JSON(200, $data);
return;
}
// 将表单对象直接传递到模型中保存数据
(new UserModel())->add($form);
```
- `$form->error() : string` 获取单条错误信息
- `$form->errors() : array` 获取全部错误信息
## 验证规则
全部的验证类型与对应的验证选项如下
```php
public function rules(): array
{
return [
'a' => ['integer', 'unsigned' => true, 'min' => 1, 'max' => 1000000, 'length' => 10, 'minLength' => 3, 'maxLength' => 5],
'b' => ['double', 'unsigned' => true, 'min' => 1, 'max' => 1000000, 'length' => 10, 'minLength' => 3, 'maxLength' => 5],
'c' => ['alpha', 'length' => 10, 'minLength' => 3, 'maxLength' => 5],
'd' => ['alphaNumeric', 'length' => 10, 'minLength' => 3, 'maxLength' => 5],
'e' => ['string', 'length' => 10, 'minLength' => 3, 'maxLength' => 5, 'filter' => ['trim', 'strip_tags', 'htmlspecialchars']],
'f' => ['email', 'length' => 10, 'minLength' => 3, 'maxLength' => 5],
'g' => ['phone'],
'h' => ['url', 'length' => 10, 'minLength' => 3, 'maxLength' => 5],
'i' => ['in', 'range' => ['A', 'B'], 'strict' => true],
'j' => ['date', 'format' => 'Y-m-d'],
'k' => ['compare', 'compareAttribute' => 'a'],
'l' => ['match', 'pattern' => '/^[\w]{1,30}$/'],
'm' => ['call', 'callback' => [$this, 'check']],
'n' => ['file', 'mimes' => ['audio/mp3', 'video/mp4'], 'maxSize' => 1024 * 1],
'r' => ['image', 'mimes' => ['image/gif', 'image/jpeg', 'image/png'], 'maxSize' => 1024 * 1],
];
}
```
### call 验证类型
该类型为用户自定义验证规则callback 内指定一个用户自定义的方法来验证
```php
public function check($fieldValue): bool
{
// 验证代码
// ...
return true;
}
```
### file / image 验证类型
该类型用来验证文件,包含的两个验证选项如下:
- mimes输入你想要限制的文件mime类型[MIME参考手册](http://www.w3school.com.cn/media/media_mimeref.asp)
- maxSize允许的文件最大尺寸单位 KB
验证成功后模型类会增加一个同名属性,该属性为 Psr\Http\Message\UploadedFileInterface 类的实例化对象,可直接调用 $this->[attributeName]->moveTo($targetPath) 移动到你需要存放的位置
## 静态调用
```php
// 验证是否为字母与数字
Mix\Validator\Validate::isAlphaNumeric($value);
// 验证是否为字母
Mix\Validator\Validate::isAlpha($value);
// 验证是否为日期
Mix\Validator\Validate::isDate($value, $format);
// 验证是否为浮点数
Mix\Validator\Validate::isDouble($value);
// 验证是否为邮箱
Mix\Validator\Validate::isEmail($value);
// 验证是否为整数
Mix\Validator\Validate::isInteger($value);
// 验证是否在某个范围
Mix\Validator\Validate::in($value, $range, $strict = false);
// 正则验证
Mix\Validator\Validate::match($value, $pattern);
// 验证是否为手机
Mix\Validator\Validate::isPhone($value);
// 验证是否为网址
Mix\Validator\Validate::isUrl($value);
```

454
docs/3.0/zh-cn/mix-vega.md Normal file
View File

@ -0,0 +1,454 @@
## Mix Vega
Vega 是一个用 PHP 编写的 CLI 模式 HTTP 网络框架,支持 Swoole、WorkerMan、FPM、CLI-Server
## 简介
Vega 是 [MixPHP](https://github.com/mix-php/mix) `V3+` 内置的最核心的组件 (可独立使用),参考
golang [gin](https://github.com/gin-gonic/gin) [mux](https://github.com/gorilla/mux) 开发,它包含 Web 应用处理的大量功能 (数据库处理除外)
,包括:路由、渲染、参数获取、中间件、文件上传、静态文件处理等;具有 CLI 模式下强大的兼容性,同时支持 Swoole、WorkerMan、FPM、CLI-Server, 并且支持 Swoole 的多种进程模型与协程。
**推荐搭配以下数据库使用:**
- https://github.com/mix-php/database
- https://github.com/mix-php/redis
- https://github.com/top-think/think-orm
- https://github.com/illuminate/database
**推荐文章:**
- [使用 mix/vega + mix/db 进行现代化的原生 PHP 开发](https://zhuanlan.zhihu.com/p/387493850)
## 安装
> 需先安装 [Swoole](https://wiki.swoole.com/#/environment) 或者 [WorkerMan](http://doc.workerman.net/install/requirement.html)
```
composer require mix/vega
```
## 快速开始
- Swoole 多进程 (异步) 中使用
```php
<?php
require __DIR__ . '/vendor/autoload.php';
$vega = new Mix\Vega\Engine();
$vega->handle('/hello', function (Mix\Vega\Context $ctx) {
$ctx->string(200, 'hello, world!');
})->methods('GET');
$http = new Swoole\Http\Server('0.0.0.0', 9501);
$http->on('Request', $vega->handler());
$http->set([
'worker_num' => 4,
]);
$http->start();
```
开启多进程协程
```php
$http->on('Request', $vega->handler());
$http->on('WorkerStart', function ($server, $workerId) {
// 协程初始化
// 比如:启动 mix/database mix/redis 的连接池
});
$http->set([
'enable_coroutine' => true,
'worker_num' => 4,
]);
```
```
php swoole.php
```
- Swoole 单进程 (协程) 中使用
```php
<?php
require __DIR__ . '/vendor/autoload.php';
Swoole\Coroutine\run(function () {
$vega = new Mix\Vega\Engine();
$vega->handle('/hello', function (Mix\Vega\Context $ctx) {
$ctx->string(200, 'hello, world!');
})->methods('GET');
$server = new Swoole\Coroutine\Http\Server('0.0.0.0', 9502, false);
$server->handle('/', $vega->handler());
$server->start();
});
```
```
php swooleco.php
```
- WorkerMan 中使用
```php
<?php
require __DIR__ . '/vendor/autoload.php';
$vega = new Mix\Vega\Engine();
$vega->handle('/hello', function (Mix\Vega\Context $ctx) {
$ctx->string(200, 'hello, world!');
})->methods('GET');
$http_worker = new Workerman\Worker("http://0.0.0.0:2345");
$http_worker->onMessage = $vega->handler();
$http_worker->count = 4;
Workerman\Worker::runAll();
```
```
php wokerman.php start
```
- PHP-FPM 中使用
`nginx` 配置 `rewrite` 重写到 `index.php`
```php
<?php
require __DIR__ . '/vendor/autoload.php';
$vega = new Mix\Vega\Engine();
$vega->handle('/hello', function (Mix\Vega\Context $ctx) {
$ctx->string(200, 'hello, world!');
})->methods('GET');
return $vega->run();
```
- PHP [cli-server](https://www.php.net/manual/zh/features.commandline.webserver.php) 中使用
这个内置的Web服务器主要用于本地开发使用不可用于线上产品环境。
```php
<?php
require __DIR__ . '/vendor/autoload.php';
$vega = new Mix\Vega\Engine();
$vega->handle('/hello', function (Mix\Vega\Context $ctx) {
$ctx->string(200, 'hello, world!');
})->methods('GET');
return $vega->run();
```
```
php -S localhost:8000 router.php
```
- 访问测试
~~~
% curl http://127.0.0.1:9501/hello
hello, world!
~~~
## 路由配置
配置 `Closure` 闭包路由
```php
$vega = new Mix\Vega\Engine();
$vega->handle('/hello', function (Mix\Vega\Context $ctx) {
$ctx->string(200, 'hello, world!');
})->methods('GET');
```
配置 `callable` 路由
```php
class Hello {
public function index(Mix\Vega\Context $ctx) {
$ctx->string(200, 'hello, world!');
}
}
$vega = new Mix\Vega\Engine();
$vega->handle('/hello', [new Hello(), 'index'])->methods('GET');
```
配置路由变量
```php
$vega = new Mix\Vega\Engine();
$vega->handle('/users/{id:\d+}', function (Mix\Vega\Context $ctx) {
$id = $ctx->param('id');
$ctx->string(200, 'hello, world!');
})->methods('GET');
```
配置多个 `method`
```php
$vega = new Mix\Vega\Engine();
$vega->handle('/hello', function (Mix\Vega\Context $ctx) {
$ctx->string(200, 'hello, world!');
})->methods('GET', 'POST');
```
## 路由前缀 (分组)
```php
$vega = new Mix\Vega\Engine();
$sub = $vega->pathPrefix('/foo');
$sub->handle('/bar1', function (Mix\Vega\Context $ctx) {
$ctx->string(200, 'hello, world!');
})->methods('GET');
$sub->handle('/bar2', function (Mix\Vega\Context $ctx) {
$ctx->string(200, 'hello1, world!');
})->methods('GET');
```
## 参数获取
### 请求参数
| 方法名称 | 描述 |
| ---- | ---- |
| $ctx->request: ServerRequestInterface | 符合PSR的请求对象 |
| $ctx->response: ResponseInterface | 符合PSR的响应对象 |
| $ctx->param(string $key): string | 获取路由参数 |
| $ctx->query(string $key): string | 获取url参数包含路由参数 |
| $ctx->defaultQuery(string $key, string $default): string | 获取url参数可配置默认值 |
| $ctx->getQuery(string $key): string or null | 获取url参数, 可判断是否存在 |
| $ctx->postForm(string $key): string | 获取post参数 |
| $ctx->defaultPostForm(string $key, string $default): string | 获取post参数可配置默认值 |
| $ctx->getPostForm(string $key): string or null | 获取post参数可判断是否存在 |
### Headers, Cookies, Uri ...
| 方法名称 | 描述 |
| ---- | ---- |
| $ctx->method(): string | 请求类型 |
| $ctx->contentType(): string | 文档类型 |
| $ctx->header(string $key): string | 请求头 |
| $ctx->cookie(string $name): string | cookies |
| $ctx->uri(): UriInterface | 完整uri |
| $ctx->rawData(): string | 原始包数据 |
### 客户端IP
| 方法名称 | 描述 |
| ---- | ---- |
| $ctx->clientIP(): string | 从反向代理获取用户真实IP |
| $ctx->remoteIP(): string | 获取远程IP |
## 上传文件处理
| 方法名称 | 描述 |
| ---- | ---- |
| $ctx->formFile(string $name): UploadedFileInterface | 获取上传的第一个文件 |
| $ctx->multipartForm(): UploadedFileInterface[] | 获取上传的全部文件 |
文件保存
```php
$file = $ctx->formFile('img');
$targetPath = '/data/project/public/uploads/' . $file->getClientFilename();
$file->moveTo($targetPath);
```
## 请求上下文
请求当中需要保存一些信息比如会话、JWT载荷等。
| 方法名称 | 描述 |
| ---- | ---- |
| $ctx->set(string $key, $value): void | 设置值 |
| $ctx->get(string $key): mixed or null | 获取值 |
| $ctx->mustGet(string $key): mixed or throws | 获取值或抛出异常 |
## 中断执行
`abort` 执行后,会停止执行后面的全部代码,包括中间件。
| 方法名称 | 描述 |
| ---- | ---- |
| $ctx->abort(): void | 中断,需自行处理响应 |
| $ctx->abortWithStatus(int $code): void | 中断并响应状态码 |
| $ctx->abortWithStatusJSON(int $code, $data): void | 中断并响应JSON |
```php
$vega = new Mix\Vega\Engine();
$vega->handle('/users/{id}', function (Mix\Vega\Context $ctx) {
if (true) {
$ctx->string(401, 'Unauthorized');
$ctx->abort();
}
$ctx->string(200, 'hello, world!');
})->methods('GET');
```
## 响应处理
| 方法名称 | 描述 |
| ---- | ---- |
| $ctx->status(int $code): void | 设置状态码 |
| $ctx->setHeader(string $key, string $value): void | 设置header |
| $ctx->setCookie(string $name, string $value, int $expire = 0, ...): void | 设置cookie |
| $ctx->redirect(string $location, int $code = 302): void | 重定向 |
## JSON 请求与输出
获取 JSON 请求数据
```php
$vega = new Mix\Vega\Engine();
$vega->handle('/users', function (Mix\Vega\Context $ctx) {
$obj = $ctx->getJSON();
if (!$obj) {
throw new \Exception('Parameter error');
}
var_dump($obj);
$ctx->JSON(200, [
'code' => 0,
'message' => 'ok'
]);
})->methods('POST');
```
`mustGetJSON` 自带有效性检查,以下代码等同于上面
```php
$vega = new Mix\Vega\Engine();
$vega->handle('/users', function (Mix\Vega\Context $ctx) {
$obj = $ctx->mustGetJSON();
var_dump($obj);
$ctx->JSON(200, [
'code' => 0,
'message' => 'ok'
]);
})->methods('POST');
```
### JSONP 处理
```php
$vega = new Mix\Vega\Engine();
$vega->handle('/jsonp', function (Mix\Vega\Context $ctx) {
$ctx->JSONP(200, [
'code' => 0,
'message' => 'ok'
]);
})->methods('GET');
```
## HTML 视图渲染
创建视图文件 `foo.php`
```php
<p>id: <?= $id ?>, name: <?= $name ?></p>
<p>friends:</p>
<ul>
<?php foreach($friends as $name): ?>
<li><?= $name ?></li>
<?php endforeach; ?>
</ul>
```
配置视图路径并响应html
```php
$vega = new Mix\Vega\Engine();
$vega->withHTMLRoot('/data/project/views');
$vega->handle('/html', function (Mix\Vega\Context $ctx) {
$ctx->HTML(200, 'foo', [
'id' => 1000,
'name' => '小明',
'friends' => [
'小花',
'小红'
]
]);
})->methods('GET');
```
## 静态文件处理
基于 `sendfile` 零拷贝,不支持在 `PHP-FPM` 中使用
```php
$vega = new Mix\Vega\Engine();
$vega->static('/static', '/data/project/public/static');
$vega->staticFile('/favicon.ico', '/data/project/public/favicon.ico');
```
## 设置中间件
给某个路由配置中间件,可配置多个
```php
$vega = new Mix\Vega\Engine();
$func = function (Mix\Vega\Context $ctx) {
// do something
$ctx->next();
};
$vega->handle('/hello', $func, function (Mix\Vega\Context $ctx) {
$ctx->string(200, 'hello, world!');
})->methods('GET');
```
配置全局中间件,即便没有匹配到路由也会执行
```php
$vega = new Mix\Vega\Engine();
$vega->use(function (Mix\Vega\Context $ctx) {
$ctx->next();
});
```
前置中间件
```php
$vega->use(function (Mix\Vega\Context $ctx) {
// do something
$ctx->next();
});
```
后置中间件
```php
$vega->use(function (Mix\Vega\Context $ctx) {
$ctx->next();
// do something
});
```
### 404 自定义
```php
$vega = new Mix\Vega\Engine();
$vega->use(function (Mix\Vega\Context $ctx) {
try{
$ctx->next();
} catch (Mix\Vega\Exception\NotFoundException $ex) {
$ctx->string(404, 'New 404 response');
$ctx->abort();
}
});
```
### 500 全局异常捕获
```php
$vega = new Mix\Vega\Engine();
$vega->use(function (Mix\Vega\Context $ctx) {
try{
$ctx->next();
} catch (\Throwable $ex) {
if ($ex instanceof Mix\Vega\Abort || $ex instanceof Mix\Vega\Exception\NotFoundException) {
throw $ex;
}
$ctx->string(500, 'New 500 response');
$ctx->abort();
}
});
```

View File

@ -0,0 +1,96 @@
## Mix WebSocket
基于 Swoole 协程的 PHP WebSocket 服务器与客户端
## 简介
该 WebSocket 支持处理服务器和客户端,服务器基于 Swoole 单进程协程 `Swoole\Coroutine\Http\Server` 驱动,没有多进程那些复杂的作用域和生命周期概念,开发体验和 Golang 一致,简单又高效。
**推荐搭配以下数据库使用 (支持协程和连接池)**
- https://github.com/mix-php/database
- https://github.com/mix-php/redis
**推荐搭配以下库处理 Subscribe**
- https://github.com/mix-php/redis-subscriber
## 安装
- Swoole >= 4.4.15: https://wiki.swoole.com/#/environment
```
composer require mix/websocket
```
## 服务器 Server
在 [Mix Vega](https://github.com/mix-php/vega) 中使用 (只支持 Swoole 单进程协程)
```php
$upgrader = new Mix\WebSocket\Upgrader();
$vega = new Mix\Vega\Engine();
$vega->handleFunc('/websocket', function (Mix\Vega\Context $ctx) use ($upgrader) {
// 升级连接
$conn = $upgrader->upgrade($ctx->request, $ctx->response);
// 接收消息
$in = $conn->readMessage();
var_dump($in->data);
// 发送消息
$out = new \Swoole\WebSocket\Frame();
$out->data = sprintf('hello, %s', $in->data);
$conn->send($out);
$conn->close();
})->methods('GET');
```
在 Swoole 原生中使用 (只支持单进程协程)
```php
Swoole\Coroutine\run(function () {
$upgrader = new Mix\WebSocket\Upgrader();
$server = new Swoole\Coroutine\Http\Server('0.0.0.0', 9502, false);
$server->handle('/websocket', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($upgrader) {
// 升级连接
$conn = $upgrader->upgradeRaw($request, $response);
// ...
});
$server->start();
});
```
获取当前连接数
```php
$total = $upgrader->count();
```
关闭全部连接
```php
$upgrader->closeAll();
```
## 客户端 Client
可以连接任何 websocket v13 的服务器
```php
$cli = Mix\WebSocket\Client('ws://127.0.0.1:9502/websocket');
// 发送消息
$out = new \Swoole\WebSocket\Frame();
$out->data = 'xiaoming';
$cli->writeMessage($out);
// 接收消息
$in = $cli->readMessage();
var_dump($in->data);
$cli->close();
```

View File

@ -0,0 +1,113 @@
## Mix Worker Pool
基于 Swoole 的工作池,协程池
> go 版本https://github.com/mix-go/xwp
## 安装
```
composer require mix/worker-pool
```
## 单次调度
- 如果不想阻塞执行,可以使用 `$pool->start()` 启动
```php
$jobQueue = new Swoole\Coroutine\Channel(200);
$maxWorkers = 100;
$handler = function ($data) {
// do something
};
$pool = new Mix\WorkerPool\WorkerPool($jobQueue, $maxWorkers, $handler);
go(function () use ($jobQueue, $pool) {
// 投放任务
for ($i = 0; $i < 1000; $i++) {
$jobQueue->push($i);
}
// 停止
$pool->stop();
});
$pool->run(); // 阻塞等待
```
上面是采用闭包处理任务,也可以使用对象处理任务
```php
class FooHandler implements \Mix\WorkerPool\RunInterface
{
public function do($data): void
{
// do something
}
}
$pool = new Mix\WorkerPool\WorkerPool($jobQueue, $maxWorkers, new FooHandler());
```
## 常驻调度
> 适合处理 MQ 队列的异步消费
以 Redis 作为 MQ 为例:
```php
$maxWorkers = 20;
$maxQueue = 10;
$jobQueue = new Swoole\Coroutine\Channel($maxQueue);
$handler = function ($data) {
// do something
};
$pool = new Mix\WorkerPool\WorkerPool($jobQueue, $maxWorkers, $handler);
$quit = new Swoole\Coroutine\Channel();
foreach ([SIGHUP, SIGINT, SIGTERM] as $signal) {
Swoole\Process::signal($signal, function () use ($quit) {
$quit->push(true);
});
}
go(function () use ($jobQueue, $pool, $quit) {
// 投放任务
while (true) {
if (!$quit->isEmpty()) {
$pool->stop();
return;
}
try {
$data = $redis->brPop(['test'], 1);
} catch (\Throwable $ex) {
// print log
$pool->stop();
return;
}
if (!$data) {
continue;
}
$data = array_pop($data); // brPop命令最后一个键才是值
$jobQueue->push($data);
}
});
$pool->run(); // 阻塞等待
```
## 异常处理
闭包或者对象 `do` 方法中执行的代码,可能会抛出异常,必须要使用 `try/catch` 避免协程退出
```php
class FooHandler implements \Mix\WorkerPool\RunInterface
{
public function do($data): void
{
try {
// do something
} catch (\Throwable $ex){
// print log
}
}
}
```

View File

@ -0,0 +1,17 @@
## QQ 交流群
敲门暗号:`phper`
- OpenMix 技术交流 A 群: [284806582](https://shang.qq.com/wpa/qunwpa?idkey=b3a8618d3977cda4fed2363a666b081a31d89e3d31ab164497f53b72cf49968a)
- OpenMix 技术交流 B 群: [825122875](http://shang.qq.com/wpa/qunwpa?idkey=d2908b0c7095fc7ec63a2391fa4b39a8c5cb16952f6cfc3f2ce4c9726edeaf20)
## 知乎
- 作者: https://www.zhihu.com/people/onanying
- MixPHP 专栏: https://www.zhihu.com/column/mix-php
## 推荐阅读
- [MixPHP V3 开发流程体验 Swoole, Workerman, FPM, CLI-Server 多种运行模式介绍](https://zhuanlan.zhihu.com/p/398381870)
- [MixPHP V3 增加了 PHP-FPM、CLI-Server 的支持](https://zhuanlan.zhihu.com/p/394059925)
- [MixPHP V3 发布前的感想, 有哪些变化和特点](https://zhuanlan.zhihu.com/p/392558932)

View File

@ -0,0 +1,42 @@
# PHP Built-in CLI-Server
[CLI-Server](https://www.php.net/manual/zh/features.commandline.webserver.php) 是 PHP 内置的 Web 服务器,具有 `零扩展依赖` `热更新` `适合本机开发` 的特点。
## 入口文件
骨架路径 `public/index.php`
```php
<?php
require __DIR__ . '/../vendor/autoload.php';
/**
* PHP-FPM, cli-server 模式专用
*/
use App\Vega;
use Dotenv\Dotenv;
Dotenv::createUnsafeImmutable(__DIR__ . '/../', '.env')->load();
define("APP_DEBUG", env('APP_DEBUG'));
App\Error::register();
return Vega::new()->run();
```
## 启动服务
- API
```
php -S localhost:8000 public/index.php
```
- Web
支持静态文件处理
```
php -S localhost:8000 -t public
```

View File

@ -0,0 +1,53 @@
# PHP-FPM
PHP-FPM 部署具有 `热更新` `适合共享开发` `适合 admin 开发` 的特点,即便是在该传统模式下压测结果都接近 `Phalcon` 的性能。
## 入口文件
骨架路径 `public/index.php`
```php
<?php
require __DIR__ . '/../vendor/autoload.php';
/**
* PHP-FPM, cli-server 模式专用
*/
use App\Vega;
use Dotenv\Dotenv;
Dotenv::createUnsafeImmutable(__DIR__ . '/../', '.env')->load();
define("APP_DEBUG", env('APP_DEBUG'));
App\Error::register();
return Vega::new()->run();
```
## 部署
和 Laravel、ThinkPHP 部署方法完全一致,将 `public/index.php``nginx` 配置 `rewrite` 重写即可
```
server {
server_name www.domain.com;
listen 80;
root /data/project/public;
index index.html index.php;
location / {
if (!-e $request_filename) {
rewrite ^/(.*)$ /index.php/$1 last;
}
}
location ~ ^(.+\.php)(.*)$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_split_path_info ^(.+\.php)(.*)$;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
```

View File

@ -0,0 +1,69 @@
# Swoole Coroutine
[Swoole](https://www.swoole.com/) 驱动,该模式为单进程协程模型,非常适合编写 `WebSocket`、`gRPC` 服务。
## 入口文件
骨架路径 `bin/swooleco.php`
```php
#!/usr/bin/env php
<?php
ini_set('display_errors', 'on');
ini_set('display_startup_errors', 'on');
ini_set('error_reporting', E_ALL ^ E_NOTICE);
ini_set('memory_limit', '1G');
require __DIR__ . '/../vendor/autoload.php';
use App\Container\Logger;
use App\Vega;
use Dotenv\Dotenv;
Dotenv::createUnsafeImmutable(__DIR__ . '/../', '.env')->load();
define("APP_DEBUG", env('APP_DEBUG'));
App\Error::register();
Swoole\Coroutine\run(function () {
$vega = Vega::new();
$host = '0.0.0.0';
$port = 9502;
$server = new Swoole\Coroutine\Http\Server($host, $port, false, false);
$server->handle('/', $vega->handler());
App\Container\DB::enableCoroutine();
App\Container\RDS::enableCoroutine();
foreach ([SIGHUP, SIGINT, SIGTERM] as $signal) {
Swoole\Process::signal($signal, function () use ($server) {
Logger::instance()->info('Shutdown swoole coroutine server');
$server->shutdown();
});
}
echo <<<EOL
____
______ ___ _____ ___ _____ / /_ _____
/ __ `__ \/ /\ \/ /__ / __ \/ __ \/ __ \
/ / / / / / / /\ \/ _ / /_/ / / / / /_/ /
/_/ /_/ /_/_/ /_/\_\ / .___/_/ /_/ .___/
/_/ /_/
EOL;
printf("System Name: %s\n", strtolower(PHP_OS));
printf("PHP Version: %s\n", PHP_VERSION);
printf("Swoole Version: %s\n", swoole_version());
printf("Listen Addr: http://%s:%d\n", $host, $port);
Logger::instance()->info('Start swoole coroutine server');
$server->start();
});
```
## 启动服务
```
php bin/swooleco.php
```

View File

@ -0,0 +1,78 @@
# Swoole
[Swoole](https://www.swoole.com/) 驱动,该模式为多进程模型。
- 同步模式:`常驻内存` `兼容 composer 生态`
- 协程模式:`常驻内存` `协程性能强劲`
## 入口文件
骨架路径 `bin/swoole.php`**默认为协程模式**,可手动修改为同步模式
```php
#!/usr/bin/env php
<?php
ini_set('display_errors', 'on');
ini_set('display_startup_errors', 'on');
ini_set('error_reporting', E_ALL ^ E_NOTICE);
ini_set('memory_limit', '1G');
require __DIR__ . '/../vendor/autoload.php';
use App\Container\Logger;
use App\Vega;
use Dotenv\Dotenv;
Dotenv::createUnsafeImmutable(__DIR__ . '/../', '.env')->load();
define("APP_DEBUG", env('APP_DEBUG'));
App\Error::register();
/**
* 多进程默认开启了协程
* 关闭协程只需关闭 `enable_coroutine` 配置并注释数据库的 `::enableCoroutine()` 即可退化为多进程同步模式
*/
$vega = Vega::new();
$host = '0.0.0.0';
$port = 9501;
$http = new Swoole\Http\Server($host, $port);
$http->on('Request', $vega->handler());
$http->on('WorkerStart', function ($server, $workerId) {
// swoole 协程不支持 set_exception_handler 需要手动捕获异常
try {
App\Container\DB::enableCoroutine();
App\Container\RDS::enableCoroutine();
} catch (\Throwable $ex) {
App\Error::handle($ex);
}
});
$http->set([
'enable_coroutine' => true,
'worker_num' => 4,
]);
echo <<<EOL
____
______ ___ _____ ___ _____ / /_ _____
/ __ `__ \/ /\ \/ /__ / __ \/ __ \/ __ \
/ / / / / / / /\ \/ _ / /_/ / / / / /_/ /
/_/ /_/ /_/_/ /_/\_\ / .___/_/ /_/ .___/
/_/ /_/
EOL;
printf("System Name: %s\n", strtolower(PHP_OS));
printf("PHP Version: %s\n", PHP_VERSION);
printf("Swoole Version: %s\n", swoole_version());
printf("Listen Addr: http://%s:%d\n", $host, $port);
Logger::instance()->info('Start swoole server');
$http->start();
```
## 启动服务
```
php bin/swoole.php
```

View File

@ -0,0 +1,57 @@
# WorkerMan
[WorkerMan](https://www.workerman.net/) 驱动,该模式为多进程模型,具有 `常驻内存` `兼容 composer 生态` 的特点。
## 入口文件
骨架路径 `bin/workerman.php`
```php
#!/usr/bin/env php
<?php
ini_set('display_errors', 'on');
ini_set('display_startup_errors', 'on');
ini_set('error_reporting', E_ALL ^ E_NOTICE);
ini_set('memory_limit', '1G');
require __DIR__ . '/../vendor/autoload.php';
use App\Container\Logger;
use App\Vega;
use Dotenv\Dotenv;
Dotenv::createUnsafeImmutable(__DIR__ . '/../', '.env')->load();
define("APP_DEBUG", env('APP_DEBUG'));
App\Error::register();
$vega = Vega::new();
$addr = 'http://0.0.0.0:2345';
$http = new Workerman\Worker($addr);
$http->onMessage = $vega->handler();
$http->count = 4;
echo <<<EOL
____
______ ___ _____ ___ _____ / /_ _____
/ __ `__ \/ /\ \/ /__ / __ \/ __ \/ __ \
/ / / / / / / /\ \/ _ / /_/ / / / / /_/ /
/_/ /_/ /_/_/ /_/\_\ / .___/_/ /_/ .___/
/_/ /_/
EOL;
printf("System Name: %s\n", strtolower(PHP_OS));
printf("PHP Version: %s\n", PHP_VERSION);
printf("Workerman Version: %s\n", Workerman\Worker::VERSION);
printf("Listen Addr: %s\n", $addr);
Logger::instance()->info('Start workerman server');
Workerman\Worker::runAll();
```
## 启动服务
```
php bin/workerman.php start
```

View File

@ -1,3 +1,34 @@
* 前言
* 序言
* [项目介绍](zh-cn/README.md)
* [技术交流](zh-cn/online-chating.md)
* [性能压测](zh-cn/benchmarks.md)
* [项目介绍](zh-cn/README.md)
* 快速开始
* [编写 CLI 程序](zh-cn/write-cli.md)
* [编写 API 接口](zh-cn/write-api.md)
* [编写 Web 页面](zh-cn/write-web.md)
* [编写 WebSocket 服务](zh-cn/write-websocket.md)
* [编写 gRPC 接口](zh-cn/write-grpc.md)
* 服务器
* [PHP Built-in CLI-Server](zh-cn/server-cli-server.md)
* [PHP-FPM](zh-cn/server-php-fpm.md)
* [Swoole](zh-cn/server-swoole.md)
* [Swoole Coroutine](zh-cn/server-swoole-coroutine.md)
* [WorkerMan](zh-cn/server-workerman.md)
* 独立模块
* [mix/vega](zh-cn/mix-vega.md)
* [mix/database](zh-cn/mix-database.md)
* [mix/redis](zh-cn/mix-redis.md)
* [mix/redis-subscriber](zh-cn/mix-redis-subscriber.md)
* [mix/grpc](zh-cn/mix-grpc.md)
* [mix/websocket](zh-cn/mix-websocket.md)
* [mix/cli](zh-cn/mix-cli.md)
* [mix/worker-pool](zh-cn/mix-worker-pool.md)
* [mix/validator](zh-cn/mix-validator.md)
* [mix/event](zh-cn/mix-event.md)

220
docs/3.0/zh-cn/write-api.md Normal file
View File

@ -0,0 +1,220 @@
# 编写一个 API 接口
帮助你快速搭建 API 项目骨架,并指导你如何使用该骨架的细节,骨架默认开启了 SQL、Redis 日志,压测前请先关闭 `.env``APP_DEBUG`
## 安装
> 需要先安装 [Swoole](https://wiki.swoole.com/#/environment) 或者 [WorkerMan](http://doc.workerman.net/install/requirement.html)
```
composer create-project --prefer-dist mix/api-skeleton api
```
## 快速开始
启动 [cli-server](https://www.php.net/manual/zh/features.commandline.webserver.php) 开发服务 (零依赖)
```
composer run-script --timeout=0 cliserver:start
```
启动 Swoole 多进程服务
```
composer run-script --timeout=0 swoole:start
```
启动 Swoole 协程服务
```
composer run-script --timeout=0 swooleco:start
```
启动 WorkerMan 多进程服务
```
composer run-script --timeout=0 workerman:start
```
## 执行脚本
- `composer run-script` 命令中的 `--timeout=0` 参数是防止 composer [执行超时](https://getcomposer.org/doc/06-config.md#process-timeout)
- `composer.json` 定义了命令执行脚本,对应上面的执行命令
```json
"scripts": {
"cliserver:start": "php -S localhost:8000 public/index.php",
"swoole:start": "php bin/swoole.php",
"swooleco:start": "php bin/swooleco.php",
"workerman:start": "php bin/workerman.php start",
"cli:clearcache": "php bin/cli.php clearcache"
}
```
当然也可以直接下面这样启动,效果是一样的,但是 `scripts` 能帮你记录到底有哪些可用的命令同时在IDE中调试更加方便。
```
php bin/swoole.php start
```
## 部署
- CLI
线上部署启动时,修改 `shell/server.sh` 脚本中的绝对路径和参数
```
php=/usr/local/bin/php
file=/project/bin/swoole.php
cmd=start
numprocs=1
```
启动管理
```
sh shell/server.sh start
sh shell/server.sh stop
sh shell/server.sh restart
```
使用 `nginx` 或者 `SLB` 代理到服务器端口即可
```
server {
server_name www.domain.com;
listen 80;
root /data/project/public;
location / {
proxy_http_version 1.1;
proxy_set_header Connection "keep-alive";
proxy_set_header Host $http_host;
proxy_set_header Scheme $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
if (!-f $request_filename) {
proxy_pass http://127.0.0.1:9501;
}
}
}
```
- PHP-FPM
和 Laravel、ThinkPHP 部署方法完全一致,将 `public/index.php``nginx` 配置 `rewrite` 重写即可
```
server {
server_name www.domain.com;
listen 80;
root /data/project/public;
index index.html index.php;
location / {
if (!-e $request_filename) {
rewrite ^/(.*)$ /index.php/$1 last;
}
}
location ~ ^(.+\.php)(.*)$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_split_path_info ^(.+\.php)(.*)$;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
```
## 编写一个 API 接口
首先修改根目录 `.env` 文件的数据库信息
然后在 `routes/index.php` 定义一个新的路由
```php
$vega->handle('/users/{id}', [new Users(), 'index'])->methods('GET');
```
路由里使用了 `Users` 控制器,我们需要创建他
- 如何配置路由:[mix/vega](zh-cn/mix-vega.md)
- 如何调用数据库:[mix/database](zh-cn/mix-database.md)
```php
<?php
namespace App\Controller;
use App\Container\DB;
use Mix\Vega\Context;
class Users
{
/**
* @param Context $ctx
* @throws \Exception
*/
public function index(Context $ctx)
{
$row = DB::instance()->table('users')->where('id = ?', $ctx->param('id'))->first();
if (!$row) {
throw new \Exception('User not found');
}
$ctx->JSON(200, [
'code' => 0,
'message' => 'ok',
'data' => $row
]);
}
}
```
重新启动服务器后方可测试新开发的接口
> 实际开发中使用 PhpStorm 的 Run 功能,只需要点击一下重启按钮即可
```
// 查找进程 PID
ps -ef | grep swoole
// 通过 PID 停止进程
kill PID
// 重新启动进程
composer run-script swoole:start
// curl 测试
curl http://127.0.0.1:9501/users/1
```
## 使用容器中的对象
容器采用了一个简单的单例模式,你可以修改为更加适合自己的方式。
- 数据库:[mix/database](zh-cn/mix-database.md)
```
DB::instance()
```
- Redis[mix/redis](zh-cn/mix-redis.md)
```
RDS::instance()
```
- 日志:[monolog/monolog](https://seldaek.github.io/monolog/doc/01-usage.html)
```
Logger::instance()
```
- 配置:[hassankhan/config](https://github.com/hassankhan/config#getting-values)
```
Config::instance()
```

110
docs/3.0/zh-cn/write-cli.md Normal file
View File

@ -0,0 +1,110 @@
# 编写一个 CLI 程序
帮助你快速搭建 CLI 项目骨架,并指导你如何使用该骨架的细节,骨架默认开启了 SQL、Redis 日志,压测前请先关闭 `.env``APP_DEBUG`
## 安装
```
composer create-project --prefer-dist mix/cli-skeleton cli
```
## 快速开始
使用 `composer` 执行命令
```
composer run-script --timeout=0 cli:clearcache
```
## 执行脚本
- `composer run-script` 命令中的 `--timeout=0` 参数是防止 composer [执行超时](https://getcomposer.org/doc/06-config.md#process-timeout)
- `composer.json` 定义了命令执行脚本,对应上面的执行命令
```json
"scripts": {
"cli:clearcache": "php bin/cli.php clearcache"
}
```
当然也可以直接下面这样启动,效果是一样的,但是 `scripts` 能帮你记录到底有哪些可用的命令同时在IDE中调试更加方便。
```
php bin/cli.php clearcache
```
## 编写一个 CLI 程序
首先我们在 `bin/cli.php` 入口文件中增加一个命令
- 如何配置命令:[mix/cli](https://github.com/mix-php/cli#readme)
```php
Cli::setName('app')->setVersion('0.0.0-alpha');
$cmds = [
new Mix\Cli\Command([
'name' => 'clearcache',
'short' => 'Clear cache',
'options' => [
new Mix\Cli\Option([
'names' => ['k', 'key'],
'usage' => 'Key name'
]),
],
'run' => new App\Command\ClearCache(),
])
];
Cli::addCommand(...$cmds)->run();
```
查看命令帮助,检查配置是否正确
```
$ php bin/cli.php
Usage: bin/cli.php [OPTIONS] COMMAND [ARG...]
Commands:
clearcache Clear cache
Global Options:
-h, --help Print usage
-v, --version Print version information
Run 'bin/cli.php COMMAND --help' for more information on a command.
Developed with Mix PHP framework. (openmix.org/mix-php)
```
执行 `clearcache` 命令
```
$ php bin/cli.php clearcache
```
## 使用容器中的对象
容器采用了一个简单的单例模式,你可以修改为更加适合自己的方式。
- 数据库:[mix/database](zh-cn/mix-database.md)
```
DB::instance()
```
- Redis[mix/redis](zh-cn/mix-redis.md)
```
RDS::instance()
```
- 日志:[monolog/monolog](https://seldaek.github.io/monolog/doc/01-usage.html)
```
Logger::instance()
```
- 配置:[hassankhan/config](https://github.com/hassankhan/config#getting-values)
```
Config::instance()
```

View File

@ -0,0 +1,166 @@
# 编写一个 gRPC 接口
帮助你快速搭建 gRPC 项目骨架,并指导你如何使用该骨架的细节,骨架默认开启了 SQL、Redis 日志,压测前请先关闭 `.env``APP_DEBUG`
## 安装
> 需要先安装 [Swoole](https://wiki.swoole.com/#/environment)
- Swoole >= 4.4.4: https://wiki.swoole.com/#/environment
- 需要开启 `--enable-http2`
```
composer create-project --prefer-dist mix/grpc-skeleton grpc
```
## 快速开始
启动 Swoole 多进程服务
```
composer run-script --timeout=0 swoole:start
```
启动 Swoole 协程服务
```
composer run-script --timeout=0 swooleco:start
```
## 执行脚本
- `composer run-script` 命令中的 `--timeout=0` 参数是防止 composer [执行超时](https://getcomposer.org/doc/06-config.md#process-timeout)
- `composer.json` 定义了命令执行脚本,对应上面的执行命令
```json
"scripts": {
"swoole:start": "php bin/swoole.php",
"swooleco:start": "php bin/swooleco.php",
"cli:clearcache": "php bin/cli.php clearcache"
}
```
当然也可以直接下面这样启动,效果是一样的,但是 `scripts` 能帮你记录到底有哪些可用的命令同时在IDE中调试更加方便。
```
php bin/swoole.php start
```
## 部署
线上部署启动时,修改 `shell/server.sh` 脚本中的绝对路径和参数
```
php=/usr/local/bin/php
file=/project/bin/swoole.php
cmd=start
numprocs=1
```
启动管理
```
sh shell/server.sh start
sh shell/server.sh stop
sh shell/server.sh restart
```
gRPC 通常都是内部使用,使用内网 `SLB` 代理到服务器IP或者直接使用 IP:PORT 调用
## 编写一个 gRPC 接口
首先修改根目录 `.env` 文件的数据库信息
然后在 `proto` 目录创建 `greeter.proto` 文件,并根据 [使用说明](https://github.com/mix-php/grpc#%E9%80%9A%E8%BF%87-proto-%E7%94%9F%E6%88%90-php-%E4%BB%A3%E7%A0%81) 将 .proto 文件生成为 PHP 代码
```
protoc --php_out=. --mix_out=. greeter.proto
```
然后创建一个新的服务 `src/Service/Say.php`
- `Say` 类实现了代码生成器生成的 `Php\Micro\Grpc\Greeter\SayInterface` 接口
```php
<?php
namespace App\Service;
use Mix\Grpc\Context;
use Php\Micro\Grpc\Greeter\Request;
use Php\Micro\Grpc\Greeter\Response;
/**
* Class Say
* @package App\Service
*/
class Say implements \Php\Micro\Grpc\Greeter\SayInterface
{
/**
* @param Context $context
* @param Request $request
* @return Response
*/
public function Hello(Context $context, Request $request): Response
{
$response = new Response();
$response->setMsg(sprintf('hello, %s', $request->getName()));
return $response;
}
}
```
`src/Grpc.php` 中将服务注册到服务器
```php
$server->register(Say::class);
```
重新启动服务器后方可测试新开发的接口
> 实际开发中使用 PhpStorm 的 Run 功能,只需要点击一下重启按钮即可
```
// 查找进程 PID
ps -ef | grep swoole
// 通过 PID 停止进程
kill PID
// 重新启动进程
composer run-script swoole:start
```
## 如何使用 gRPC 客户端
- [mix/grpc#客户端调用一个 gRPC 服务](https://gitee.com/mix-php/mix/tree/master/src/grpc#%E5%AE%A2%E6%88%B7%E7%AB%AF%E8%B0%83%E7%94%A8%E4%B8%80%E4%B8%AA-grpc-%E6%9C%8D%E5%8A%A1)
## 使用容器中的对象
容器采用了一个简单的单例模式,你可以修改为更加适合自己的方式。
- 数据库:[mix/database](zh-cn/mix-database.md)
```
DB::instance()
```
- Redis[mix/redis](zh-cn/mix-redis.md)
```
RDS::instance()
```
- 日志:[monolog/monolog](https://seldaek.github.io/monolog/doc/01-usage.html)
```
Logger::instance()
```
- 配置:[hassankhan/config](https://github.com/hassankhan/config#getting-values)
```
Config::instance()
```

221
docs/3.0/zh-cn/write-web.md Normal file
View File

@ -0,0 +1,221 @@
# 编写一个 Web 页面
帮助你快速搭建 Web 项目骨架,并指导你如何使用该骨架的细节,骨架默认开启了 SQL、Redis 日志,压测前请先关闭 `.env``APP_DEBUG`
## 安装
> 需要先安装 [Swoole](https://wiki.swoole.com/#/environment) 或者 [WorkerMan](http://doc.workerman.net/install/requirement.html)
```
composer create-project --prefer-dist mix/web-skeleton web
```
## 快速开始
启动 [cli-server](https://www.php.net/manual/zh/features.commandline.webserver.php) 开发服务 (零依赖)
```
composer run-script --timeout=0 cliserver:start
```
启动 Swoole 多进程服务
```
composer run-script --timeout=0 swoole:start
```
启动 Swoole 协程服务
```
composer run-script --timeout=0 swooleco:start
```
启动 WorkerMan 多进程服务
```
composer run-script --timeout=0 workerman:start
```
## 执行脚本
- `composer run-script` 命令中的 `--timeout=0` 参数是防止 composer [执行超时](https://getcomposer.org/doc/06-config.md#process-timeout)
- `composer.json` 定义了命令执行脚本,对应上面的执行命令
```json
"scripts": {
"cliserver:start": "php -S localhost:8000 -t public",
"swoole:start": "php bin/swoole.php",
"swooleco:start": "php bin/swooleco.php",
"workerman:start": "php bin/workerman.php start",
"cli:clearcache": "php bin/cli.php clearcache"
}
```
当然也可以直接下面这样启动,效果是一样的,但是 `scripts` 能帮你记录到底有哪些可用的命令同时在IDE中调试更加方便。
```
php bin/swoole.php start
```
## 部署
- CLI
线上部署启动时,修改 `shell/server.sh` 脚本中的绝对路径和参数
```
php=/usr/local/bin/php
file=/project/bin/swoole.php
cmd=start
numprocs=1
```
启动管理
```
sh shell/server.sh start
sh shell/server.sh stop
sh shell/server.sh restart
```
使用 `nginx` 或者 `SLB` 代理到服务器端口即可
```
server {
server_name www.domain.com;
listen 80;
root /data/project/public;
location / {
proxy_http_version 1.1;
proxy_set_header Connection "keep-alive";
proxy_set_header Host $http_host;
proxy_set_header Scheme $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
if (!-f $request_filename) {
proxy_pass http://127.0.0.1:9501;
}
}
}
```
- PHP-FPM
和 Laravel、ThinkPHP 部署方法完全一致,将 `public/index.php``nginx` 配置 `rewrite` 重写即可
```
server {
server_name www.domain.com;
listen 80;
root /data/project/public;
index index.html index.php;
location / {
if (!-e $request_filename) {
rewrite ^/(.*)$ /index.php/$1 last;
}
}
location ~ ^(.+\.php)(.*)$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_split_path_info ^(.+\.php)(.*)$;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
```
## 编写一个 Web 页面
首先修改根目录 `.env` 文件的数据库信息
然后在 `routes/index.php` 定义一个新的路由
```php
$vega->handle('/', [new Hello(), 'index'])->methods('GET');
```
路由里使用了 `Hello` 控制器,我们需要创建他
- 如何配置路由:[mix/vega](zh-cn/mix-vega.md)
```php
<?php
namespace App\Controller;
use Mix\Vega\Context;
class Hello
{
/**
* @param Context $ctx
*/
public function index(Context $ctx)
{
$ctx->HTML(200, 'index', [
'title' => 'Hello, World!'
]);
}
}
```
`views` 目录创建 `index.php` 视图文件
```php
<html>
<h1>
<?= $title ?>
</h1>
</html>
```
重新启动服务器后方可测试新开发的接口
> 实际开发中使用 PhpStorm 的 Run 功能,只需要点击一下重启按钮即可
```
// 查找进程 PID
ps -ef | grep swoole
// 通过 PID 停止进程
kill PID
// 重新启动进程
composer run-script swoole:start
// curl 测试
curl http://127.0.0.1:9501/
```
## 使用容器中的对象
容器采用了一个简单的单例模式,你可以修改为更加适合自己的方式。
- 数据库:[mix/database](zh-cn/mix-database.md)
```
DB::instance()
```
- Redis[mix/redis](zh-cn/mix-redis.md)
```
RDS::instance()
```
- 日志:[monolog/monolog](https://seldaek.github.io/monolog/doc/01-usage.html)
```
Logger::instance()
```
- 配置:[hassankhan/config](https://github.com/hassankhan/config#getting-values)
```
Config::instance()
```

View File

@ -0,0 +1,239 @@
# 编写一个 WebSocket 服务
帮助你快速搭建 WebSocket 项目骨架,并指导你如何使用该骨架的细节,骨架默认开启了 SQL、Redis 日志,压测前请先关闭 `.env``APP_DEBUG`
## 安装
> 需要先安装 [Swoole](https://wiki.swoole.com/#/environment)
- Swoole >= 4.4.15: https://wiki.swoole.com/#/environment
```
composer create-project --prefer-dist mix/websocket-skeleton websocket
```
## 快速开始
启动 Swoole 协程服务
```
composer run-script --timeout=0 swooleco:start
```
## 执行脚本
- `composer run-script` 命令中的 `--timeout=0` 参数是防止 composer [执行超时](https://getcomposer.org/doc/06-config.md#process-timeout)
- `composer.json` 定义了命令执行脚本,对应上面的执行命令
```json
"scripts": {
"swooleco:start": "php bin/swooleco.php",
"cli:clearcache": "php bin/cli.php clearcache"
}
```
当然也可以直接下面这样启动,效果是一样的,但是 `scripts` 能帮你记录到底有哪些可用的命令同时在IDE中调试更加方便。
```
php bin/swooleco.php start
```
## 部署
线上部署启动时,修改 `shell/server.sh` 脚本中的绝对路径和参数
```
php=/usr/local/bin/php
file=/project/bin/swooleco.php
cmd=start
numprocs=1
```
启动管理
```
sh shell/server.sh start
sh shell/server.sh stop
sh shell/server.sh restart
```
使用 `nginx` 或者 `SLB` 代理到服务器端口即可
```
location /websocket {
proxy_pass http://127.0.0.1:9502;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
}
```
## 编写一个 WebSocket 服务
首先修改根目录 `.env` 文件的数据库信息
然后在 `routes/index.php` 定义一个新的路由
```php
$vega->handle('/websocket', [new WebSocket(), 'index'])->methods('GET');
```
路由里使用了 `WebSocket` 控制器,我们需要创建他
- 如何配置路由:[mix/vega](zh-cn/mix-vega.md)
- 如何使用 WebSocket 升级器:[mix/websocket](https://github.com/mix-php/websocket#readme)
```php
<?php
namespace App\Controller;
use App\Container\Upgrader;
use App\Service\Session;
use Mix\Vega\Context;
class WebSocket
{
/**
* @param Context $ctx
*/
public function index(Context $ctx)
{
$conn = Upgrader::instance()->upgrade($ctx->request, $ctx->response);
$session = new Session($conn);
$session->start();
}
}
```
控制器中使用了一个 `Session` 类来处理连接事务
```php
<?php
namespace App\Service;
use App\Handler\Hello;
use Mix\WebSocket\Connection;
use Swoole\Coroutine\Channel;
class Session
{
/**
* @var Connection
*/
protected $conn;
/**
* @var Channel
*/
protected $writeChan;
/**
* Session constructor.
* @param Connection $conn
*/
public function __construct(Connection $conn)
{
$this->conn = $conn;
$this->writeChan = new Channel(10);
}
/**
* @param string $data
*/
public function send(string $data): void
{
$this->writeChan->push($data);
}
public function start(): void
{
// 接收消息
go(function () {
while (true) {
$frame = $this->conn->recv();
$message = $frame->data;
(new Hello($this))->index($message);
}
});
// 发送消息
go(function () {
while (true) {
$data = $this->writeChan->pop();
if (!$data) {
return;
}
$frame = new \Swoole\WebSocket\Frame();
$frame->data = $data;
$frame->opcode = WEBSOCKET_OPCODE_TEXT; // or WEBSOCKET_OPCODE_BINARY
$this->conn->send($frame);
}
});
}
}
```
在接收消息处,使用了 `src/Handler/Hello.php` 处理器对当前发送的消息做逻辑处理,我们只需根据自己的需求增加新的处理器来处理不同消息即可。
```
(new Hello($this))->index($message);
```
重新启动服务器后方可测试新开发的接口
> 实际开发中使用 PhpStorm 的 Run 功能,只需要点击一下重启按钮即可
```
// 查找进程 PID
ps -ef | grep swoole
// 通过 PID 停止进程
kill PID
// 重新启动进程
composer run-script swoole:start
```
使用测试工具测试
- [WEBSOCKET 在线测试工具](http://www.easyswoole.com/wstool.html)
## 如何使用 WebSocket 客户端
- [mix-php/websocket#客户端-client](https://github.com/mix-php/websocket#%E5%AE%A2%E6%88%B7%E7%AB%AF-client)
## 使用容器中的对象
容器采用了一个简单的单例模式,你可以修改为更加适合自己的方式。
- 数据库:[mix/database](zh-cn/mix-database.md)
```
DB::instance()
```
- Redis[mix/redis](zh-cn/mix-redis.md)
```
RDS::instance()
```
- 日志:[monolog/monolog](https://seldaek.github.io/monolog/doc/01-usage.html)
```
Logger::instance()
```
- 配置:[hassankhan/config](https://github.com/hassankhan/config#getting-values)
```
Config::instance()
```

View File

@ -3,9 +3,28 @@
<head>
<meta charset="UTF-8">
<title>MixPHP 开发文档</title>
<style>
body {
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
color: #34495e;
font-family: Source Sans Pro,Helvetica Neue,Arial,sans-serif;
font-size: 15px;
letter-spacing: 0;
margin: 20px;
overflow-x: hidden;
}
h1 {
margin: 0 auto 1rem;
font-size: 1.5rem;
font-weight: 300;
color: inherit;
text-decoration: none;
}
</style>
</head>
<body>
MixPHP 开发文档
<h1>MixPHP 开发文档</h1>
<ul>
<li><a href="3.0">V3.0</a></li>
<li><a href="https://www.kancloud.cn/onanying/mixphp2-2/content">V2.2</a></li>