Merge branch 'master' into 2.1-merge

This commit is contained in:
李铭昕 2020-12-14 19:25:02 +08:00
commit a8ea182146
67 changed files with 1602 additions and 75 deletions

View File

@ -1,8 +1,47 @@
# v2.0.22 - TBD
# v2.0.24 - TBD
# v2.0.23 - 2020-12-14
## Added
- [#2872](https://github.com/hyperf/hyperf/pull/2872) Added `hyperf/phar` component.
## Fixed
- [#2952](https://github.com/hyperf/hyperf/pull/2952) Fixed bug that nacos config center does not works in coroutine server.
## Changed
- [#2934](https://github.com/hyperf/hyperf/pull/2934) Changed config file `scout.php` which search engine index is used as the model index name by default.
- [#2958](https://github.com/hyperf/hyperf/pull/2958) Added NoneEngine as the default engine of view config.
## Optimized
- [#2951](https://github.com/hyperf/hyperf/pull/2951) Optimized code for model-cache, which will delete model cache only once, when using it in transaction.
- [#2953](https://github.com/hyperf/hyperf/pull/2953) Hide `Swoole\ExitException` trace message in command.
- [#2963](https://github.com/hyperf/hyperf/pull/2963) Removed `onStart` event from server default callbacks when the mode is `SWOOLE_BASE`.
# v2.0.22 - 2020-12-07
## Added
- [#2896](https://github.com/hyperf/hyperf/pull/2896) Support to define autoloaded view component classes and anonymous components.
- [#2921](https://github.com/hyperf/hyperf/pull/2921) Added method `count()` for `Parallel`.
## Fixed
- [#2913](https://github.com/hyperf/hyperf/pull/2913) Fixed memory leak when using `with()` for ORM.
- [#2915](https://github.com/hyperf/hyperf/pull/2915) Fixed bug that worker will be stoped when `onMessage` or `onClose` failed in websocket server.
- [#2927](https://github.com/hyperf/hyperf/pull/2927) Fixed validation rule `alpha_dash` does not support `int`.
## Changed
- [#2918](https://github.com/hyperf/hyperf/pull/2918) Don't allow to open `server.settings.daemonize` configuration when using `hyperf/watcher`.
- [#2930](https://github.com/hyperf/hyperf/pull/2930) Upgrade the minimum version of `php-amqplib` to `v2.9.2`.
## Optimized
- [#2931](https://github.com/hyperf/hyperf/pull/2931) Pass controller instance as first argument to method_exists function not the class namespace string.
# v2.0.21 - 2020-11-30

View File

@ -63,7 +63,7 @@
"nikic/php-parser": "^4.1",
"overtrue/flysystem-cos": "^2.0|^3.0",
"overtrue/flysystem-qiniu": "^1.0",
"php-amqplib/php-amqplib": "^2.7",
"php-amqplib/php-amqplib": "^2.9.2",
"php-di/php-di": "^6.0",
"phpspec/prophecy-phpunit": "^2.0",
"phpstan/phpstan": "^0.12",
@ -203,6 +203,7 @@
"Hyperf\\Nats\\": "src/nats/src/",
"Hyperf\\Nsq\\": "src/nsq/src/",
"Hyperf\\Paginator\\": "src/paginator/src/",
"Hyperf\\Phar\\": "src/phar/src/",
"Hyperf\\Pool\\": "src/pool/src/",
"Hyperf\\Process\\": "src/process/src/",
"Hyperf\\Protocol\\": "src/protocol/src/",
@ -281,6 +282,7 @@
"HyperfTest\\Nats\\": "src/nats/tests/",
"HyperfTest\\Nsq\\": "src/nsq/tests/",
"HyperfTest\\Paginator\\": "src/paginator/tests/",
"HyperfTest\\Phar\\": "src/phar/tests/",
"HyperfTest\\Pool\\": "src/pool/tests/",
"HyperfTest\\Process\\": "src/process/tests/",
"HyperfTest\\Protocol\\": "src/protocol/tests/",
@ -360,6 +362,7 @@
"Hyperf\\Nats\\ConfigProvider",
"Hyperf\\Nsq\\ConfigProvider",
"Hyperf\\Paginator\\ConfigProvider",
"Hyperf\\Phar\\ConfigProvider",
"Hyperf\\Pool\\ConfigProvider",
"Hyperf\\Process\\ConfigProvider",
"Hyperf\\Protocol\\ConfigProvider",

View File

@ -255,7 +255,7 @@ use Hyperf\HttpServer\Annotation\AutoController;
/**
* @AutoController
*/
class QueueController extends Controller
class QueueController extends AbstractController
{
/**
* @Inject
@ -327,7 +327,7 @@ use Hyperf\HttpServer\Annotation\AutoController;
/**
* @AutoController
*/
class QueueController extends Controller
class QueueController extends AbstractController
{
/**
* @Inject

View File

@ -1,5 +1,48 @@
# 版本更新记录
# v2.0.23 - 2020-12-14
## 新增
- [#2872](https://github.com/hyperf/hyperf/pull/2872) 新增 `hyperf/phar` 组件,用于将 `Hyperf` 项目打包成 `phar`
## 修复
- [#2952](https://github.com/hyperf/hyperf/pull/2952) 修复 `Nacos` 配置中心,在协程风格服务中无法正常使用的问题。
## 变更
- [#2934](https://github.com/hyperf/hyperf/pull/2934) 变更配置文件 `scout.php`,默认使用 `Elasticsearch` 索引作为模型索引。
- [#2958](https://github.com/hyperf/hyperf/pull/2958) 变更 `view` 组件默认的渲染引擎为 `NoneEngine`
## 优化
- [#2951](https://github.com/hyperf/hyperf/pull/2951) 优化 `model-cache` 组件,使其执行完多次事务后,只会删除一次缓存。
- [#2953](https://github.com/hyperf/hyperf/pull/2953) 隐藏命令行因执行 `exit` 导致的异常 `Swoole\ExitException`
- [#2963](https://github.com/hyperf/hyperf/pull/2963) 当异步风格服务使用 `SWOOLE_BASE` 时,会从默认的事件回调中移除 `onStart` 事件。
# v2.0.22 - 2020-12-07
## 新增
- [#2896](https://github.com/hyperf/hyperf/pull/2896) 允许 `view-engine` 组件配置自定义加载类组件和匿名组件。
- [#2921](https://github.com/hyperf/hyperf/pull/2921) 为 `Parallel` 增加 `count()` 方法,返回同时执行的个数。
## 修复
- [#2913](https://github.com/hyperf/hyperf/pull/2913) 修复使用 `ORM` 中的 `with` 预加载逻辑时,会因循环依赖导致内存泄露的问题。
- [#2915](https://github.com/hyperf/hyperf/pull/2915) 修复 `WebSocket` 工作进程会因 `onMessage` or `onClose` 回调失败,导致进程退出的问题。
- [#2927](https://github.com/hyperf/hyperf/pull/2927) 修复验证器规则 `alpha_dash` 不支持 `int` 的问题。
## 变更
- [#2918](https://github.com/hyperf/hyperf/pull/2918) 当使用 `watcher` 组件时,不可以开启 `daemonize`
- [#2930](https://github.com/hyperf/hyperf/pull/2930) 更新 `php-amqplib` 组件最低版本由 `v2.7``v2.9.2`
## 优化
- [#2931](https://github.com/hyperf/hyperf/pull/2931) 判断控制器方法是否存在时,使用实际从容器中得到的对象,而非命名空间。
# v2.0.21 - 2020-11-30
## 新增

View File

@ -111,7 +111,7 @@ namespace App\Controller;
use App\Constants\ErrorCode;
use App\Exception\BusinessException;
class IndexController extends Controller
class IndexController extends AbstractController
{
public function index()
{

View File

@ -183,7 +183,7 @@ class UserCollection extends ResourceCollection
```
你可以在控制器中返回已定义的资源集合:
你可以在控制器中返回已定义的资源集合:
```php
<?php
@ -891,7 +891,7 @@ class UserCollection extends ResourceCollection
#### 构造资源时添加元数据
你还可以在控制器中构造资源实例时添加顶层数据。所有资源都可以使用 `additional` 方法来接受应该被添加到资源响应中的数据数组:
你还可以在控制器中构造资源实例时添加顶层数据。所有资源都可以使用 `additional` 方法来接受应该被添加到资源响应中的数据数组:
```php
<?php

View File

@ -95,7 +95,7 @@ namespace App\Controller;
use App\Exception\FooException;
class IndexController extends Controller
class IndexController extends AbstractController
{
public function index()
{

View File

@ -55,7 +55,7 @@ class CalculatorService implements CalculatorServiceInterface
`@RpcService` 共有 `4` 个参数:
`name` 属性为定义该服务的名称这里定义一个全局唯一的名字即可Hyperf 会根据该属性生成对应的 ID 注册到服务中心去;
`protocol` 属性为定义该服务暴露的协议,目前仅支持 `jsonrpc` 和 `jsonrpc-http`,分别对应于 TCP 协议和 HTTP 协议下的两种协议,默认值为 `jsonrpc-http`,这里的值对应在 `Hyperf\Rpc\ProtocolManager` 里面注册的协议的 `key`这两个本质上都是 JSON RPC 协议,区别在于数据格式化、数据打包、数据传输器等不同。
`protocol` 属性为定义该服务暴露的协议,目前仅支持 `jsonrpc-http`, `jsonrpc`, `jsonrpc-tcp-length-check` ,分别对应于 HTTP 协议和 TCP 协议下的两种协议,默认值为 `jsonrpc-http`,这里的值对应在 `Hyperf\Rpc\ProtocolManager` 里面注册的协议的 `key`它们本质上都是 JSON RPC 协议,区别在于数据格式化、数据打包、数据传输器等不同。
`server` 属性为绑定该服务类发布所要承载的 `Server`,默认值为 `jsonrpc-http`,该属性对应 `config/autoload/server.php` 文件内 `servers` 下所对应的 `name`,这里也就意味着我们需要定义一个对应的 `Server`,我们下一章节具体阐述这里应该怎样去处理;
`publishTo` 属性为定义该服务所要发布的服务中心,目前仅支持 `consul` 或为空,为空时代表不发布该服务到服务中心去,但也就意味着您需要手动处理服务发现的问题,当值为 `consul` 时需要对应配置好 [hyperf/consul](zh-cn/consul.md) 组件的相关配置,要使用此功能需安装 [hyperf/service-governance](https://github.com/hyperf/service-governance) 组件,具体可参考 [服务注册](zh-cn/service-register.md) 章节;

View File

@ -59,7 +59,7 @@ use Hyperf\Nats\Driver\DriverInterface;
/**
* @AutoController(prefix="nats")
*/
class NatsController extends Controller
class NatsController extends AbstractController
{
/**
* @Inject
@ -96,7 +96,7 @@ use Hyperf\Nats\Message;
/**
* @AutoController(prefix="nats")
*/
class NatsController extends Controller
class NatsController extends AbstractController
{
/**
* @Inject
@ -135,7 +135,7 @@ use Hyperf\Nats\Message;
/**
* @AutoController(prefix="nats")
*/
class NatsController extends Controller
class NatsController extends AbstractController
{
/**
* @Inject

View File

@ -8,7 +8,9 @@ Hyperf 使用 [nikic/fast-route](https://github.com/nikic/FastRoute) 作为默
不仅如此,框架还提供了极其强大和方便灵活的 `注解路由` 功能,关于路由的详情文档请查阅 [路由](zh-cn/router.md) 章节
### 通过配置文件定义路由
路由的文件位于 [hyperf-skeleton](https://github.com/hyperf/hyperf-skeleton) 项目的 `config/routes.php` ,下面是一些常用的用法示例。
```php
<?php
use Hyperf\HttpServer\Router\Router;
@ -36,10 +38,19 @@ Router::addRoute(['GET', 'POST', 'HEAD'], '/multi', [\App\Controller\IndexContro
`Hyperf` 提供了极其强大和方便灵活的 [注解](zh-cn/annotation.md) 功能在路由的定义上也毫无疑问地提供了注解定义的方式Hyperf 提供了 `@Controller``@AutoController` 两种注解来定义一个 `Controller`,此处仅做简单的说明,更多细节请查阅 [路由](zh-cn/router.md) 章节。
### 通过 `@AutoController` 注解定义路由
`@AutoController` 为绝大多数简单的访问场景提供路由绑定支持,使用 `@AutoController` 时则 Hyperf 会自动解析所在类的所有 `public` 方法并提供 `GET``POST` 两种请求方式。
> 使用 `@AutoController` 注解时需 `use Hyperf\HttpServer\Annotation\AutoController;` 命名空间;
驼峰命名的控制器,会自动转化为蛇形路由,以下为控制器与实际路由的对应关系示例:
| 控制器 | 注解 | 访问路由 |
| :--------------: | :-----------------------------: | :------------: |
| MyDataController | @AutoController() | /my_data/index |
| MydataController | @AutoController() | /mydata/index |
| MyDataController | @AutoController(prefix="/data") | /data/index |
```php
<?php
declare(strict_types=1);
@ -318,22 +329,22 @@ class IndexController
除上述提到的 `SwooleEvent::ON_REQUEST` 事件,框架还支持其他事件,所有事件名如下。
| 事件名 | 备注 |
|:-----------------------------:|:-----------------------------------:|
| SwooleEvent::ON_REQUEST | |
| SwooleEvent::ON_START | 该事件在 `SWOOLE_BASE` 模式下无效 |
| SwooleEvent::ON_WORKER_START | |
| SwooleEvent::ON_WORKER_EXIT | |
| SwooleEvent::ON_PIPE_MESSAGE | |
| SwooleEvent::ON_RECEIVE | |
| SwooleEvent::ON_CONNECT | |
| SwooleEvent::ON_HAND_SHAKE | |
| SwooleEvent::ON_OPEN | |
| SwooleEvent::ON_MESSAGE | |
| SwooleEvent::ON_CLOSE | |
| SwooleEvent::ON_TASK | |
| SwooleEvent::ON_FINISH | |
| SwooleEvent::ON_SHUTDOWN | |
| SwooleEvent::ON_PACKET | |
| SwooleEvent::ON_MANAGER_START | |
| SwooleEvent::ON_MANAGER_STOP | |
| 事件名 | 备注 |
| :---------------------------: | :-------------------------------: |
| SwooleEvent::ON_REQUEST | |
| SwooleEvent::ON_START | 该事件在 `SWOOLE_BASE` 模式下无效 |
| SwooleEvent::ON_WORKER_START | |
| SwooleEvent::ON_WORKER_EXIT | |
| SwooleEvent::ON_PIPE_MESSAGE | |
| SwooleEvent::ON_RECEIVE | |
| SwooleEvent::ON_CONNECT | |
| SwooleEvent::ON_HAND_SHAKE | |
| SwooleEvent::ON_OPEN | |
| SwooleEvent::ON_MESSAGE | |
| SwooleEvent::ON_CLOSE | |
| SwooleEvent::ON_TASK | |
| SwooleEvent::ON_FINISH | |
| SwooleEvent::ON_SHUTDOWN | |
| SwooleEvent::ON_PACKET | |
| SwooleEvent::ON_MANAGER_START | |
| SwooleEvent::ON_MANAGER_STOP | |

View File

@ -140,4 +140,18 @@ Fatal error: Uncaught PhpParser\Error: Syntax error, unexpected T_STRING on line
可以执行脚本 `composer analyse`,对项目进行静态检测,便可以找到出现问题的代码段。
此问题通常是由于 [zircote/swagger](https://github.com/zircote/swagger-php) 的 3.0.5 版本更新导致, 详情请见 [#834](https://github.com/zircote/swagger-php/issues/834) 。
如果安装了 [hyperf/swagger](https://github.com/hyperf/swagger) 建议将 [zircote/swagger](https://github.com/zircote/swagger-php) 的版本锁定在 3.0.4
如果安装了 [hyperf/swagger](https://github.com/hyperf/swagger) 建议将 [zircote/swagger](https://github.com/zircote/swagger-php) 的版本锁定在 3.0.4
## 内存限制太小导致项目无法运行
PHP 默认的 `memory_limit` 只有 `128M`,因为 `Hyperf` 使用了 `BetterReflection`,不使用扫描缓存时,会消耗大量内存,所以可能会出现内存不够的情况。
我们可以使用 `php -dmemory_limit=-1 bin/hyperf.php start` 运行, 或者修改 `php.ini` 配置文件
```
# 查看 php.ini 配置文件位置
php --ini
# 修改 memory_limit 配置
memory_limit=-1
```

View File

@ -90,6 +90,14 @@ Router::addGroup('/user/',function (){
> 使用 `@AutoController` 注解时需 `use Hyperf\HttpServer\Annotation\AutoController;` 命名空间;
驼峰命名的控制器,会自动转化为蛇形路由,以下为控制器与实际路由的对应关系示例:
| 控制器 | 注解 | 访问路由 |
| :--------------: | :-----------------------------: | :------------: |
| MyDataController | @AutoController() | /my_data/index |
| MydataController | @AutoController() | /mydata/index |
| MyDataController | @AutoController(prefix="/data") | /data/index |
```php
<?php
declare(strict_types=1);

View File

@ -39,6 +39,44 @@ class Post extends Model
}
```
## 配置
### 配置文件
生成配置文件
```
php bin/hyperf.php vendor:publish hyperf/scout
```
配置文件
```php
<?php
declare(strict_types=1);
return [
'default' => env('SCOUT_ENGINE', 'elasticsearch'),
'chunk' => [
'searchable' => 500,
'unsearchable' => 500,
],
'prefix' => env('SCOUT_PREFIX', ''),
'soft_delete' => false,
'concurrency' => 100,
'engine' => [
'elasticsearch' => [
'driver' => Hyperf\Scout\Provider\ElasticsearchProvider::class,
// 如果 index 设置为 null则每个模型会对应一个索引反之每个模型对应一个类型
'index' => null,
'hosts' => [
env('ELASTICSEARCH_HOST', 'http://127.0.0.1:9200'),
],
],
],
];
```
### 配置模型索引
每个模型与给定的搜索「索引」同步,这个「索引」包含该模型的所有可搜索记录。换句话说,你可以把每一个「索引」设想为一张 MySQL 数据表。默认情况下,每个模型都会被持久化到与模型的「表」名(通常是模型名称的复数形式)相匹配的索引。你也可以通过覆盖模型上的 `searchableAs` 方法来自定义模型的索引:
@ -66,6 +104,7 @@ class Post extends Model
}
<a name="configuring-searchable-data"></a>
### 配置可搜索的数据
默认情况下,「索引」会从模型的 `toArray` 方法中读取数据来做持久化。如果要自定义同步到搜索索引的数据,可以覆盖模型上的 `toSearchableArray` 方法:

View File

@ -381,15 +381,15 @@ if ($errors->has('foo')) {
##### alpha
验证字段必须是字母。
验证字段必须是字母(包含中文)
##### alpha_dash
验证字段可以包含字母和数字,以及破折号和下划线。
验证字段可以包含字母(包含中文)和数字,以及破折号和下划线。
##### alpha_num
验证字段必须是字母或数字。
验证字段必须是字母(包含中文)或数字。
##### array

View File

@ -2,13 +2,13 @@
> 基于 laravel blade 模板引擎改写, 支持原始 blade 模板引擎的语法.
```
```bash
composer require hyperf/view-engine
```
## 生成配置
```
```bash
php bin/hyperf.php vendor:publish hyperf/view-engine
```
@ -109,7 +109,7 @@ return [
@yield('content', 'Hyperf')
```
`Blade` 视图可以用 `Hyperf\\ViewEngine\\view` 辅助函数返回:
`Blade` 视图可以用 `Hyperf\ViewEngine\view` 辅助函数返回:
```php
<?php
@ -124,7 +124,7 @@ use function Hyperf\ViewEngine\view;
/**
* @AutoController(prefix="view")
*/
class ViewController extends Controller
class ViewController extends AbstractController
{
public function child()
{
@ -792,7 +792,7 @@ class ConfigProvider
#### 组件自动加载
默认情况下,`App\View\Component\\` 及 `components.` 下的组件会自动注册。你也可以通过配置文件修改这个配置:
默认情况下,`App\View\Component\` 及 `components.` 下的组件会自动注册。你也可以通过配置文件修改这个配置:
> config/autoload/view.php

View File

@ -10,7 +10,13 @@ composer require hyperf/view
## 配置
View 组件的配置文件位于 `config/autoload/view.php`,若配置文件不存在可自行创建,以下为相关配置的说明:
View 组件的配置文件位于 `config/autoload/view.php`,若配置文件不存在可执行如下命令生成配置文件
```bash
php bin/hyperf.php vendor:publish hyperf/view
```
以下为相关配置的说明:
| 配置 | 类型 | 默认值 | 备注 |
|:-----------------:|:------:|:-------------------------------------:|:----------------:|

View File

@ -48,3 +48,4 @@ parameters:
- '#InfluxDB\\Point constructor expects float\|null, string given#'
- '#Call to an undefined method Hyperf\\Database\\Model\\Builder::unsearchable#'
- '#trackerHookMalloc not found#'
- '#method Phar\:\:buildFromIterator\(\) expects Iterator, Traversable given#'

View File

@ -40,6 +40,7 @@
<directory suffix="Test.php">./src/model-cache/tests</directory>
<directory suffix="Test.php">./src/nsq/tests</directory>
<directory suffix="Test.php">./src/paginator/tests</directory>
<directory suffix="Test.php">./src/phar/tests</directory>
<directory suffix="Test.php">./src/pool/tests</directory>
<directory suffix="Test.php">./src/process/tests</directory>
<directory suffix="Test.php">./src/protocol/tests</directory>
@ -92,6 +93,7 @@
<directory suffix=".php">./src/model-cache/src</directory>
<directory suffix=".php">./src/nsq/src</directory>
<directory suffix=".php">./src/paginator/src</directory>
<directory suffix=".php">./src/phar/src</directory>
<directory suffix=".php">./src/redis/src</directory>
<directory suffix=".php">./src/rpc/src</directory>
<directory suffix=".php">./src/scout/src</directory>

View File

@ -16,15 +16,15 @@
},
"require": {
"php": ">=7.2",
"doctrine/instantiator": "^1.2.0",
"hyperf/contract": "~2.1.0",
"hyperf/utils": "~2.1.0",
"hyperf/process": "~2.1.0",
"hyperf/pool": "~2.1.0",
"php-amqplib/php-amqplib": "^2.9.2",
"psr/container": "^1.0",
"psr/event-dispatcher": "^1.0",
"psr/log": "^1.0",
"php-amqplib/php-amqplib": "^2.7",
"doctrine/instantiator": "^1.2.0"
"psr/log": "^1.0"
},
"require-dev": {
"hyperf/di": "~2.1.0",

View File

@ -15,6 +15,7 @@ use Hyperf\Utils\Contracts\Arrayable;
use Hyperf\Utils\Coroutine;
use Hyperf\Utils\Str;
use Psr\EventDispatcher\EventDispatcherInterface;
use Swoole\ExitException;
use Symfony\Component\Console\Command\Command as SymfonyCommand;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Symfony\Component\Console\Helper\Table;
@ -435,6 +436,8 @@ abstract class Command extends SymfonyCommand
$this->eventDispatcher && $this->eventDispatcher->dispatch(new Event\BeforeHandle($this));
call([$this, 'handle']);
$this->eventDispatcher && $this->eventDispatcher->dispatch(new Event\AfterHandle($this));
} catch (ExitException $exception) {
// Do nothing.
} catch (\Throwable $exception) {
if (! $this->eventDispatcher) {
throw $exception;

View File

@ -1298,7 +1298,7 @@ class Builder
[$name, $constraints] = Str::contains($name, ':')
? $this->createSelectWithConstraint($name)
: [$name, function () {
: [$name, static function () {
}];
}
@ -1321,7 +1321,7 @@ class Builder
*/
protected function createSelectWithConstraint($name)
{
return [explode(':', $name)[0], function ($query) use ($name) {
return [explode(':', $name)[0], static function ($query) use ($name) {
$query->select(explode(',', explode(':', $name)[1]));
}];
}
@ -1344,7 +1344,7 @@ class Builder
$progress[] = $segment;
if (! isset($results[$last = implode('.', $progress)])) {
$results[$last] = function () {
$results[$last] = static function () {
};
}
}

View File

@ -153,7 +153,7 @@ class CoreMiddleware implements CoreMiddlewareInterface
} else {
[$controller, $action] = $this->prepareHandler($dispatched->handler->callback);
$controllerInstance = $this->container->get($controller);
if (! method_exists($controller, $action)) {
if (! method_exists($controllerInstance, $action)) {
// Route found, but the handler does not exist.
throw new ServerErrorHttpException('Method of class does not exist.');
}

View File

@ -11,12 +11,14 @@ declare(strict_types=1);
*/
namespace HyperfTest\HttpServer;
use FastRoute\Dispatcher;
use Hyperf\Contract\NormalizerInterface;
use Hyperf\Di\ClosureDefinitionCollector;
use Hyperf\Di\ClosureDefinitionCollectorInterface;
use Hyperf\Di\MethodDefinitionCollector;
use Hyperf\Di\MethodDefinitionCollectorInterface;
use Hyperf\Dispatcher\HttpRequestHandler;
use Hyperf\HttpMessage\Exception\ServerErrorHttpException;
use Hyperf\HttpMessage\Server\Request;
use Hyperf\HttpMessage\Stream\SwooleStream;
use Hyperf\HttpMessage\Uri\Uri;
@ -30,6 +32,7 @@ use Hyperf\Utils\Contracts\Jsonable;
use Hyperf\Utils\Serializer\SimpleNormalizer;
use HyperfTest\HttpServer\Stub\CoreMiddlewareStub;
use HyperfTest\HttpServer\Stub\DemoController;
use HyperfTest\HttpServer\Stub\FooController;
use HyperfTest\HttpServer\Stub\SetHeaderMiddleware;
use Mockery;
use PHPUnit\Framework\TestCase;
@ -183,6 +186,37 @@ class CoreMiddlewareTest extends TestCase
$response = $handler->handle($request);
}
public function testHandleFound()
{
$container = $this->getContainer();
$container->shouldReceive('get')->with(DemoController::class)->andReturn(new DemoController());
$middleware = new CoreMiddleware($container, 'http');
$ref = new \ReflectionClass($middleware);
$method = $ref->getMethod('handleFound');
$method->setAccessible(true);
$handler = new Handler([DemoController::class, 'demo'], '/');
$dispatched = new Dispatched([Dispatcher::FOUND, $handler, []]);
$res = $method->invokeArgs($middleware, [$dispatched, Mockery::mock(ServerRequestInterface::class)]);
$this->assertSame('Hello World.', $res);
}
public function testHandleFoundWithNamespace()
{
$container = $this->getContainer();
$container->shouldReceive('get')->with(DemoController::class)->andReturn(new FooController());
$middleware = new CoreMiddleware($container, 'http');
$ref = new \ReflectionClass($middleware);
$method = $ref->getMethod('handleFound');
$method->setAccessible(true);
$this->expectException(ServerErrorHttpException::class);
$this->expectExceptionMessage('Method of class does not exist.');
$handler = new Handler([DemoController::class, 'demo'], '/');
$dispatched = new Dispatched([Dispatcher::FOUND, $handler, []]);
$method->invokeArgs($middleware, [$dispatched, Mockery::mock(ServerRequestInterface::class)]);
}
protected function getContainer()
{
$container = Mockery::mock(ContainerInterface::class);

View File

@ -26,4 +26,9 @@ class DemoController
{
return $this->__return($id, $name, $params);
}
public function demo()
{
return 'Hello World.';
}
}

View File

@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace HyperfTest\HttpServer\Stub;
class FooController
{
}

View File

@ -29,7 +29,7 @@ class InvalidCacheManager
public function delete(): void
{
foreach ($this->models as $model) {
while ($model = array_pop($this->models)) {
$model->deleteCache();
}
}

View File

@ -93,6 +93,20 @@ class ManagerTest extends TestCase
$this->assertSame(100, $manager->getCacheTTL($model, $handler));
}
public function testInvalidCacheManager()
{
parallel([static function () {
$manager = ModelCache\InvalidCacheManager::instance();
$model = Mockery::mock(ModelCache\CacheableInterface::class);
$model->shouldReceive('deleteCache')->once()->andReturn(true);
$manager->push($model);
$manager->delete();
$manager->delete();
}]);
$this->assertInstanceOf(ModelCache\InvalidCacheManager::class, ModelCache\InvalidCacheManager::instance());
}
protected function getConfig(): array
{
return [

View File

@ -22,8 +22,10 @@ use Hyperf\Nacos\Constants;
use Hyperf\Nacos\Exception\RuntimeException;
use Hyperf\Nacos\Instance;
use Hyperf\Nacos\Service;
use Hyperf\Server\Event\CoroutineServerStart;
use Hyperf\Server\Event\MainCoroutineServerStart;
use Hyperf\Utils\Arr;
use Hyperf\Utils\Coordinator\CoordinatorManager;
use Hyperf\Utils\Coroutine;
use Psr\Container\ContainerInterface;
class MainWorkerStartListener implements ListenerInterface
@ -48,7 +50,7 @@ class MainWorkerStartListener implements ListenerInterface
{
return [
MainWorkerStart::class,
CoroutineServerStart::class,
MainCoroutineServerStart::class,
];
}
@ -79,19 +81,49 @@ class MainWorkerStartListener implements ListenerInterface
}
$this->logger->info('nacos register instance success.', compact('instance'));
$client = $this->container->get(Client::class);
$config = $this->container->get(ConfigInterface::class);
$appendNode = $config->get('nacos.config_append_node');
$this->refreshConfig();
foreach ($client->pull() as $key => $conf) {
$configKey = $appendNode ? $appendNode . '.' . $key : $key;
if (is_array($conf) && $config->get('nacos.config_merge_mode') == Constants::CONFIG_MERGE_APPEND) {
$conf = Arr::merge($config->get($configKey, []), $conf);
}
$config->set($configKey, $conf);
if ($event instanceof MainCoroutineServerStart) {
$interval = (int) $config->get('nacos.config_reload_interval', 3);
Coroutine::create(function () use ($interval) {
sleep($interval);
retry(INF, function () use ($interval) {
$prevConfig = [];
while (true) {
$coordinator = CoordinatorManager::until(\Hyperf\Utils\Coordinator\Constants::WORKER_EXIT);
$workerExited = $coordinator->yield($interval);
if ($workerExited) {
break;
}
$prevConfig = $this->refreshConfig($prevConfig);
}
}, $interval * 1000);
});
}
} catch (\Throwable $exception) {
$this->logger->critical((string) $exception);
}
}
protected function refreshConfig(array $prevConfig = []): array
{
$client = $this->container->get(Client::class);
$config = $this->container->get(ConfigInterface::class);
$appendNode = $config->get('nacos.config_append_node');
$result = $client->pull();
if ($result === $prevConfig) {
return $result;
}
foreach ($result as $key => $conf) {
$configKey = $appendNode ? $appendNode . '.' . $key : $key;
if (is_array($conf) && $config->get('nacos.config_merge_mode') == Constants::CONFIG_MERGE_APPEND) {
$conf = Arr::merge($config->get($configKey, []), $conf);
}
$config->set($configKey, $conf);
}
return $result;
}
}

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

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

25
src/phar/.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,25 @@
on:
push:
# Sequence of patterns matched against refs/tags
tags:
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
name: Release
jobs:
release:
name: Release
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Create Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: Release ${{ github.ref }}
draft: false
prerelease: false

21
src/phar/LICENSE Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) Hyperf
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

55
src/phar/composer.json Normal file
View File

@ -0,0 +1,55 @@
{
"name": "hyperf/phar",
"description": "A component that supports pack Hyperf project as a Phar stand-alone package.",
"license": "MIT",
"keywords": [
"php",
"swoole",
"hyperf",
"phar"
],
"homepage": "https://hyperf.io",
"support": {
"docs": "https://hyperf.wiki",
"issues": "https://github.com/hyperf/hyperf/issues",
"pull-request": "https://github.com/hyperf/hyperf/pulls",
"source": "https://github.com/hyperf/hyperf"
},
"autoload": {
"psr-4": {
"Hyperf\\Phar\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"HyperfTest\\Phar\\": "tests/"
}
},
"require": {
"php": ">=7.2",
"ext-json": "*",
"hyperf/command": "~2.0.0",
"hyperf/contract": "~2.0.0",
"nikic/php-parser": "^4.1",
"psr/container": "^1.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.14",
"mockery/mockery": "^1.0",
"phpstan/phpstan": "^0.12",
"phpunit/phpunit": ">=7.0"
},
"config": {
"sort-packages": true
},
"scripts": {
"test": "phpunit -c phpunit.xml --colors=always",
"analyse": "phpstan analyse --memory-limit 1024M -l 0 ./src",
"cs-fix": "php-cs-fixer fix $1"
},
"extra": {
"hyperf": {
"config": "Hyperf\\Phar\\ConfigProvider"
}
}
}

32
src/phar/src/Ast/Ast.php Normal file
View File

@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Phar\Ast;
use PhpParser\NodeTraverser;
use PhpParser\ParserFactory;
use PhpParser\PrettyPrinter\Standard;
class Ast
{
public static function parse(string $code, array $visitors): string
{
$parser = (new ParserFactory())->create(ParserFactory::ONLY_PHP7);
$printer = new Standard();
$traverser = new NodeTraverser();
foreach ($visitors as $visitor) {
$traverser->addVisitor($visitor);
}
$stmts = $parser->parse($code);
$stmts = $traverser->traverse($stmts);
return $printer->prettyPrintFile($stmts);
}
}

View File

@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Phar\Ast\Visitor;
use PhpParser\Node;
use PhpParser\NodeVisitorAbstract;
class RewriteConfigVisitor extends NodeVisitorAbstract
{
public function leaveNode(Node $node)
{
if ($node instanceof Node\Stmt\Return_) {
if (! $node->expr instanceof Node\Expr\Array_) {
return $node;
}
foreach ($node->expr->items as $item) {
if (! $item instanceof Node\Expr\ArrayItem) {
continue;
}
if ($item->key instanceof Node\Scalar\String_ && strtolower($item->key->value) == 'scan_cacheable') {
$item->value = new Node\Expr\ConstFetch(new Node\Name('true'));
}
}
}
return $node;
}
}

View File

@ -0,0 +1,91 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Phar;
use Hyperf\Command\Command as HyperfCommand;
use InvalidArgumentException;
use Psr\Container\ContainerInterface;
use RuntimeException;
use Symfony\Component\Console\Input\InputOption;
use UnexpectedValueException;
class BuildCommand extends HyperfCommand
{
/**
* @var ContainerInterface
*/
protected $container;
public function __construct(ContainerInterface $container)
{
parent::__construct('phar:build');
$this->container = $container;
}
public function configure()
{
$this->setDescription('Pack your project into a Phar package.')
->addOption('name', '', InputOption::VALUE_OPTIONAL, 'This is the name of the Phar package, and if it is not passed in, the project name is used by default', null)
->addOption('bin', 'b', InputOption::VALUE_OPTIONAL, 'The script path to execute by default.', 'bin/hyperf.php')
->addOption('path', 'p', InputOption::VALUE_OPTIONAL, 'Project root path, default BASE_PATH.', null);
}
public function handle()
{
$this->assertWritable();
$name = $this->input->getOption('name');
$bin = $this->input->getOption('bin');
$path = $this->input->getOption('path');
if (empty($path)) {
$path = BASE_PATH;
}
$builder = $this->getPharBuilder($path);
if (! empty($bin)) {
$builder->setMain($bin);
}
if (! empty($name)) {
$builder->setTarget($name);
}
$builder->build();
}
/**
* check readonly.
*/
public function assertWritable()
{
if (ini_get('phar.readonly') === '1') {
throw new UnexpectedValueException('Your configuration disabled writing phar files (phar.readonly = On), please update your configuration');
}
}
public function getPharBuilder(string $path, ?string $version = null): PharBuilder
{
if ($version !== null) {
$path .= ':' . $version;
}
if (is_dir($path)) {
$path = rtrim($path, '/') . '/composer.json';
}
if (! is_file($path)) {
throw new InvalidArgumentException(sprintf('The given path %s is not a readable file', $path));
}
$pharBuilder = new PharBuilder($path, $this->container->get(LoggerInterface::class));
$vendorPath = $pharBuilder->getPackage()->getVendorAbsolutePath();
if (! is_dir($vendorPath)) {
throw new RuntimeException('The project has not been initialized, please manually execute the command `composer install` to install the dependencies');
}
return $pharBuilder;
}
}

95
src/phar/src/Bundle.php Normal file
View File

@ -0,0 +1,95 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Phar;
use ArrayIterator;
use IteratorAggregate;
use SplFileInfo;
use Symfony\Component\Finder\Finder;
use Traversable;
class Bundle implements IteratorAggregate
{
/**
* @var Finder[]|string[]
*/
private $resources = [];
/**
* Add a file to the resource bundle.
* @return $this
*/
public function addFile(string $file)
{
$this->resources[] = $file;
return $this;
}
/**
* @param string[] $dirs
* @return $this
*/
public function addDirs(array $dirs)
{
return $this->addFinder((new Finder())->files()->ignoreVCS(true)->in($dirs));
}
/**
* Add a directory package to a resource package.
* @return $this
*/
public function addFinder(Finder $dir)
{
$this->resources[] = $dir;
return $this;
}
/**
* Determines whether the file exists in the resource bundle.
*/
public function checkContains(string $resource): bool
{
foreach ($this->resources as $containedResource) {
if ($containedResource instanceof Finder && $this->directoryContains($containedResource, $resource)) {
return true;
}
if (is_string($containedResource) && $containedResource === $resource) {
return true;
}
}
return false;
}
/**
* Returns an iterator for a list of resources.
*/
public function getIterator(): Traversable
{
return new ArrayIterator($this->resources);
}
/**
* Determines whether the file exists in the folder resource bundle.
*/
private function directoryContains(Finder $dir, string $resource): bool
{
$resourceStrLength = strlen($resource);
foreach ($dir as $containedResource) {
/* @var $containedResource SplFileInfo */
if (substr($containedResource->getRealPath(), 0, $resourceStrLength) == $resource) {
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Phar;
use Hyperf\Framework\Logger\StdoutLogger;
class ConfigProvider
{
public function __invoke(): array
{
return [
'dependencies' => [
LoggerInterface::class => StdoutLogger::class,
],
'commands' => [
BuildCommand::class,
],
];
}
}

View File

@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Phar;
interface LoggerInterface extends \Psr\Log\LoggerInterface
{
}

115
src/phar/src/Package.php Normal file
View File

@ -0,0 +1,115 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Phar;
use Symfony\Component\Finder\Finder;
class Package
{
/**
* @var array
*/
protected $package;
/**
* @var string
*/
protected $directory;
public function __construct(array $package, string $directory)
{
$this->package = $package;
$this->directory = rtrim($directory, '/') . '/';
}
/**
* Get full package name.
*/
public function getName(): ?string
{
return $this->package['name'] ?? null;
}
/**
* Gets the short package name
* If not, the pathname is used as the package name.
*/
public function getShortName(): string
{
$name = $this->getName();
if ($name === null) {
$name = realpath($this->getDirectory());
if ($name === false) {
$name = $this->getDirectory();
}
}
return basename($name);
}
/**
* Gets the relative address of the vendor directory, which supports custom addresses in composer.json.
*/
public function getVendorPath(): string
{
$vendor = 'vendor';
if (isset($this->package['config']['vendor-dir'])) {
$vendor = $this->package['config']['vendor-dir'];
}
return $vendor . '/';
}
/**
* Gets the absolute address of the vendor directory.
*/
public function getVendorAbsolutePath(): string
{
return $this->getDirectory() . $this->getVendorPath();
}
/**
* Get package directory.
*/
public function getDirectory(): string
{
return $this->directory;
}
/**
* Get resource bundle object.
*/
public function bundle(Finder $finder = null): Bundle
{
$bundle = new Bundle();
$dir = $this->getDirectory();
$vendorPath = $this->getVendorPath();
if (empty($this->package['autoload']) && ! is_dir($dir . $vendorPath)) {
return $bundle;
}
if ($finder == null) {
$finder = Finder::create()
->files()
->ignoreVCS(true)
->exclude(rtrim($vendorPath, '/'))
->notPath('/^composer\.phar/')
->in($dir);
}
return $bundle->addFinder($finder);
}
/**
* Gets the executable file path, and the directory address where the Phar package will run.
*/
public function getBins(): array
{
return $this->package['bin'] ?? [];
}
}

View File

@ -0,0 +1,281 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Phar;
use FilesystemIterator;
use GlobIterator;
use Hyperf\Phar\Ast\Ast;
use Hyperf\Phar\Ast\Visitor\RewriteConfigVisitor;
use InvalidArgumentException;
use Phar;
use Psr\Log\LoggerInterface;
use RuntimeException;
use Symfony\Component\Finder\Finder;
use UnexpectedValueException;
class PharBuilder
{
/**
* @var LoggerInterface
*/
protected $logger;
/**
* @var Package
*/
private $package;
/**
* @var null|string|TargetPhar
*/
private $target;
/**
* @var string
*/
private $main;
public function __construct(string $path, LoggerInterface $logger)
{
$this->logger = $logger;
$this->package = new Package($this->loadJson($path), dirname(realpath($path)));
}
/**
* Gets the Phar package name.
*/
public function getTarget(): string
{
if ($this->target === null) {
$this->target = $this->package->getShortName() . '.phar';
}
return (string) $this->target;
}
/**
* Set the Phar package name.
* @param string|TargetPhar $target
* @return $this
*/
public function setTarget($target)
{
if (is_dir($target)) {
$this->target = null;
$target = rtrim($target, '/') . '/' . $this->getTarget();
}
$this->target = $target;
return $this;
}
/**
* Gets the default run script path.
*/
public function getMain(): string
{
if ($this->main === null) {
foreach ($this->package->getBins() as $path) {
if (! file_exists($this->package->getDirectory() . $path)) {
throw new UnexpectedValueException('Bin file "' . $path . '" does not exist');
}
$this->main = $path;
break;
}
// Use the default hyperf bootstrap file as default.
if ($this->main == null) {
return 'bin/hyperf.php';
}
}
return $this->main;
}
/**
* Set the default startup file.
* @return $this
*/
public function setMain(string $main)
{
$this->main = $main;
return $this;
}
/**
* Get package object.
*/
public function getPackage(): Package
{
return $this->package;
}
/**
* Gets a list of all dependent packages.
* @return Package[]
*/
public function getPackagesDependencies(): array
{
$packages = [];
$vendorPath = $this->package->getVendorAbsolutePath();
// Gets all installed dependency packages
if (is_file($vendorPath . 'composer/installed.json')) {
$installed = $this->loadJson($vendorPath . 'composer/installed.json');
$installedPackages = $installed;
// Adapte Composer 2.0
if (isset($installed['packages'])) {
$installedPackages = $installed['packages'];
}
// Package all of these dependent components into the packages
foreach ($installedPackages as $package) {
$dir = $package['name'] . '/';
if (isset($package['target-dir'])) {
$dir .= trim($package['target-dir'], '/') . '/';
}
$dir = $vendorPath . $dir;
$packages[] = new Package($package, $dir);
}
}
return $packages;
}
/**
* Gets the relative path relative to the resource bundle.
*/
public function getPathLocalToBase(string $path): ?string
{
$root = $this->package->getDirectory();
if (strpos($path, $root) !== 0) {
throw new UnexpectedValueException('Path "' . $path . '" is not within base project path "' . $root . '"');
}
return substr($path, strlen($root)) ?? null;
}
/**
* Compile the code into the Phar file.
*/
public function build()
{
$this->logger->info('Creating phar <info>' . $this->getTarget() . '</info>');
$time = microtime(true);
$vendorPath = $this->package->getVendorAbsolutePath();
if (! is_dir($vendorPath)) {
throw new RuntimeException(sprintf('Directory %s not properly installed, did you run "composer install" ?', $vendorPath));
}
// Get file path which could be written for phar.
$target = $this->getTarget();
do {
$tmp = $target . '.' . mt_rand() . '.phar';
} while (file_exists($tmp));
$targetPhar = new TargetPhar(new Phar($tmp), $this);
$this->logger->info('Adding main package "' . $this->package->getName() . '"');
$finder = Finder::create()
->files()
->ignoreVCS(true)
->exclude(rtrim($this->package->getVendorPath(), '/'))
->exclude('runtime') //Ignore runtime dir
->notPath('/^composer\.phar/')
->notPath($target) //Ignore the phar package that exists in the project itself
->in($this->package->getDirectory());
$targetPhar->addBundle($this->package->bundle($finder));
// Force to turn on ScanCacheable.
$this->enableScanCacheable($targetPhar);
// Load the Runtime folder separately
if (is_dir($this->package->getDirectory() . 'runtime')) {
$this->logger->info('Adding runtime container files');
$finder = Finder::create()
->files()
->in($this->package->getDirectory() . 'runtime/container');
$targetPhar->addBundle($this->package->bundle($finder));
}
$this->logger->info('Adding composer base files');
// Add composer autoload file.
$targetPhar->addFile($vendorPath . 'autoload.php');
// Add composer autoload files.
$targetPhar->buildFromIterator(new GlobIterator($vendorPath . 'composer/*.*', FilesystemIterator::KEY_AS_FILENAME));
// Add composer depenedencies.
foreach ($this->getPackagesDependencies() as $package) {
// Cannot package yourself.
if (stripos($package->getDirectory(), 'vendor/hyperf/phar/') !== false) {
continue;
}
$this->logger->info('Adding dependency "' . $package->getName() . '" from "' . $this->getPathLocalToBase($package->getDirectory()) . '"');
$targetPhar->addBundle($package->bundle());
}
$this->logger->info('Setting main/stub');
$main = $this->getMain();
// Add the default stub.
$targetPhar->setStub($targetPhar->createDefaultStub($main));
$this->logger->info('Setting default stub <info>' . $main . '</info>.');
$targetPhar->stopBuffering();
if (file_exists($target)) {
$this->logger->info('Overwriting existing file <info>' . $target . '</info> (' . $this->getSize($target) . ')');
}
if (rename($tmp, $target) === false) {
throw new UnexpectedValueException(sprintf('Unable to rename temporary phar archive to %s', $target));
}
$time = max(microtime(true) - $time, 0);
$this->logger->info('');
$this->logger->info(' <info>OK</info> - Creating <info>' . $this->getTarget() . '</info> (' . $this->getSize($this->getTarget()) . ') completed after ' . round($time, 1) . 's');
}
/**
* Find the scan_cacheable configuration and force it to open.
*/
protected function enableScanCacheable(TargetPhar $targetPhar)
{
$configPath = 'config/config.php';
$absPath = $this->package->getDirectory() . $configPath;
if (! file_exists($absPath)) {
return;
}
$code = file_get_contents($absPath);
$code = Ast::parse($code, [new RewriteConfigVisitor()]);
$targetPhar->addFromString($configPath, $code);
}
/**
* Load the configuration.
*/
private function loadJson(string $path): array
{
$result = json_decode(file_get_contents($path), true);
if ($result === null) {
throw new InvalidArgumentException(sprintf('Unable to parse given path %s', $path), json_last_error());
}
return $result;
}
/**
* Get file size.
*
* @param PharBuilder|string $path
*/
private function getSize($path): string
{
return round(filesize((string) $path) / 1024, 1) . ' KiB';
}
}

108
src/phar/src/TargetPhar.php Normal file
View File

@ -0,0 +1,108 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Phar;
use Phar;
use Symfony\Component\Finder\Finder;
use Traversable;
class TargetPhar
{
/**
* @var Phar
*/
private $phar;
/**
* @var PharBuilder
*/
private $pharBuilder;
public function __construct(Phar $phar, PharBuilder $pharBuilder)
{
$phar->startBuffering();
$this->phar = $phar;
$this->pharBuilder = $pharBuilder;
}
public function __toString(): string
{
$exploded = explode('/', $this->phar->getPath());
return end($exploded);
}
/**
* Start writing the Phar package.
*/
public function stopBuffering()
{
$this->phar->stopBuffering();
}
/**
* Add a resource bundle to the Phar package.
*/
public function addBundle(Bundle $bundle)
{
/** @var Finder|string $resource */
foreach ($bundle as $resource) {
if (is_string($resource)) {
$this->addFile($resource);
} else {
$this->buildFromIterator($resource);
}
}
}
/**
* Add the file to the Phar package.
*/
public function addFile(string $filename): void
{
$this->phar->addFile($filename, $this->pharBuilder->getPathLocalToBase($filename));
}
/**
* Add folder resources to the Phar package.
*/
public function buildFromIterator(Traversable $iterator): void
{
$this->phar->buildFromIterator($iterator, $this->pharBuilder->getPackage()->getDirectory());
}
/**
* Create the default execution file.
*/
public function createDefaultStub(string $indexFile, string $webIndexFile = null): string
{
if ($webIndexFile != null) {
return $this->phar->createDefaultStub($indexFile, $webIndexFile);
}
return $this->phar->createDefaultStub($indexFile);
}
/**
* Set the default startup file.
*/
public function setStub(string $stub): void
{
$this->phar->setStub($stub);
}
/**
* Add a string to the Phar package.
*/
public function addFromString(string $local, string $contents): void
{
$this->phar->addFromString($local, $contents);
}
}

View File

@ -0,0 +1,93 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace HyperfTest\Phar;
use Hyperf\Phar\Package;
use PHPUnit\Framework\TestCase;
/**
* @internal
* @coversNothing
*/
class PackageTest extends TestCase
{
public function testDefaults()
{
$package = new Package([], 'dirs/');
$this->assertEquals([], $package->getBins());
$this->assertEquals('dirs/', $package->getDirectory());
$this->assertEquals(null, $package->getName());
$this->assertEquals('dirs', $package->getShortName());
$this->assertEquals('vendor/', $package->getVendorPath());
}
public function testPackage()
{
$package = new Package([
'name' => 'hyperf/phar',
'bin' => ['bin/hyperf.php', 'bin/phar.php'],
'config' => [
'vendor-dir' => 'src/vendors',
],
], 'dirs/');
$this->assertEquals(['bin/hyperf.php', 'bin/phar.php'], $package->getBins());
$this->assertEquals('hyperf/phar', $package->getName());
$this->assertEquals('phar', $package->getShortName());
$this->assertEquals('src/vendors/', $package->getVendorPath());
}
public function testBundleWillContainComposerJsonButNotVendor()
{
$dir = realpath(__DIR__ . '/fixtures/03-project-with-phars') . '/';
$package = new Package([
'config' => [
'vendor-dir' => 'vendors',
],
], $dir);
$bundle = $package->bundle();
$this->assertTrue($bundle->checkContains($dir . 'composer.json'));
$this->assertFalse($bundle->checkContains($dir . 'vendor/autoload.php'));
$this->assertFalse($bundle->checkContains($dir . 'composer.phar'));
$this->assertTrue($bundle->checkContains($dir . 'phar-composer.phar'));
}
public function testBundleWillNotContainComposerPharInRoot()
{
$dir = realpath(__DIR__ . '/fixtures/03-project-with-phars') . '/';
$package = new Package([
'config' => [
'vendor-dir' => 'vendors',
],
], $dir);
$bundle = $package->bundle();
$this->assertFalse($bundle->checkContains($dir . 'composer.phar'));
$this->assertTrue($bundle->checkContains($dir . 'phar-composer.phar'));
}
public function testBundleWillContainComposerPharFromSrc()
{
$dir = realpath(__DIR__ . '/fixtures/04-project-with-phars-in-src') . '/';
$package = new Package([
'config' => [
'vendor-dir' => 'vendors',
],
], $dir);
$bundle = $package->bundle();
$this->assertTrue($bundle->checkContains($dir . 'composer.json'));
$this->assertTrue($bundle->checkContains($dir . 'src/composer.phar'));
$this->assertTrue($bundle->checkContains($dir . 'src/phar-composer.phar'));
}
}

View File

@ -0,0 +1,74 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace HyperfTest\Phar;
use Hyperf\Phar\Ast\Ast;
use Hyperf\Phar\Ast\Visitor\RewriteConfigVisitor;
use PHPUnit\Framework\TestCase;
/**
* @internal
* @coversNothing
*/
class VisitorTest extends TestCase
{
public function testRewriteConfig()
{
$code = "<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
use Hyperf\\Contract\\StdoutLoggerInterface;
use Psr\\Log\\LogLevel;
return [
'app_name' => env('APP_NAME', 'skeleton'),
'app_env' => env('APP_ENV', 'dev'),
'scan_cacheable' => env('SCAN_CACHEABLE', false),
StdoutLoggerInterface::class => [
'log_level' => [
LogLevel::ALERT,
LogLevel::CRITICAL,
LogLevel::DEBUG,
LogLevel::EMERGENCY,
LogLevel::ERROR,
LogLevel::INFO,
LogLevel::NOTICE,
LogLevel::WARNING,
],
],
];
";
$code = Ast::parse($code, [new RewriteConfigVisitor()]);
$this->assertSame("<?php
declare (strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
use Hyperf\\Contract\\StdoutLoggerInterface;
use Psr\\Log\\LogLevel;
return ['app_name' => env('APP_NAME', 'skeleton'), 'app_env' => env('APP_ENV', 'dev'), 'scan_cacheable' => true, StdoutLoggerInterface::class => ['log_level' => [LogLevel::ALERT, LogLevel::CRITICAL, LogLevel::DEBUG, LogLevel::EMERGENCY, LogLevel::ERROR, LogLevel::INFO, LogLevel::NOTICE, LogLevel::WARNING]]];", $code);
}
}

View File

@ -0,0 +1,3 @@
{
"name": "vendor/empty"
}

View File

@ -0,0 +1,3 @@
# Readme
Not a valid project directory (does not contain a `composer.json` file).

View File

@ -0,0 +1,2 @@
{
}

View File

@ -0,0 +1,2 @@
<?php
// empty

View File

@ -0,0 +1,2 @@
<?php
// empty

View File

@ -0,0 +1,2 @@
{
}

View File

@ -0,0 +1,2 @@
<?php
// empty

View File

@ -0,0 +1,2 @@
<?php
// empty

View File

@ -0,0 +1,3 @@
{
"bin": ["bin/invalid"]
}

View File

@ -0,0 +1,5 @@
{
"require": {
"roave/security-advisories": "dev-master"
}
}

View File

@ -21,9 +21,9 @@ return [
'engine' => [
'elasticsearch' => [
'driver' => Hyperf\Scout\Provider\ElasticsearchProvider::class,
'index' => env('ELASTICSEARCH_INDEX', 'hyperf'),
'index' => null,
'hosts' => [
env('ELASTICSEARCH_HOST', 'http://localhost'),
env('ELASTICSEARCH_HOST', 'http://127.0.0.1:9200'),
],
],
],

View File

@ -209,18 +209,24 @@ class Server implements ServerInterface
protected function defaultCallbacks()
{
$hasCallback = class_exists(Bootstrap\StartCallback::class) &&
class_exists(Bootstrap\ManagerStartCallback::class) &&
class_exists(Bootstrap\WorkerStartCallback::class);
$hasCallback = class_exists(Bootstrap\StartCallback::class)
&& class_exists(Bootstrap\ManagerStartCallback::class)
&& class_exists(Bootstrap\WorkerStartCallback::class);
if ($hasCallback) {
return [
Event::ON_START => [Bootstrap\StartCallback::class, 'onStart'],
$callbacks = [
Event::ON_MANAGER_START => [Bootstrap\ManagerStartCallback::class, 'onManagerStart'],
Event::ON_WORKER_START => [Bootstrap\WorkerStartCallback::class, 'onWorkerStart'],
Event::ON_WORKER_STOP => [Bootstrap\WorkerStopCallback::class, 'onWorkerStop'],
Event::ON_WORKER_EXIT => [Bootstrap\WorkerExitCallback::class, 'onWorkerExit'],
];
if ($this->server->mode === SWOOLE_BASE) {
return $callbacks;
}
return array_merge([
Event::ON_START => [Bootstrap\StartCallback::class, 'onStart'],
], $callbacks);
}
return [

View File

@ -74,6 +74,11 @@ class Parallel
return $result;
}
public function count(): int
{
return count($this->callbacks);
}
public function clear(): void
{
$this->callbacks = [];

View File

@ -216,6 +216,25 @@ class ParallelTest extends TestCase
}
}
public function testParallelCount()
{
$parallel = new Parallel();
$id = 0;
$parallel->add(static function () use (&$id) {
++$id;
});
$parallel->add(static function () use (&$id) {
++$id;
});
$this->assertSame(2, $parallel->count());
$parallel->wait();
$this->assertSame(2, $parallel->count());
$this->assertSame(2, $id);
$parallel->wait();
$this->assertSame(2, $parallel->count());
$this->assertSame(4, $id);
}
public function returnCoId()
{
return Coroutine::id();

View File

@ -146,7 +146,7 @@ trait ValidatesAttributes
return false;
}
return preg_match('/^[\pL\pM\pN_-]+$/u', $value) > 0;
return preg_match('/^[\pL\pM\pN_-]+$/u', (string) $value) > 0;
}
/**

View File

@ -20,6 +20,34 @@ use PHPUnit\Framework\TestCase;
*/
class ValidateAttributesTest extends TestCase
{
public function testValidateAlpha()
{
$validator = new ValidatesAttributesStub();
$this->assertTrue($validator->validateAlpha('', 'xxx'));
$this->assertTrue($validator->validateAlpha('', '你好'));
$this->assertFalse($validator->validateAlpha('', '123'));
$this->assertFalse($validator->validateAlpha('', '123f1'));
$this->assertFalse($validator->validateAlpha('', 123));
$this->assertFalse($validator->validateAlpha('', 123.1));
$this->assertFalse($validator->validateAlpha('', '123_f1'));
$this->assertFalse($validator->validateAlpha('', 'xxx_yy'));
}
public function testValidateAlphaDash()
{
$validator = new ValidatesAttributesStub();
$this->assertTrue($validator->validateAlphaDash('', 'xxx'));
$this->assertTrue($validator->validateAlphaDash('', 'xxx_yy'));
$this->assertTrue($validator->validateAlphaDash('', '你好'));
$this->assertTrue($validator->validateAlphaDash('', '123'));
$this->assertTrue($validator->validateAlphaDash('', '123f1'));
$this->assertTrue($validator->validateAlphaDash('', 123));
$this->assertTrue($validator->validateAlphaDash('', '123_f1'));
$this->assertFalse($validator->validateAlphaDash('', 123.1));
}
public function testValidateAlphaNum()
{
$validator = new ValidatesAttributesStub();
@ -27,8 +55,10 @@ class ValidateAttributesTest extends TestCase
$this->assertTrue($validator->validateAlphaNum('', '123'));
$this->assertTrue($validator->validateAlphaNum('', '123f1'));
$this->assertTrue($validator->validateAlphaNum('', 123));
$this->assertTrue($validator->validateAlphaNum('', '你好'));
$this->assertFalse($validator->validateAlphaNum('', 123.1));
$this->assertFalse($validator->validateAlphaNum('', '123_f1'));
$this->assertFalse($validator->validateAlphaNum('', 'xxx_yy'));
}
}

View File

@ -9,15 +9,14 @@ declare(strict_types=1);
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
use Hyperf\View\Engine\NoneEngine;
use Hyperf\View\Mode;
use Hyperf\ViewEngine\HyperfViewEngine;
return [
'engine' => HyperfViewEngine::class,
'engine' => NoneEngine::class,
'mode' => Mode::SYNC,
'config' => [
'view_path' => BASE_PATH . '/storage/view/',
'cache_path' => BASE_PATH . '/runtime/view/',
'charset' => 'UTF-8',
],
];

View File

@ -0,0 +1,57 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\View\Engine;
class NoneEngine implements EngineInterface
{
public function render($template, $data, $config): string
{
return <<<'HTML'
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Hyperf</title>
<!-- Fonts -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css"
integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
</head>
<body>
<div class="container">
<div class="jumbotron">
<h1>Hyperf</h1>
<p>Hyperf is an extremely performant and flexible PHP CLI framework based on Swoole 4.5+, powered by the
state-of-the-art coroutine server and a large number of battle-tested components. Aside from the decisive
benchmark outmatching against PHP-FPM frameworks, Hyperf also distinct itself by its focus on flexibility
and composability. Hyperf ships with an AOP-enabling dependency injector to ensure components and classes
are pluggable and meta programmable. All of its core components strictly follow the PSR standards and thus
can be used in other frameworks.</p>
<p><a class="btn btn-primary btn-lg" href="https://hyperf.wiki/" role="button">Learn more</a></p>
<p>This view engine is not available, please use engines below.</p>
<ul class="list-group">
<li class="list-group-item"><a href="https://github.com/hyperf/view-engine">hyperf/view-engine</a></li>
<li class="list-group-item"><a href="https://github.com/duncan3dc/blade">duncan3dc/blade</a></li>
<li class="list-group-item"><a href="https://github.com/smarty-php/smarty">smarty/smarty</a></li>
<li class="list-group-item"><a href="https://github.com/twigphp/Twig">twig/twig</a></li>
<li class="list-group-item"><a href="https://github.com/thephpleague/plates">league/plates</a></li>
<li class="list-group-item"><a href="https://github.com/sy-records/think-template">sy-records/think-template</a></li>
</ul>
</div>
</div>
</body>
</html>
HTML;
}
}

View File

@ -17,7 +17,7 @@ use Hyperf\Task\Task;
use Hyperf\Task\TaskExecutor;
use Hyperf\Utils\Context;
use Hyperf\View\Engine\EngineInterface;
use Hyperf\View\Engine\SmartyEngine;
use Hyperf\View\Engine\NoneEngine;
use Hyperf\View\Exception\EngineNotFindException;
use Hyperf\View\Exception\RenderException;
use Psr\Container\ContainerInterface;
@ -47,7 +47,7 @@ class Render implements RenderInterface
public function __construct(ContainerInterface $container, ConfigInterface $config)
{
$engine = $config->get('view.engine', SmartyEngine::class);
$engine = $config->get('view.engine', NoneEngine::class);
if (! $container->has($engine)) {
throw new EngineNotFindException("{$engine} engine is not found.");
}

View File

@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace HyperfTest\View;
use Hyperf\View\Engine\NoneEngine;
use PHPUnit\Framework\TestCase;
/**
* @internal
* @coversNothing
*/
class NoneTest extends TestCase
{
public function testRender()
{
$content = (new NoneEngine())->render('/', [], []);
$this->assertNotEmpty($content);
}
}

View File

@ -16,6 +16,7 @@ use Hyperf\Di\Annotation\AnnotationReader;
use Hyperf\Di\ClassLoader;
use Hyperf\Utils\Codec\Json;
use Hyperf\Utils\Coroutine;
use Hyperf\Utils\Exception\InvalidArgumentException;
use Hyperf\Utils\Filesystem\FileNotFoundException;
use Hyperf\Utils\Filesystem\Filesystem;
use Hyperf\Watcher\Driver\DriverInterface;
@ -157,6 +158,10 @@ class Watcher
if (empty($file)) {
throw new FileNotFoundException('The config of pid_file is not found.');
}
$daemonize = $this->config->get('server.settings.daemonize', false);
if ($daemonize) {
throw new InvalidArgumentException('Please set `server.settings.daemonize` to false');
}
if (! $isStart && $this->filesystem->exists($file)) {
$pid = $this->filesystem->get($file);
try {

View File

@ -231,7 +231,11 @@ class Server implements MiddlewareInitializerInterface, OnHandShakeInterface, On
return;
}
$instance->onMessage($server, $frame);
try {
$instance->onMessage($server, $frame);
} catch (\Throwable $exception) {
$this->logger->error((string) $exception);
}
}
public function onClose($server, int $fd, int $reactorId): void
@ -252,7 +256,11 @@ class Server implements MiddlewareInitializerInterface, OnHandShakeInterface, On
$instance = $this->container->get($fdObj->class);
if ($instance instanceof OnCloseInterface) {
$instance->onClose($server, $fd, $reactorId);
try {
$instance->onClose($server, $fd, $reactorId);
} catch (\Throwable $exception) {
$this->logger->error((string) $exception);
}
}
}