diff --git a/.travis.yml b/.travis.yml index 8b96a097a..cb4aa2a78 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,17 +5,11 @@ sudo: required matrix: include: - php: 7.2 - env: SW_VERSION="4.3.6" - - php: 7.2 - env: SW_VERSION="4.4.4" + env: SW_VERSION="4.4.5" - php: 7.3 - env: SW_VERSION="4.3.6" - - php: 7.3 - env: SW_VERSION="4.4.4" + env: SW_VERSION="4.4.5" - php: master - env: SW_VERSION="4.3.6" - - php: master - env: SW_VERSION="4.4.4" + env: SW_VERSION="4.4.5" allow_failures: - php: master @@ -44,4 +38,5 @@ before_script: - composer config -g process-timeout 900 && composer update script: + - composer analyse src/di src/json-rpc - composer test diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f38e69ab..5528cdee5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,50 @@ +# v1.1.0 - TBD + +## Added + +- [#401](https://github.com/hyperf-cloud/hyperf/pull/401) [#447](https://github.com/hyperf-cloud/hyperf/issues/447) Optimized server and Fixed middleware that user defined does not works. +- [#402](https://github.com/hyperf-cloud/hyperf/pull/402) Added Annotation AsyncQueueMessage. +- [#418](https://github.com/hyperf-cloud/hyperf/pull/418) Allows send WebSocket message to any fd in current server, even the worker process does not hold the fd +- [#420](https://github.com/hyperf-cloud/hyperf/pull/420) Added listener for model. +- [#441](https://github.com/hyperf-cloud/hyperf/pull/441) Automatically close the spare redis client when it is used in low frequency. +- [#500](https://github.com/hyperf-cloud/hyperf/pull/499) Added fluent method calls of `Hyperf\HttpServer\Contract\ResponseInterface`. +- [#523](https://github.com/hyperf-cloud/hyperf/pull/523) Added option `table-mapping` for command `db:model`. +- [#555](https://github.com/hyperf-cloud/hyperf/pull/555) Added global function `swoole_hook_flags` to get the hook flags by constant `SWOOLE_HOOK_FLAGS`, and you could define in `bin/hyperf.php` via `! defined('SWOOLE_HOOK_FLAGS') && define('SWOOLE_HOOK_FLAGS', SWOOLE_HOOK_ALL);` to define the constant. + +## Changed + +- [#437](https://github.com/hyperf-cloud/hyperf/pull/437) Changed `Hyperf\Testing\Client` handle exception handlers instead of throw an exception directly. +- [#463](https://github.com/hyperf-cloud/hyperf/pull/463) Simplify `container.php` and improve annotation caching mechanism. +- [#523](https://github.com/hyperf-cloud/hyperf/pull/523) Generate the singular class of an plural table. + +config/container.php + +```php + Warning: 请勿于生产环境使用 `热更新/热重载` 功能 diff --git a/doc/zh/cache.md b/doc/zh/cache.md index 9ed42cd07..1e98de982 100644 --- a/doc/zh/cache.md +++ b/doc/zh/cache.md @@ -74,7 +74,7 @@ class UserService ### 清理注解缓存 -当然,如果我们数据库中的数据改变了,如果删除缓存呢?这里就需要用到后面的监听器。下面新建一个 Service 提供一方法,来帮我们处理缓存。 +当然,如果我们数据库中的数据改变了,如何删除缓存呢?这里就需要用到后面的监听器。下面新建一个 Service 提供一方法,来帮我们处理缓存。 ```php [], + // 合并到 config/autoload/annotations.php 文件 + 'scan' => [ + 'paths' => [ + __DIR__, + ], + ], + // 默认 Command 的定义,合并到 Hyperf\Contract\ConfigInterface 内,换个方式理解也就是与 config/autoload/commands.php 对应 + 'commands' => [], + // 与 commands 类似 + 'listeners' => [], + ]; + } +} +``` + +只创建一个类并不会被 Hyperf 自动的加载,您仍需在组件的 `composer.json` 添加一些定义,告诉 Hyperf 这是一个 ConfigProvider 类需要被加载,您需要在组件内的 `composer.json` 文件内增加 `extra.hyperf.config` 配置,并指定对应的 `ConfigProvider` 类的命名空间,如下所示: + +```json +{ + "name": "hyperf/foo", + "require": { + "php": ">=7.2" + }, + "autoload": { + "psr-4": { + "Hyperf\\Foo\\": "src/" + } + }, + "extra": { + "hyperf": { + "config": "Hyperf\\Foo\\ConfigProvider" + } + } +} +``` + +定义了之后需执行 `composer install` 或 `composer update` 或 `composer dump-autoload` 等会让 Composer 重新生成 `composer.lock` 文件的命令,才能被正常读取。 + +# ConfigProvider 机制的执行流程 + +关于 `ConfigProvider` 的配置并非一定就是这样去划分,这是一些约定成俗的格式,实际上最终如何来解析这些配置的决定权也在于用户,用户可通过修改 Skeleton 项目的 `config/container.php` 文件内的代码来调整相关的加载,也就意味着,`config/container.php` 文件决定了 `ConfigProvider` 的扫描和加载。 + +# 组件设计规范 + +由于 `composer.json` 内的 `extra` 属性在数据不被利用时无其它作用和影响,故这些组件内的定义在其它框架使用时,不会造成任何的干扰和影响,顾 `ConfigProvider` 是一种仅作用于 Hyperf 框架的机制,对其它没有利用此机制的框架不会造成任何的影响,这也就为组件的复用打下了基础,但这也要求在进行组件设计时,必须遵循以下规范: + +- 所有类的设计都必须允许通过标准 `OOP` 的使用方式来使用,所有 Hyperf 专有的功能必须作为增强功能并以单独的类来提供,也就意味着在非 Hyperf 框架下仍能通过标准的手段来实现组件的使用; +- 组件的依赖设计如果可满足 [PSR 标准](https://www.php-fig.org/psr) 则优先满足且依赖对应的接口而不是实现类;如 [PSR 标准](https://www.php-fig.org/psr) 没有包含的功能,则可满足由 Hyperf 定义的契约库 [Hyperf/contract](https://github.com/hyperf-cloud/contract) 内的接口时优先满足且依赖对应的接口而不是实现类; +- 对于实现 Hyperf 专有功能所增加的增强功能类,通常来说也会对 Hyperf 的一些组件有依赖,那么这些组件的依赖不应该写在 `composer.json` 的 `require` 项,而是写在 `suggust` 项作为建议项存在; +- 组件设计时不应该通过注解进行任何的依赖注入,注入方式应只使用 `构造函数注入` 的方式,这样同时也能满足在 `OOP` 下的使用; +- 组件设计时不应该通过注解进行任何的功能定义,功能定义应只通过 `ConfigProvider` 来定义; +- 类的设计时应尽可能的不储存状态数据,因为这会导致这个类不能作为长生命周期的对象来提供,也无法很方便的使用依赖注入功能,这样会在一定程度下降低性能,状态数据应都通过 `Hyperf\Utils\Context` 协程上下文来储存; diff --git a/doc/zh/controller.md b/doc/zh/controller.md index 2a59e70fd..726759eeb 100644 --- a/doc/zh/controller.md +++ b/doc/zh/controller.md @@ -37,6 +37,6 @@ Hello Hyperf. ## 避免协程间数据混淆 -在传统的 PHP-FPM 的框架里,会习惯提供一个 `AbstractController` 或其它命名的 `Controller 抽象父类`,然后定义的 `Controller` 需要基础它用于获取一些请求数据或进行一些返回操作,在 Hyperf 里是 **不能这样做** 的,因为在 Hyperf 内绝大部分的对象包括 `Controller` 都是以 `单例(Singleton)` 形式存在的,这也是为了更好的复用对象,而对于与请求相关的数据在协程下也是需要储存到 `协程上下文(Context)` 内的,所以在编写代码时请务必注意 **不要** 将单个请求相关的数据储存在类属性内,包括非静态属性。 +在传统的 PHP-FPM 的框架里,会习惯提供一个 `AbstractController` 或其它命名的 `Controller 抽象父类`,然后定义的 `Controller` 需要继承它用于获取一些请求数据或进行一些返回操作,在 Hyperf 里是 **不能这样做** 的,因为在 Hyperf 内绝大部分的对象包括 `Controller` 都是以 `单例(Singleton)` 形式存在的,这也是为了更好的复用对象,而对于与请求相关的数据在协程下也是需要储存到 `协程上下文(Context)` 内的,所以在编写代码时请务必注意 **不要** 将单个请求相关的数据储存在类属性内,包括非静态属性。 当然如果非要通过类属性来储存请求数据的话,也不是没有办法的,我们可以注意到我们获取 `请求(Request)` 与 `响应(Response)` 对象时是通过注入 `Hyperf\HttpServer\Contract\RequestInterface` 和 `Hyperf\HttpServer\Contract\ResponseInterface` 来获取的,那对应的对象不也是个单例吗?这里是如何做到协程安全的呢?就 `RequestInterface` 来举例,对应的 `Hyperf\HttpServer\Request` 对象内部在获取 `PSR-7 请求对象` 时,都是从 `协程上下文(Context)` 获取的,所以实际使用的类仅仅是一个代理类,实际调用的都是从 `协程上下文(Context)` 中获取的。 \ No newline at end of file diff --git a/doc/zh/di.md b/doc/zh/di.md index fbc4081da..ab291f81a 100644 --- a/doc/zh/di.md +++ b/doc/zh/di.md @@ -95,7 +95,7 @@ class IndexController } ``` -> 注意调用方也就是 `IndexController` 必须是由 DI 创建的对象才能完成自动注入,Controller 默认是由 DI 创建的 +> 注意调用方也就是 `IndexController` 必须是由 DI 创建的对象才能完成自动注入,Controller 默认是由 DI 创建的,直接 `new` 该对象不会生效; > 使用 `@Inject` 注解时需 `use Hyperf\Di\Annotation\Inject;` 命名空间; diff --git a/doc/zh/exception-handler.md b/doc/zh/exception-handler.md index 86db76929..d7efc3ccd 100644 --- a/doc/zh/exception-handler.md +++ b/doc/zh/exception-handler.md @@ -1,7 +1,7 @@ # 异常处理器 -在 `Hyperf` 里,业务代码都运行在 `Worker进程` 上,也就意味着一旦任意一个请求的业务存在没有捕获处理的异常的话,都会导致对应的 `Worker进程` 被中断退出,虽然被中断的 `Worker进程` 仍会被重新拉起,但对服务而已也是不能接受的,且捕获异常并输出合理的报错内容给客户端也是更加友好的。 -我们可以通过对各个 `server` 定义不同的 `异常处理器(ExceptionHandler)`,一旦业务流程存在没有捕获的异常,到会被传递到已注册的 `异常处理器(ExceptionHandler)` 去处理。 +在 `Hyperf` 里,业务代码都运行在 `Worker进程` 上,也就意味着一旦任意一个请求的业务存在没有捕获处理的异常的话,都会导致对应的 `Worker进程` 被中断退出,虽然被中断的 `Worker进程` 仍会被重新拉起,但对服务而言也是不能接受的,且捕获异常并输出合理的报错内容给客户端也是更加友好的。 +我们可以通过对各个 `server` 定义不同的 `异常处理器(ExceptionHandler)`,一旦业务流程存在没有捕获的异常,都会被传递到已注册的 `异常处理器(ExceptionHandler)` 去处理。 ## 自定义一个异常处理 @@ -57,7 +57,7 @@ class FooExceptionHandler extends ExceptionHandler } // 交给下一个异常处理器 - return $respose; + return $response; // 或者不做处理直接屏蔽异常 } diff --git a/doc/zh/imgs/snowflake.jpeg b/doc/zh/imgs/snowflake.jpeg new file mode 100644 index 000000000..b56b9868b Binary files /dev/null and b/doc/zh/imgs/snowflake.jpeg differ diff --git a/doc/zh/logger.md b/doc/zh/logger.md index a48764802..a62fe5171 100644 --- a/doc/zh/logger.md +++ b/doc/zh/logger.md @@ -1,6 +1,6 @@ # 日志 -`hyperf/logger` 组件是基于 [psr/logger](https://github.com/php-fig/logger) 实现的,默认使用 [monolog/monolog](https://github.com/Seldaek/monolog) 作为驱动,在 `hyperf-skeleton` 项目内默认提供了一些日志配置,默认使用 `Monolog\Handler\StreamHandler`, 由于 `Swoole` 已经对 `fopen`, `fwrite` 等函数进行了协程化处理,所以只要不将 `useLocking` 参数设置为 `true`,就是协程安全的。 +`hyperf/logger` 组件是基于 [psr/logger](https://github.com/php-fig/log) 实现的,默认使用 [monolog/monolog](https://github.com/Seldaek/monolog) 作为驱动,在 `hyperf-skeleton` 项目内默认提供了一些日志配置,默认使用 `Monolog\Handler\StreamHandler`, 由于 `Swoole` 已经对 `fopen`, `fwrite` 等函数进行了协程化处理,所以只要不将 `useLocking` 参数设置为 `true`,就是协程安全的。 ## 安装 @@ -58,8 +58,8 @@ class DemoService public function __construct(LoggerFactory $loggerFactory) { - // default 对应 config/autoload/logger.php 内的 key - $this->logger = $loggerFactory->get('default'); + // 第一个参数对应日志的 name, 第二个参数对应 config/autoload/logger.php 内的 key + $this->logger = $loggerFactory->get('log', 'default'); } public function method() diff --git a/doc/zh/microservice.md b/doc/zh/microservice.md index d8fa0ee94..787dd6f3f 100644 --- a/doc/zh/microservice.md +++ b/doc/zh/microservice.md @@ -52,4 +52,4 @@ 虽然 `微服务架构(Microservice)` 好处众多,但 **微服务不是银弹 !!!** ,您需要面对所有分布式系统都需要面对的复杂性,你可能需要在部署、测试和监控上做很多的工作,在服务间调用、服务的可靠性上做很多工作,甚至您还需要处理类似于 分布式事务 或者与 CAP 相关的问题。尽管 `Hyperf` 已经为您解决了许多的问题,但在实施 `微服务架构(Microservice)` 之前您的团队必须储备足够的分布式系统相关的知识体系,以面对很多您在 `单体架构(Monolithic architecture)` 下可能没有面临过甚至没有考虑过的问题。 -*| 本章节部分内容译自 Sam Newman 的 《Building Microservices 》* \ No newline at end of file +*| 本章节部分内容译自 Sam Newman 的 《Building Microservices》* diff --git a/doc/zh/middleware/middleware.md b/doc/zh/middleware/middleware.md index 18ed5dd2d..0b124a168 100644 --- a/doc/zh/middleware/middleware.md +++ b/doc/zh/middleware/middleware.md @@ -120,7 +120,7 @@ class IndexController #### 定义方法级别的中间件 在通过配置文件的方式配置中间件时定义到方法级别上很简单,那么要通过注解的形式定义到方法级别呢?您只需将注解直接定义到方法上即可。 -方法级别上的中间件会优先于类级别的中间件,我们通过代码来举例一下: +类级别上的中间件会优先于方法级别的中间件,我们通过代码来举例一下: ```php 方法级别中间件 -> 类级别中间件`。 +我们从上面可以了解到总共有 `3` 种级别的中间件,分别为 `全局中间件`、`类级别中间件`、`方法级别中间件`,如果都定义了这些中间件,执行顺序为:`全局中间件 -> 类级别中间件 -> 方法级别中间件`。 ## 全局更改请求和响应对象 diff --git a/doc/zh/paginator.md b/doc/zh/paginator.md index a55f51708..77b99d956 100644 --- a/doc/zh/paginator.md +++ b/doc/zh/paginator.md @@ -81,7 +81,7 @@ if ($paginator->hasMorePages()) { } ``` -## 获取上一页和下一页的 URL +## 获取对应分页的 URL ```php hasMorePages()) { $nextPageUrl = $paginator->nextPageUrl(); // 上一页的 URL $previousPageUrl = $paginator->previousPageUrl(); +// 获取指定 $page 页数的 URL +$url = $paginator->url($page); ``` -## 获取指定页数的 URL +## 是否处于第一页 ```php url($page); -``` \ No newline at end of file +$onFirstPage = $paginator->onFirstPage(); +``` + +## 是否有更多分页 + +```php +hasMorePages(); +``` + +## 每页的数据条数 + +```php +perPage(); +``` + +## 数据总数 + +```php +total(); +``` diff --git a/doc/zh/quick-start/overview.md b/doc/zh/quick-start/overview.md index e1d5df50f..5d9565d0a 100644 --- a/doc/zh/quick-start/overview.md +++ b/doc/zh/quick-start/overview.md @@ -222,10 +222,11 @@ class IndexController ## 启动 Hyperf 服务 由于 `Hyperf` 内置了协程服务器,也就意味着 `Hyperf` 将以 `CLI` 的形式去运行,所以在定义好路由及实际的逻辑代码之后,我们需要在项目根目录并通过命令行运行 `php bin/hyperf.php start` 来启动服务。 -当 `Console` 界面显示服务启动后便可通过 `cURL` 或 浏览器对服务正常发起访问了,默认情况下上面的例子是访问 `http://127.0.0.1:9501/index/info?id=1`。 +当 `Console` 界面显示服务启动后便可通过 `cURL` 或 浏览器对服务正常发起访问了,默认服务会提供一个首页 `http://127.0.0.1:9501/`,对于本章示例引导的情况下,也就是上面的例子所对应的访问地址为 `http://127.0.0.1:9501/index/info?id=1`。 ## 重新加载代码 由于 `Hyperf` 是持久化的 `CLI` 应用,也就意味着一旦进程启动,已解析的 `PHP` 代码会持久化在进程中,也就意味着启动服务后您再修改的 `PHP` 代码不会改变已启动的服务,如您希望服务重新加载您修改后的代码,您需要通过在启动的 `Console` 中键入 `CTRL + C` 终止服务,再重新执行启动命令完成重启和重新加载。 > Tips: 您也可以将启动 Server 的命令配置在 IDE 上,便可直接通过 IDE 的 `启动/停止` 操作快捷的完成 `启动服务` 或 `重启服务` 的操作。 +> 且非视图开发时可以采用 [TDD(Test-Driven Development)](https://baike.baidu.com/item/TDD/9064369) 测试驱动开发来进行开发,这样不仅可以省略掉服务重启和频繁切换窗口的麻烦,还可保证接口数据的正确性。 diff --git a/doc/zh/quick-start/questions.md b/doc/zh/quick-start/questions.md index 789c0e020..32f992503 100644 --- a/doc/zh/quick-start/questions.md +++ b/doc/zh/quick-start/questions.md @@ -28,17 +28,22 @@ swoole.use_shortname = 'Off' runtime/container/proxy/ ``` -清理命令 -``` +重新生成缓存命令,新缓存会覆盖原目录 +```bash php bin/hyperf.php di:init-proxy ``` -所以单测命令可以使用以下代替 +删除代理类缓存 +```bash +rm -rf ./runtime/container/proxy ``` + +所以单测命令可以使用以下代替: +```bash php bin/hyperf.php di:init-proxy && composer test ``` 同理,启动命令可以使用以下代替 -``` +```bash php bin/hyperf.php di:init-proxy && php bin/hyperf.php start ``` diff --git a/doc/zh/rate-limit.md b/doc/zh/rate-limit.md index abce56c33..3584dd133 100644 --- a/doc/zh/rate-limit.md +++ b/doc/zh/rate-limit.md @@ -86,7 +86,7 @@ use Hyperf\RateLimit\Annotation\RateLimit; /** * @Controller(prefix="rate-limit") - * @RateLimit(limitCallback={RateLimitController::class, 'limitCallback'}) + * @RateLimit(limitCallback={RateLimitController::class, "limitCallback"}) */ class RateLimitController { @@ -107,4 +107,4 @@ class RateLimitController return $proceedingJoinPoint->process(); } } -``` \ No newline at end of file +``` diff --git a/doc/zh/redis.md b/doc/zh/redis.md index c4b3e1e2a..a52282bb1 100644 --- a/doc/zh/redis.md +++ b/doc/zh/redis.md @@ -115,7 +115,7 @@ $result = $redis->keys('*'); ### 使用工厂类 -在每个库对应一个静态的场景时,通过代理类是一种很好的区分的方法,但有时候需求可能会更加的动态,这时候我们可以通过 `Hyperf\Redis\RedisFactory` 工厂类来动态的传递 `poolName` 来获得对应的连接池的客户端,而无需为每个库创建代理类,示例如下: +在每个库对应一个固定的使用场景时,通过代理类是一种很好的区分的方法,但有时候需求可能会更加的动态,这时候我们可以通过 `Hyperf\Redis\RedisFactory` 工厂类来动态的传递 `poolName` 来获得对应的连接池的客户端,而无需为每个库创建代理类,示例如下: ```php generate($userId); -``` \ No newline at end of file +``` diff --git a/doc/zh/summary.md b/doc/zh/summary.md index 64d13416e..36f4b7f37 100644 --- a/doc/zh/summary.md +++ b/doc/zh/summary.md @@ -35,6 +35,7 @@ * [命令行](zh/command.md) * [自动化测试](zh/testing.md) * [视图](zh/view.md) + * [国际化](zh/translation.md) * 数据库模型 @@ -74,10 +75,11 @@ * [开发者工具](zh/devtool.md) * [辅助类](zh/utils.md) * [限流器](zh/rate-limit.md) - * [Swoole Enterprise](zh/swoole-enterprise.md) + * [Swoole Tracker](zh/swoole-tracker.md) * [定时任务](zh/crontab.md) * [Task 机制](zh/task.md) * [枚举类](zh/constants.md) + * [Snowflake](zh/snowflake.md) * 应用部署 @@ -85,6 +87,7 @@ * [DaoCloud Devops 搭建](zh/tutorial/daocloud.md) * [Supervisor 部署](zh/tutorial/supervisor.md) * [Nginx 反向代理](zh/tutorial/nginx.md) + * [阿里云日志服务](zh/tutorial/aliyun-logger.md) * Awesome Hyperf @@ -94,4 +97,4 @@ * [指南前言](zh/component-guide/intro.md) * [创建新的组件](zh/component-guide/create.md) - * Hyperf 框架流程介入 + * [ConfigProvider 机制](zh/component-guide/configprovider.md) diff --git a/doc/zh/swoole-enterprise.md b/doc/zh/swoole-tracker.md similarity index 71% rename from doc/zh/swoole-enterprise.md rename to doc/zh/swoole-tracker.md index 3757b73fc..190b4a916 100644 --- a/doc/zh/swoole-enterprise.md +++ b/doc/zh/swoole-tracker.md @@ -1,6 +1,6 @@ -# Swoole Enterprise +# Swoole Tracker -[Swoole Enterprise](https://www.swoole-cloud.com/dashboard.html) 作为 `Swoole` 官方出品的一整套企业级`PHP`和`Swoole`分析调试工具,更专一、更专业。 +[Swoole Tracker](https://www.swoole-cloud.com/tracker.html) 作为 `Swoole` 官方出品的一整套企业级 `PHP` 和 `Swoole`分析调试工具,更专一、更专业。(曾命名:Swoole Enterprise) - 时刻掌握应用架构模型 > 自动发现应用依赖拓扑结构和展示,时刻掌握应用的架构模型 @@ -21,7 +21,7 @@ 注册完账户后,进入[控制台](https://www.swoole-cloud.com/dashboard/catdemo/),并申请试用,下载对应客户端。 -相关文档,请移步 [试用文档](https://www.yuque.com/swoole-wiki/try) 或 [详细文档](https://www.yuque.com/swoole-wiki/dam5n7) +相关文档,请移步 [试用文档](https://www.kancloud.cn/swoole-inc/ee-base-wiki/1214079) 或 [详细文档](https://www.kancloud.cn/swoole-inc/ee-help-wiki/1213080) > 具体文档地址,以从控制台下载的对应客户端中展示的为准。 @@ -38,11 +38,11 @@ php /opt/www/bin/hyperf.php start ``` -2. `swoole-plus.ini` +2. `swoole-tracker.ini` ```bash -[swoole_plus] -extension=/opt/swoole_plus.so +[swoole_tracker] +extension=/opt/swoole_tracker.so apm.enable=1 #打开总开关 apm.sampling_rate=100 #采样率 例如:100% @@ -98,8 +98,8 @@ WORKDIR /opt/www/.build # 这里的地址,以客户端中显示的为准 RUN ./deploy_env.sh www.swoole-cloud.com \ && chmod 755 entrypoint.sh \ - && cp swoole_plus72.so /opt/swoole_plus.so \ - && cp swoole-plus.ini /etc/php7/conf.d/swoole-plus.ini \ + && cp swoole_tracker72.so /opt/swoole_tracker.so \ + && cp swoole-tracker.ini /etc/php7/conf.d/swoole-tracker.ini \ && php -m WORKDIR /opt/www @@ -113,23 +113,34 @@ EXPOSE 9501 ENTRYPOINT ["sh", ".build/entrypoint.sh"] ``` -### 安装组件 - -```bash -composer require hyperf/swoole-enterprise dev-master -``` - ## 使用 -在 `config/autoload/middlewares.php` 配置文件中注册 `Hyperf\SwooleEnterprise\Middleware\HttpServerMiddleware` 中间件即可,如下: +### 不依赖组件 + +`Swoole Tracker` 的 `v2.5.0` 以上版本支持自动生成应用名称并创建应用,无需修改任何代码。 + +如果使用 `Swoole` 的 `HttpServer` 那么生成的应用名称为`ip:port` + +如果使用 `Swoole` 其他的 `Server` 那么生成的应用名称为`ip(hostname):port` + +即安装好 `swoole_tracker` 扩展之后就可以正常使用 `Swoole Tracker` 的功能 + +### 依赖组件 + +当你需要自定义应用名称时则需要安装组件,使用 `Composer` 安装: + +```bash +composer require hyperf/swoole-tracker +``` + +安装完成后在 `config/autoload/middlewares.php` 配置文件中注册 `Hyperf\SwooleTracker\Middleware\HttpServerMiddleware` 中间件即可,如下: ```php [ - Hyperf\SwooleEnterprise\Middleware\HttpServerMiddleware::class + Hyperf\SwooleTracker\Middleware\HttpServerMiddleware::class ], ]; ``` - diff --git a/doc/zh/task.md b/doc/zh/task.md index 34f9b86df..b0fb58744 100644 --- a/doc/zh/task.md +++ b/doc/zh/task.md @@ -105,9 +105,9 @@ $result = $task->handle(Coroutine::id()); Swoole 暂时没有协程化的函数列表 -- mysql:底层使用 libmysqlclient -- curl:底层使用 libcurl(即不能使用CURL驱动的Guzzle) -- mongo:底层使用 mongo-c-client +- mysql,底层使用 libmysqlclient +- curl,底层使用 libcurl,在 Swoole 4.4 后底层进行了协程化(beta) +- mongo,底层使用 mongo-c-client - pdo_pgsql - pdo_ori - pdo_odbc @@ -143,10 +143,8 @@ class MongoTask /** * @Task - * @param string $namespace - * @param array $document */ - public function insert($namespace, $document) + public function insert(string $namespace, array $document) { $writeConcern = new WriteConcern(WriteConcern::MAJORITY, 1000); $bulk = new BulkWrite(); @@ -158,11 +156,8 @@ class MongoTask /** * @Task - * @param string $namespace - * @param array $filter - * @param array $options */ - public function query($namespace, $filter = [], $options = []) + public function query(string $namespace, array $filter = [], array $options = []) { $query = new Query($filter, $options); $cursor = $this->manager()->executeQuery($namespace, $query); diff --git a/doc/zh/tutorial/aliyun-logger.md b/doc/zh/tutorial/aliyun-logger.md new file mode 100644 index 000000000..da362969e --- /dev/null +++ b/doc/zh/tutorial/aliyun-logger.md @@ -0,0 +1,127 @@ +# 阿里云日志服务 + +在 `Docker集群` 部署项目时,收集日志会是一个比较麻烦的问题,但阿里云提供了十分好用的 `日志收集系统`,本篇文档就是简略介绍一下阿里云日志收集的使用方法。 + +* [Docker Swarm 集群搭建](zh/tutorial/docker-swarm.md) + +## 开通日志服务 + +首先第一步便是在阿里云上开通 `日志服务`。 + +[日志服务文档](https://help.aliyun.com/product/28958.html) + +以下的教程是一个顺序的操作方式,一步一步讲述如何使用日志服务。 + +## 安装 Logtail 容器 + +[标准 Docker 日志采集流程文档](https://help.aliyun.com/document_detail/66659.html) + +| 参数 | 说明 | +|:-------------------------------------:|:------------------------------------------:| +| ${your_region_name} | 区域ID 比如华东1区域是 cn-hangzhou | +| ${your_aliyun_user_id} | 用户标识,请替换为您的阿里云主账号用户ID。 | +| ${your_machine_group_user_defined_id} | 集群的机器组自定义标识 以下使用 Hyperf | + +``` +docker run -d -v /:/logtail_host:ro -v /var/run/docker.sock:/var/run/docker.sock \ +--env ALIYUN_LOGTAIL_CONFIG=/etc/ilogtail/conf/${your_region_name}/ilogtail_config.json \ +--env ALIYUN_LOGTAIL_USER_ID=${your_aliyun_user_id} \ +--env ALIYUN_LOGTAIL_USER_DEFINED_ID=${your_machine_group_user_defined_id} \ +registry.cn-hangzhou.aliyuncs.com/log-service/logtail +``` + +## 配置日志收集 + +### 创建 Project + +登录阿里云日志服务,点击 `创建 Project`,填写以下信息 + +| 参数 | 填写示例 | +|:------------:|:----------------:| +| Project名称 | hyperf | +| 注释 | 用于日志系统演示 | +| 所属区域 | 华东1(杭州) | +| 开通服务 | 详细日志 | +| 日志存储位置 | 当前Project | + +### 创建 Logstore + +除以下参数,按需填写,其他都使用默认即可 + +| 参数 | 填写示例 | +|:------------:|:---------------:| +| Logstore名称 | hyperf-demo-api | +| 永久保存 | false | +| 数据保存时间 | 60 | + +### 接入数据 + +1. 选择 Docker 文件 + +2. 创建机器组 + +如果已经创建过机器组,可以跳过这一步 + +| 参数 | 填写示例 | +|:-------------:|:-------------:| +| 机器组名称 | Hyperf | +| 机器组标识 | 用户自定义标识 | +| 用户自定义标识 | Hyperf | + +3. 配置机器组 + +应用刚刚创建的机器组 + +4. 配置 Logtail + +`Label` 白名单,这里可以按需填写,以下按照项目名字来配置,而项目名会在 Docker 容器运行时设置。 + +| 参数 | 填写示例 | 填写示例 | +|:--------------:|:-------------------------------------------------:|:---------------:| +| 配置名称 | hyperf-demo-api | | +| 日志路径 | /opt/www/runtime/logs | *.log | +| Label白名单 | app.name | hyperf-demo-api | +| 模式 | 完整正则模式 | | +| 单行模式 | false | | +| 日志样例 | [2019-03-07 11:58:57] hyperf.WARNING: xxx | | +| 首行正则表达式 | \[\d+-\d+-\d+\s\d+:\d+:\d+\]\s.* | | +| 提取字段 | true | | +| 正则表达式 | \[(\d+-\d+-\d+\s\d+:\d+:\d+)\]\s(\w+)\.(\w+):(.*) | | +| 日志抽取内容 | time name level content | | + +5. 查询分析配置 + +字段索引属性 + +| 字段名称 | 类型 | 别名 | 中文分词 | 开启统计 | +|:--------:|:----:|:-------:|:--------:|:--------:| +| name | text | name | false | true | +| level | text | level | false | true | +| time | text | time | false | false | +| content | text | content | true | false | + +### 运行镜像 + +运行镜像时,只需要设置 Container `labels` 即可。 + +| name | value | +|:--------:|:---------------:| +| app.name | hyperf-demo-api | + +比如以下 Dockerfile + +```Dockerfile +# Default Dockerfile + +FROM hyperf/hyperf:7.2-alpine-cli +LABEL maintainer="Hyperf Developers " version="1.0" license="MIT" app.name="hyperf-demo-api" + +# 其它内容省略 +``` + +## 注意事项 + +- Docker 存储驱动限制:目前只支持 `overlay`、`overlay2`,其他存储驱动需将日志所在目录 `mount` 到本地,然后改为收集宿主机 `~/logtail_host/your_path` 下的日志即可。 + + + diff --git a/doc/zh/tutorial/docker-swarm.md b/doc/zh/tutorial/docker-swarm.md index 5ac9157ac..a441b93e5 100644 --- a/doc/zh/tutorial/docker-swarm.md +++ b/doc/zh/tutorial/docker-swarm.md @@ -8,6 +8,12 @@ curl -sSL https://get.daocloud.io/docker | sh ``` +修改文件 `/lib/systemd/system/docker.service`,允许使用 `TCP` 连接 `Docker` + +``` +ExecStart=/usr/bin/dockerd -H unix:// -H tcp://0.0.0.0:2375 +``` + ## 搭建自己的Gitlab ### 安装Gitlab @@ -100,6 +106,7 @@ docker network create \ --driver overlay \ --subnet 10.0.0.0/24 \ --opt encrypted \ +--attachable \ default-network ``` @@ -120,6 +127,14 @@ $ docker swarm join --token ip:2377 > 其他与 builder 一致,但是 tag 却不能一样。线上环境可以设置为 tags,测试环境设置为 test +## 安装其他应用 + +以下以 `Mysql` 为例,直接使用上述 `network`,支持容器内使用 name 互调。 + +``` +docker run --name mysql -v /srv/mysql:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=xxxx -p 3306:3306 --rm --network default-network -d mysql:5.7 +``` + ## 安装 Portainer [Portainer](https://github.com/portainer/portainer) @@ -226,6 +241,85 @@ REDIS_DB=0 curl http://127.0.0.1:9501/ ``` +## 安装 KONG 网关 + +通常情况下,Swarm集群是不会直接对外的,所以我们这里推荐使用 `KONG` 作为网关。 +还有另外一个原因,那就是 `Swarm` 的 `Ingress网络` 设计上有缺陷,所以在连接不复用的情况下,会有并发瓶颈,具体请查看对应 `Issue` [#35082](https://github.com/moby/moby/issues/35082) +而 `KONG` 作为网关,默认情况下就会复用后端的连接,所以会极大减缓上述问题。 + +### 安装数据库 + +``` +docker run -d --name kong-database \ + --network=default-network \ + -p 5432:5432 \ + -e "POSTGRES_USER=kong" \ + -e "POSTGRES_DB=kong" \ + postgres:9.6 +``` + +### 安装网关 + +初始化数据库 + +``` +docker run --rm \ + --network=default-network \ + -e "KONG_DATABASE=postgres" \ + -e "KONG_PG_HOST=kong-database" \ + -e "KONG_CASSANDRA_CONTACT_POINTS=kong-database" \ + kong:latest kong migrations bootstrap +``` + +启动 + +``` +docker run -d --name kong \ + --network=default-network \ + -e "KONG_DATABASE=postgres" \ + -e "KONG_PG_HOST=kong-database" \ + -e "KONG_CASSANDRA_CONTACT_POINTS=kong-database" \ + -e "KONG_PROXY_ACCESS_LOG=/dev/stdout" \ + -e "KONG_ADMIN_ACCESS_LOG=/dev/stdout" \ + -e "KONG_PROXY_ERROR_LOG=/dev/stderr" \ + -e "KONG_ADMIN_ERROR_LOG=/dev/stderr" \ + -e "KONG_ADMIN_LISTEN=0.0.0.0:8001, 0.0.0.0:8444 ssl" \ + -p 8000:8000 \ + -p 8443:8443 \ + -p 8001:8001 \ + -p 8444:8444 \ + kong:latest +``` + +### 安装 KONG Dashboard + +> 暂时 `Docker` 中没有更新 `v3.6.0` 所以最新版的 `KONG` 可能无法使用 + +``` +docker run --rm --network=default-network -p 8080:8080 -d --name kong-dashboard pgbi/kong-dashboard start \ + --kong-url http://kong:8001 \ + --basic-auth user1=password1 user2=password2 +``` + +### 配置 + +接下来只需要把部署 `KONG` 的机器 `IP` 对外,然后配置 `Service` 即可。 +如果机器直接对外,最好只开放 `80` `443` 端口,然后把 `Kong` 容器的 `8000` 和 `8443` 映射到 `80` 和 `443` 上。 +当然,如果使用了 `SLB` 等负载均衡,就直接通过负载均衡,把 `80` 和 `443` 映射到 `KONG` 所在几台机器的 `8000` `8443` 上。 + +## 如何使用 Linux Crontab + +`Hyperf` 虽然提供了 `crontab` 组件,但是不一定可以满足所有人的需求,这里提供一个 `Linux` 使用的脚本,执行 `Docker` 内的 `Command`。 + +```bash +#!/usr/bin/env bash +basepath=$(cd `dirname $0`; pwd) +docker pull registry-vpc.cn-shanghai.aliyuncs.com/namespace/project:latest +docker run --rm -i -v $basepath/.env:/opt/www/.env \ +--entrypoint php registry-vpc.cn-shanghai.aliyuncs.com/namespace/project:latest \ +/opt/www/bin/hyperf.php your_command +``` + ## 意外情况 ### fatal: git fetch-pack: expected shallow list diff --git a/doc/zh/view.md b/doc/zh/view.md index 19f1439b6..c5129a0d4 100644 --- a/doc/zh/view.md +++ b/doc/zh/view.md @@ -12,12 +12,12 @@ composer require hyperf/view View 组件的配置文件位于 `config/autoload/view.php`,若配置文件不存在可自行创建,以下为相关配置的说明: -| 配置 | 类型 | 默认值 | 备注 | -|:-----------------:|:------:|:--------------------------------------:|:----------------:| +| 配置 | 类型 | 默认值 | 备注 | +|:-----------------:|:------:|:-------------------------------------:|:----------------:| | engine | string | Hyperf\View\Engine\BladeEngine::class | 视图渲染引擎 | -| mode | string | Mode::TASK | 视图渲染模式 | -| config.view_path | string | 无 | 视图文件默认地址 | -| config.cache_path | string | 无 | 视图文件缓存地址 | +| mode | string | Mode::TASK | 视图渲染模式 | +| config.view_path | string | 无 | 视图文件默认地址 | +| config.cache_path | string | 无 | 视图文件缓存地址 | 配置文件格式示例: diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 000000000..23633bb5c --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,92 @@ +# Magic behaviour with __get, __set, __call and __callStatic is not exactly static analyser-friendly :) +# Fortunately, You can ingore it by the following config. +# +rules: + - PHPStan\Rules\Arrays\DeadForeachRule + - PHPStan\Rules\Comparison\BooleanOrConstantConditionRule + - PHPStan\Rules\Comparison\ElseIfConstantConditionRule + - PHPStan\Rules\Comparison\IfConstantConditionRule + - PHPStan\Rules\Comparison\TernaryOperatorConstantConditionRule + +parameters: + bootstrap: "bootstrap.php" + checkFunctionArgumentTypes: true + checkArgumentsPassedByReference: true + featureToggles: + deadCatchesRule: false + noopRule: false + tooWideTypehints: false + unreachableStatement: false + ignoreErrors: + - "#will always evaluate to false#" + excludes_analyse: + - %currentWorkingDirectory%/src/*/tests/* + +conditionalTags: + PHPStan\Rules\Exceptions\DeadCatchRule: + phpstan.rules.rule: %featureToggles.deadCatchesRule% + PHPStan\Rules\DeadCode\NoopRule: + phpstan.rules.rule: %featureToggles.noopRule% + PHPStan\Rules\DeadCode\UnreachableStatementRule: + phpstan.rules.rule: %featureToggles.unreachableStatement% + PHPStan\Rules\TooWideTypehints\TooWideClosureReturnTypehintRule: + phpstan.rules.rule: %featureToggles.tooWideTypehints% + PHPStan\Rules\TooWideTypehints\TooWideFunctionReturnTypehintRule: + phpstan.rules.rule: %featureToggles.tooWideTypehints% + PHPStan\Rules\TooWideTypehints\TooWidePrivateMethodReturnTypehintRule: + phpstan.rules.rule: %featureToggles.tooWideTypehints% + +services: + - + class: PHPStan\Rules\Classes\ImpossibleInstanceOfRule + arguments: + checkAlwaysTrueInstanceof: %checkAlwaysTrueInstanceof% + tags: + - phpstan.rules.rule + + - + class: PHPStan\Rules\Comparison\ImpossibleCheckTypeFunctionCallRule + arguments: + checkAlwaysTrueCheckTypeFunctionCall: %checkAlwaysTrueCheckTypeFunctionCall% + tags: + - phpstan.rules.rule + + - + class: PHPStan\Rules\Comparison\ImpossibleCheckTypeMethodCallRule + arguments: + checkAlwaysTrueCheckTypeFunctionCall: %checkAlwaysTrueCheckTypeFunctionCall% + tags: + - phpstan.rules.rule + + - + class: PHPStan\Rules\Comparison\ImpossibleCheckTypeStaticMethodCallRule + arguments: + checkAlwaysTrueCheckTypeFunctionCall: %checkAlwaysTrueCheckTypeFunctionCall% + tags: + - phpstan.rules.rule + + - + class: PHPStan\Rules\Comparison\StrictComparisonOfDifferentTypesRule + arguments: + checkAlwaysTrueStrictComparison: %checkAlwaysTrueStrictComparison% + tags: + - phpstan.rules.rule + + - + class: PHPStan\Rules\Exceptions\DeadCatchRule + + - + class: PHPStan\Rules\DeadCode\NoopRule + + - + class: PHPStan\Rules\DeadCode\UnreachableStatementRule + + - + class: PHPStan\Rules\TooWideTypehints\TooWideClosureReturnTypehintRule + + - + class: PHPStan\Rules\TooWideTypehints\TooWideFunctionReturnTypehintRule + + - + class: PHPStan\Rules\TooWideTypehints\TooWidePrivateMethodReturnTypehintRule + diff --git a/phpunit.xml b/phpunit.xml index b87b25ea4..6cb629664 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -31,12 +31,14 @@ ./src/logger/tests ./src/model-cache/tests ./src/paginator/tests + ./src/pool/tests ./src/redis/tests ./src/rpc/tests ./src/server/tests ./src/service-governance/tests ./src/snowflake/tests ./src/task/tests + ./src/testing/tests ./src/translation/tests ./src/utils/tests ./src/websocket-client/tests diff --git a/src/amqp/composer.json b/src/amqp/composer.json index e6a258e79..82324eaf7 100644 --- a/src/amqp/composer.json +++ b/src/amqp/composer.json @@ -11,10 +11,10 @@ }, "require": { "php": ">=7.2", - "hyperf/contract": "~1.0.0", - "hyperf/utils": "~1.0.0", - "hyperf/process": "~1.0.0", - "hyperf/pool": "~1.0.0", + "hyperf/contract": "~1.1.0", + "hyperf/utils": "~1.1.0", + "hyperf/process": "~1.1.0", + "hyperf/pool": "~1.1.0", "psr/container": "^1.0", "psr/event-dispatcher": "^1.0", "psr/log": "^1.0", @@ -22,9 +22,9 @@ "doctrine/instantiator": "^1.2.0" }, "require-dev": { - "hyperf/di": "~1.0.0", - "hyperf/event": "~1.0.0", - "hyperf/framework": "~1.0.0", + "hyperf/di": "~1.1.0", + "hyperf/event": "~1.1.0", + "hyperf/framework": "~1.1.0", "malukenho/docheader": "^0.1.6", "mockery/mockery": "^1.0", "phpunit/phpunit": "^7.0.0", @@ -49,7 +49,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.1-dev" }, "hyperf": { "config": "Hyperf\\Amqp\\ConfigProvider" diff --git a/src/async-queue/composer.json b/src/async-queue/composer.json index c2acd9bb9..f2401b6ab 100644 --- a/src/async-queue/composer.json +++ b/src/async-queue/composer.json @@ -13,12 +13,12 @@ "php": ">=7.2", "psr/container": "^1.0", "psr/event-dispatcher": "^1.0", - "hyperf/contract": "~1.0.0", - "hyperf/command": "~1.0.0", - "hyperf/utils": "~1.0.0" + "hyperf/contract": "~1.1.0", + "hyperf/command": "~1.1.0", + "hyperf/utils": "~1.1.0" }, "require-dev": { - "hyperf/process": "~1.0.0", + "hyperf/process": "~1.1.0", "malukenho/docheader": "^0.1.6", "mockery/mockery": "^1.0", "phpunit/phpunit": "^7.0.0", @@ -43,7 +43,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.1-dev" }, "hyperf": { "config": "Hyperf\\AsyncQueue\\ConfigProvider" diff --git a/src/async-queue/src/Annotation/AsyncQueueMessage.php b/src/async-queue/src/Annotation/AsyncQueueMessage.php new file mode 100644 index 000000000..681c0f293 --- /dev/null +++ b/src/async-queue/src/Annotation/AsyncQueueMessage.php @@ -0,0 +1,32 @@ +class = $class; + $this->method = $method; + foreach ($params as $key => $value) { + if ($value instanceof CompressInterface) { + $value = $value->compress(); + } + $this->params[$key] = $value; + } + } + + public function handle() + { + $container = ApplicationContext::getContainer(); + + $class = $container->get($this->class); + + $params = []; + foreach ($this->params as $key => $value) { + if ($value instanceof UnCompressInterface) { + $value = $value->uncompress(); + } + $params[$key] = $value; + } + $class->{$this->method}(...$params); + } +} diff --git a/src/async-queue/src/Aspect/AsyncQueueAspect.php b/src/async-queue/src/Aspect/AsyncQueueAspect.php new file mode 100644 index 000000000..2f4137414 --- /dev/null +++ b/src/async-queue/src/Aspect/AsyncQueueAspect.php @@ -0,0 +1,70 @@ +container = $container; + } + + public function process(ProceedingJoinPoint $proceedingJoinPoint) + { + $env = $this->container->get(Environment::class); + if ($env->isAsyncQueue()) { + $proceedingJoinPoint->process(); + return; + } + + $class = $proceedingJoinPoint->className; + $method = $proceedingJoinPoint->methodName; + $arguments = $proceedingJoinPoint->getArguments(); + $pool = 'default'; + $delay = 0; + + $metadata = $proceedingJoinPoint->getAnnotationMetadata(); + /** @var AsyncQueueMessage $annotation */ + $annotation = $metadata->method[AsyncQueueMessage::class] ?? $metadata->class[AsyncQueueMessage::class] ?? null; + if ($annotation instanceof AsyncQueueMessage) { + $pool = $annotation->pool; + $delay = $annotation->delay; + } + + $factory = $this->container->get(DriverFactory::class); + $driver = $factory->get($pool); + + $driver->push(new AnnotationJob($class, $method, $arguments), $delay); + } +} diff --git a/src/async-queue/src/Driver/Driver.php b/src/async-queue/src/Driver/Driver.php index 0f32b46c9..bbccffc11 100644 --- a/src/async-queue/src/Driver/Driver.php +++ b/src/async-queue/src/Driver/Driver.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Hyperf\AsyncQueue\Driver; +use Hyperf\AsyncQueue\Environment; use Hyperf\AsyncQueue\Event\AfterHandle; use Hyperf\AsyncQueue\Event\BeforeHandle; use Hyperf\AsyncQueue\Event\FailedHandle; @@ -53,6 +54,8 @@ abstract class Driver implements DriverInterface public function consume(): void { + $this->container->get(Environment::class)->setAsyncQueue(true); + while (true) { [$data, $message] = $this->pop(); diff --git a/src/async-queue/src/Driver/DriverInterface.php b/src/async-queue/src/Driver/DriverInterface.php index 70647c144..d309f64ed 100644 --- a/src/async-queue/src/Driver/DriverInterface.php +++ b/src/async-queue/src/Driver/DriverInterface.php @@ -21,12 +21,6 @@ interface DriverInterface */ public function push(JobInterface $job, int $delay = 0): bool; - /** - * Push a delay job to queue. - * @deprecated v1.1 - */ - public function delay(JobInterface $job, int $delay = 0): bool; - /** * Delete a delay job to queue. */ diff --git a/src/async-queue/src/Driver/RedisDriver.php b/src/async-queue/src/Driver/RedisDriver.php index 7d324d172..f63de48e0 100644 --- a/src/async-queue/src/Driver/RedisDriver.php +++ b/src/async-queue/src/Driver/RedisDriver.php @@ -74,20 +74,6 @@ class RedisDriver extends Driver return $this->redis->zAdd($this->channel->getDelayed(), time() + $delay, $data) > 0; } - /** - * @deprecated v1.1 - */ - public function delay(JobInterface $job, int $delay = 0): bool - { - if ($delay === 0) { - return $this->push($job); - } - - $message = new Message($job); - $data = $this->packer->pack($message); - return $this->redis->zAdd($this->channel->getDelayed(), time() + $delay, $data) > 0; - } - public function delete(JobInterface $job): bool { $message = new Message($job); diff --git a/src/async-queue/src/Environment.php b/src/async-queue/src/Environment.php new file mode 100644 index 000000000..3a59baf43 --- /dev/null +++ b/src/async-queue/src/Environment.php @@ -0,0 +1,32 @@ +asyncQueue; + } + + public function setAsyncQueue(bool $asyncQueue): self + { + $this->asyncQueue = $asyncQueue; + return $this; + } +} diff --git a/src/cache/composer.json b/src/cache/composer.json index 44a3d8852..82599a01c 100644 --- a/src/cache/composer.json +++ b/src/cache/composer.json @@ -13,12 +13,12 @@ "php": ">=7.2", "psr/container": "^1.0", "psr/simple-cache": "^1.0", - "hyperf/contract": "~1.0.0", - "hyperf/utils": "~1.0.0" + "hyperf/contract": "~1.1.0", + "hyperf/utils": "~1.1.0" }, "require-dev": { - "hyperf/di": "~1.0.0", - "hyperf/event": "~1.0.0", + "hyperf/di": "~1.1.0", + "hyperf/event": "~1.1.0", "malukenho/docheader": "^0.1.6", "mockery/mockery": "^1.0", "phpunit/phpunit": "^7.0.0", @@ -43,7 +43,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.1-dev" }, "hyperf": { "config": "Hyperf\\Cache\\ConfigProvider" diff --git a/src/cache/src/CacheListenerCollector.php b/src/cache/src/CacheListenerCollector.php index 0444ae171..2bacc297d 100644 --- a/src/cache/src/CacheListenerCollector.php +++ b/src/cache/src/CacheListenerCollector.php @@ -12,12 +12,11 @@ declare(strict_types=1); namespace Hyperf\Cache; +use Hyperf\Di\MetadataCollector; use Hyperf\Utils\Traits\Container; -class CacheListenerCollector +class CacheListenerCollector extends MetadataCollector { - use Container; - /** * @var array */ diff --git a/src/cache/src/ConfigProvider.php b/src/cache/src/ConfigProvider.php index 689c4ad2f..fe493acbf 100644 --- a/src/cache/src/ConfigProvider.php +++ b/src/cache/src/ConfigProvider.php @@ -28,6 +28,9 @@ class ConfigProvider 'paths' => [ __DIR__, ], + 'collectors' => [ + CacheListenerCollector::class, + ] ], 'publish' => [ [ diff --git a/src/cache/tests/Cases/RedisDriverTest.php b/src/cache/tests/Cases/RedisDriverTest.php index 7f0c11164..cf19e0cb2 100644 --- a/src/cache/tests/Cases/RedisDriverTest.php +++ b/src/cache/tests/Cases/RedisDriverTest.php @@ -19,7 +19,9 @@ use Hyperf\Contract\ConfigInterface; use Hyperf\Contract\StdoutLoggerInterface; use Hyperf\Di\Container; use Hyperf\Pool\Channel; +use Hyperf\Pool\LowFrequencyInterface; use Hyperf\Pool\PoolOption; +use Hyperf\Redis\Frequency; use Hyperf\Redis\Pool\PoolFactory; use Hyperf\Redis\Pool\RedisPool; use Hyperf\Redis\Redis; @@ -138,6 +140,9 @@ class RedisDriverTest extends TestCase return new RedisDriver($container, $args['config']); }); $container->shouldReceive('get')->with(PhpSerializerPacker::class)->andReturn(new PhpSerializerPacker()); + $frequency = Mockery::mock(LowFrequencyInterface::class); + $frequency->shouldReceive('isLowFrequency')->andReturn(true); + $container->shouldReceive('make')->with(Frequency::class, Mockery::any())->andReturn($frequency); $container->shouldReceive('make')->with(RedisPool::class, Mockery::any())->andReturnUsing(function ($class, $args) use ($container) { return new RedisPool($container, $args['name']); }); diff --git a/src/circuit-breaker/composer.json b/src/circuit-breaker/composer.json index 6d3b3bdee..5c3c1bb71 100644 --- a/src/circuit-breaker/composer.json +++ b/src/circuit-breaker/composer.json @@ -12,11 +12,11 @@ "require": { "php": ">=7.2", "psr/container": "^1.0", - "hyperf/utils": "~1.0.0" + "hyperf/utils": "~1.1.0" }, "require-dev": { - "hyperf/contract": "~1.0.0", - "hyperf/di": "~1.0.0", + "hyperf/contract": "~1.1.0", + "hyperf/di": "~1.1.0", "malukenho/docheader": "^0.1.6", "mockery/mockery": "^1.0", "phpunit/phpunit": "^7.0.0", @@ -40,7 +40,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.1-dev" }, "hyperf": { "config": "Hyperf\\CircuitBreaker\\ConfigProvider" diff --git a/src/command/.gitattributes b/src/command/.gitattributes new file mode 100644 index 000000000..bdd4ea29c --- /dev/null +++ b/src/command/.gitattributes @@ -0,0 +1 @@ +/tests export-ignore \ No newline at end of file diff --git a/src/command/composer.json b/src/command/composer.json index d4dca7fc3..b6696b2bb 100644 --- a/src/command/composer.json +++ b/src/command/composer.json @@ -15,12 +15,13 @@ }, "autoload-dev": { "psr-4": { + "HyperfTest\\Command\\": "tests/" } }, "require": { "php": ">=7.2", "symfony/console": "^4.2", - "hyperf/utils": "~1.0.0" + "hyperf/utils": "~1.1.0" }, "require-dev": { "malukenho/docheader": "^0.1.6", @@ -33,7 +34,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.1-dev" } }, "scripts": { diff --git a/src/command/src/Command.php b/src/command/src/Command.php index 03327e3cc..66aa3f855 100644 --- a/src/command/src/Command.php +++ b/src/command/src/Command.php @@ -13,6 +13,7 @@ declare(strict_types=1); namespace Hyperf\Command; use Hyperf\Utils\Contracts\Arrayable; +use Hyperf\Utils\Coroutine; use Hyperf\Utils\Str; use Symfony\Component\Console\Command\Command as SymfonyCommand; use Symfony\Component\Console\Formatter\OutputFormatterStyle; @@ -55,7 +56,12 @@ abstract class Command extends SymfonyCommand * * @var bool */ - protected $coroutine = false; + protected $coroutine = true; + + /** + * @var int + */ + protected $hookFlags; /** * The mapping between human readable verbosity levels and Symfony's OutputInterface. @@ -76,6 +82,11 @@ abstract class Command extends SymfonyCommand if (! $name && $this->name) { $name = $this->name; } + + if (! is_int($this->hookFlags)) { + $this->hookFlags = swoole_hook_flags(); + } + parent::__construct($name); } @@ -370,10 +381,10 @@ abstract class Command extends SymfonyCommand protected function execute(InputInterface $input, OutputInterface $output) { - if ($this->coroutine) { + if ($this->coroutine && ! Coroutine::inCoroutine()) { run(function () { call([$this, 'handle']); - }); + }, $this->hookFlags); return 0; } diff --git a/src/command/tests/Command/DefaultSwooleFlagsCommand.php b/src/command/tests/Command/DefaultSwooleFlagsCommand.php new file mode 100644 index 000000000..ba9f0d276 --- /dev/null +++ b/src/command/tests/Command/DefaultSwooleFlagsCommand.php @@ -0,0 +1,27 @@ +hookFlags; + } +} diff --git a/src/command/tests/Command/SwooleFlagsCommand.php b/src/command/tests/Command/SwooleFlagsCommand.php new file mode 100644 index 000000000..dc34775ed --- /dev/null +++ b/src/command/tests/Command/SwooleFlagsCommand.php @@ -0,0 +1,29 @@ +hookFlags; + } +} diff --git a/src/command/tests/CommandTest.php b/src/command/tests/CommandTest.php new file mode 100644 index 000000000..0b3df2811 --- /dev/null +++ b/src/command/tests/CommandTest.php @@ -0,0 +1,33 @@ +assertSame(SWOOLE_HOOK_ALL, $command->getHookFlags()); + + $command = new SwooleFlagsCommand('test:demo2'); + $this->assertSame(SWOOLE_HOOK_ALL | SWOOLE_HOOK_CURL, $command->getHookFlags()); + } +} diff --git a/src/config-aliyun-acm/composer.json b/src/config-aliyun-acm/composer.json index 746e9673a..4d1b31b6a 100644 --- a/src/config-aliyun-acm/composer.json +++ b/src/config-aliyun-acm/composer.json @@ -16,14 +16,14 @@ "require": { "php": ">=7.2", "psr/container": "^1.0", - "hyperf/contract": "~1.0.0", - "hyperf/guzzle": "~1.0.0" + "hyperf/contract": "~1.1.0", + "hyperf/guzzle": "~1.1.0" }, "require-dev": { - "hyperf/config": "~1.0.0", - "hyperf/event": "~1.0.0", - "hyperf/framework": "~1.0.0", - "hyperf/process": "~1.0.0", + "hyperf/config": "~1.1.0", + "hyperf/event": "~1.1.0", + "hyperf/framework": "~1.1.0", + "hyperf/process": "~1.1.0", "malukenho/docheader": "^0.1.6", "mockery/mockery": "^1.0", "phpunit/phpunit": "^7.0.0", @@ -47,7 +47,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.1-dev" }, "hyperf": { "config": "Hyperf\\ConfigAliyunAcm\\ConfigProvider" diff --git a/src/config-apollo/composer.json b/src/config-apollo/composer.json index 65aa43857..22e62aadd 100644 --- a/src/config-apollo/composer.json +++ b/src/config-apollo/composer.json @@ -15,14 +15,14 @@ "require": { "php": ">=7.2", "psr/container": "^1.0", - "hyperf/contract": "~1.0.0", - "hyperf/utils": "~1.0.0" + "hyperf/contract": "~1.1.0", + "hyperf/utils": "~1.1.0" }, "require-dev": { - "hyperf/config": "~1.0.0", - "hyperf/event": "~1.0.0", - "hyperf/framework": "~1.0.0", - "hyperf/process": "~1.0.0", + "hyperf/config": "~1.1.0", + "hyperf/event": "~1.1.0", + "hyperf/framework": "~1.1.0", + "hyperf/process": "~1.1.0", "malukenho/docheader": "^0.1.6", "mockery/mockery": "^1.0", "phpunit/phpunit": "^7.0.0", @@ -46,7 +46,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.1-dev" }, "hyperf": { "config": "Hyperf\\ConfigApollo\\ConfigProvider" diff --git a/src/config-etcd/composer.json b/src/config-etcd/composer.json index 57ce75cd6..699c4e9fc 100644 --- a/src/config-etcd/composer.json +++ b/src/config-etcd/composer.json @@ -21,13 +21,11 @@ }, "require": { "php": ">=7.2", - "ext-swoole": ">=4.3", - "hyperf/utils": "~1.0.0", - "hyperf/etcd": "~1.0.0" + "hyperf/utils": "~1.1.0", + "hyperf/etcd": "~1.1.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "^2.14", - "hyperf/testing": "1.0.*", "phpstan/phpstan": "^0.10.5", "swoft/swoole-ide-helper": "dev-master" }, @@ -41,7 +39,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.1-dev" }, "hyperf": { "config": "Hyperf\\ConfigEtcd\\ConfigProvider" diff --git a/src/config/composer.json b/src/config/composer.json index 527edf5ed..0cdd88e32 100644 --- a/src/config/composer.json +++ b/src/config/composer.json @@ -16,13 +16,13 @@ "psr/container": "^1.0", "vlucas/phpdotenv": "^3.1", "symfony/finder": "^4.2.8", - "hyperf/contract": "~1.0.0", - "hyperf/utils": "~1.0.0" + "hyperf/contract": "~1.1.0", + "hyperf/utils": "~1.1.0" }, "require-dev": { - "hyperf/di": "~1.0.0", - "hyperf/event": "~1.0.0", - "hyperf/framework": "~1.0.0", + "hyperf/di": "~1.1.0", + "hyperf/event": "~1.1.0", + "hyperf/framework": "~1.1.0", "malukenho/docheader": "^0.1.6", "mockery/mockery": "^1.0", "phpunit/phpunit": "^7.0.0", @@ -52,7 +52,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.1-dev" }, "hyperf": { "config": "Hyperf\\Config\\ConfigProvider" diff --git a/src/constants/composer.json b/src/constants/composer.json index 01a712bbf..befd26d45 100644 --- a/src/constants/composer.json +++ b/src/constants/composer.json @@ -11,8 +11,8 @@ }, "require": { "php": ">=7.2", - "hyperf/di": "~1.0.0", - "hyperf/utils": "~1.0.0" + "hyperf/di": "~1.1.0", + "hyperf/utils": "~1.1.0" }, "require-dev": { "malukenho/docheader": "^0.1.6", @@ -37,9 +37,10 @@ }, "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.1-dev" }, "hyperf": { + "config": "Hyperf\\Constants\\ConfigProvider" } }, "bin": [ diff --git a/src/constants/src/ConfigProvider.php b/src/constants/src/ConfigProvider.php new file mode 100644 index 000000000..420568eac --- /dev/null +++ b/src/constants/src/ConfigProvider.php @@ -0,0 +1,30 @@ + [ + ], + 'scan' => [ + 'paths' => [], + 'collectors' => [ + ConstantsCollector::class, + ], + ], + ]; + } +} diff --git a/src/constants/src/ConstantsCollector.php b/src/constants/src/ConstantsCollector.php index 77f330134..351b77596 100644 --- a/src/constants/src/ConstantsCollector.php +++ b/src/constants/src/ConstantsCollector.php @@ -12,12 +12,10 @@ declare(strict_types=1); namespace Hyperf\Constants; -use Hyperf\Utils\Traits\Container; +use Hyperf\Di\MetadataCollector; -class ConstantsCollector +class ConstantsCollector extends MetadataCollector { - use Container; - /** * @var array */ diff --git a/src/consul/composer.json b/src/consul/composer.json index 09cac4752..1143c5186 100644 --- a/src/consul/composer.json +++ b/src/consul/composer.json @@ -13,7 +13,7 @@ }, "require": { "php": ">=7.2", - "hyperf/guzzle": "~1.0.0" + "hyperf/guzzle": "~1.1.0" }, "require-dev": { "malukenho/docheader": "^0.1.6", @@ -38,7 +38,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.1-dev" }, "hyperf": { "config": "Hyperf\\Consul\\ConfigProvider" diff --git a/src/contract/composer.json b/src/contract/composer.json index 9cb3573c8..32ff3b472 100644 --- a/src/contract/composer.json +++ b/src/contract/composer.json @@ -34,7 +34,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.1-dev" }, "hyperf": { } diff --git a/src/crontab/composer.json b/src/crontab/composer.json index 20d4a2881..5829675c1 100644 --- a/src/crontab/composer.json +++ b/src/crontab/composer.json @@ -37,7 +37,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.1-dev" }, "hyperf": { "config": "Hyperf\\Crontab\\ConfigProvider" diff --git a/src/database/composer.json b/src/database/composer.json index c1f6aac0f..5c925aa0f 100644 --- a/src/database/composer.json +++ b/src/database/composer.json @@ -11,8 +11,8 @@ }, "require": { "php": ">=7.2", - "hyperf/utils": "~1.0.0", - "hyperf/paginator": "~1.0.0", + "hyperf/utils": "~1.1.0", + "hyperf/paginator": "~1.1.0", "psr/container": "^1.0", "nesbot/carbon": "^2.0", "psr/event-dispatcher": "^1.0" @@ -42,7 +42,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.1-dev" } }, "bin": [ diff --git a/src/database/src/Commands/ModelCommand.php b/src/database/src/Commands/ModelCommand.php index cee45436a..5be70c3bd 100644 --- a/src/database/src/Commands/ModelCommand.php +++ b/src/database/src/Commands/ModelCommand.php @@ -94,7 +94,8 @@ class ModelCommand extends Command ->setInheritance($this->getOption('inheritance', 'commands.db:model.inheritance', $pool, 'Model')) ->setUses($this->getOption('uses', 'commands.db:model.uses', $pool, 'Hyperf\DbConnection\Model\Model')) ->setForceCasts($this->getOption('force-casts', 'commands.db:model.force_casts', $pool, false)) - ->setRefreshFillable($this->getOption('refresh-fillable', 'commands.db:model.refresh_fillable', $pool, false)); + ->setRefreshFillable($this->getOption('refresh-fillable', 'commands.db:model.refresh_fillable', $pool, false)) + ->setTableMapping($this->getOption('table-mapping', 'commands.db:model.table_mapping', $pool)); if ($table) { $this->createModel($table, $option); @@ -114,6 +115,7 @@ class ModelCommand extends Command $this->addOption('inheritance', 'i', InputOption::VALUE_OPTIONAL, 'The inheritance that you want the Model extends.'); $this->addOption('uses', 'U', InputOption::VALUE_OPTIONAL, 'The default class uses of the Model.'); $this->addOption('refresh-fillable', null, InputOption::VALUE_NONE, 'Whether generate fillable argement for model.'); + $this->addOption('table-mapping', 'M', InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Table mappings for model.'); } protected function getSchemaBuilder(string $poolName): MySqlBuilder @@ -144,7 +146,8 @@ class ModelCommand extends Command $columns = $this->formatColumns($builder->getColumnTypeListing($table)); $project = new Project(); - $class = $project->namespace($option->getPath()) . Str::studly($table); + $class = $option->getTableMapping()[$table] ?? Str::studly(Str::singular($table)); + $class = $project->namespace($option->getPath()) . $class; $path = BASE_PATH . '/' . $project->path($class); if (! file_exists($path)) { @@ -208,7 +211,14 @@ class ModelCommand extends Command protected function getOption(string $name, string $key, string $pool = 'default', $default = null) { $result = $this->input->getOption($name); - $nonInput = in_array($name, ['force-casts', 'refresh-fillable']) ? false : null; + $nonInput = null; + if (in_array($name, ['force-casts', 'refresh-fillable'])) { + $nonInput = false; + } + if (in_array($name, ['table-mapping'])) { + $nonInput = []; + } + if ($result === $nonInput) { $result = $this->config->get("databases.{$pool}.{$key}", $default); } diff --git a/src/database/src/Commands/ModelOption.php b/src/database/src/Commands/ModelOption.php index 9c2eba451..f3e2ecea2 100644 --- a/src/database/src/Commands/ModelOption.php +++ b/src/database/src/Commands/ModelOption.php @@ -49,6 +49,11 @@ class ModelOption */ protected $refreshFillable; + /** + * @var array + */ + protected $tableMapping = []; + public function getPool(): string { return $this->pool; @@ -125,4 +130,19 @@ class ModelOption $this->refreshFillable = $refreshFillable; return $this; } + + public function getTableMapping(): array + { + return $this->tableMapping; + } + + public function setTableMapping(array $tableMapping): ModelOption + { + foreach ($tableMapping as $item) { + [$key, $name] = explode(':', $item); + $this->tableMapping[$key] = $name; + } + + return $this; + } } diff --git a/src/database/src/Grammar.php b/src/database/src/Grammar.php index 36392cc13..d44c25c85 100755 --- a/src/database/src/Grammar.php +++ b/src/database/src/Grammar.php @@ -197,7 +197,7 @@ abstract class Grammar $segments[0] ) . ' as ' . $this->wrapValue( $segments[1] - ); + ); } /** diff --git a/src/database/src/Model/Events/Event.php b/src/database/src/Model/Events/Event.php index d90675bfb..75dcb25af 100644 --- a/src/database/src/Model/Events/Event.php +++ b/src/database/src/Model/Events/Event.php @@ -42,7 +42,7 @@ abstract class Event implements StoppableEventInterface public function handle() { if (method_exists($this->getModel(), $this->getMethod())) { - return $this->getModel()->{$this->getMethod()}($this); + $this->getModel()->{$this->getMethod()}($this); } return $this; diff --git a/src/database/src/Query/Grammars/Grammar.php b/src/database/src/Query/Grammars/Grammar.php index 1c8088325..af7fc4228 100755 --- a/src/database/src/Query/Grammars/Grammar.php +++ b/src/database/src/Query/Grammars/Grammar.php @@ -74,7 +74,7 @@ class Grammar extends BaseGrammar $sql = trim( $this->concatenate( $this->compileComponents($query) - ) + ) ); $query->columns = $original; @@ -770,7 +770,7 @@ class Grammar extends BaseGrammar return $not . $this->compileJsonContains( $where['column'], $this->parameter($where['value']) - ); + ); } /** diff --git a/src/database/src/Schema/Builder.php b/src/database/src/Schema/Builder.php index 5a1636507..48e33779e 100755 --- a/src/database/src/Schema/Builder.php +++ b/src/database/src/Schema/Builder.php @@ -81,7 +81,7 @@ class Builder return count($this->connection->selectFromWriteConnection( $this->grammar->compileTableExists(), [$table] - )) > 0; + )) > 0; } /** diff --git a/src/db-connection/src/Listener/ModelEventListener.php b/src/database/tests/Stubs/ModelEventListenerStub.php similarity index 86% rename from src/db-connection/src/Listener/ModelEventListener.php rename to src/database/tests/Stubs/ModelEventListenerStub.php index 08426466d..fb01bcdaa 100644 --- a/src/db-connection/src/Listener/ModelEventListener.php +++ b/src/database/tests/Stubs/ModelEventListenerStub.php @@ -10,7 +10,7 @@ declare(strict_types=1); * @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE */ -namespace Hyperf\DbConnection\Listener; +namespace HyperfTest\Database\Stubs; use Hyperf\Database\Model\Events\Event; use Hyperf\Event\Annotation\Listener; @@ -19,7 +19,7 @@ use Hyperf\Event\Contract\ListenerInterface; /** * @Listener */ -class ModelEventListener implements ListenerInterface +class ModelEventListenerStub implements ListenerInterface { public function listen(): array { diff --git a/src/database/tests/Stubs/ModelObserverStub.php b/src/database/tests/Stubs/ModelObserverStub.php new file mode 100644 index 000000000..1f988634d --- /dev/null +++ b/src/database/tests/Stubs/ModelObserverStub.php @@ -0,0 +1,23 @@ +getModel()->foo = 'bar'; + } +} diff --git a/src/db-connection/composer.json b/src/db-connection/composer.json index 1338d2033..355755540 100644 --- a/src/db-connection/composer.json +++ b/src/db-connection/composer.json @@ -12,12 +12,12 @@ }, "require": { "php": ">=7.2", - "hyperf/framework": "~1.0.0", - "hyperf/database": "~1.0.0", - "hyperf/di": "~1.0.0", - "hyperf/event": "~1.0.0", - "hyperf/pool": "~1.0.0", - "hyperf/utils": "~1.0.0" + "hyperf/framework": "~1.1.0", + "hyperf/database": "~1.1.0", + "hyperf/di": "~1.1.0", + "hyperf/model-listener": "~1.1.0", + "hyperf/pool": "~1.1.0", + "hyperf/utils": "~1.1.0" }, "require-dev": { "malukenho/docheader": "^0.1.6", @@ -42,7 +42,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.1-dev" }, "hyperf": { "config": "Hyperf\\DbConnection\\ConfigProvider" diff --git a/src/db-connection/src/Frequency.php b/src/db-connection/src/Frequency.php index 73f31ae47..c6e7987a7 100644 --- a/src/db-connection/src/Frequency.php +++ b/src/db-connection/src/Frequency.php @@ -12,82 +12,8 @@ declare(strict_types=1); namespace Hyperf\DbConnection; -use Hyperf\Contract\FrequencyInterface; -use Hyperf\Pool\LowFrequencyInterface; +use Hyperf\Pool\Frequency as DefaultFrequency; -class Frequency implements FrequencyInterface, LowFrequencyInterface +class Frequency extends DefaultFrequency { - /** - * @var array - */ - protected $hits = []; - - /** - * How much time do you want to calculate the frequency ? - * @var int - */ - protected $time = 10; - - /** - * @var int - */ - protected $lowFrequency = 5; - - /** - * @var int - */ - protected $beginTime; - - public function __construct() - { - $this->beginTime = time(); - } - - public function hit(int $number = 1): bool - { - $this->flush(); - - $now = time(); - $hit = $this->hits[$now] ?? 0; - $this->hits[$now] = $number + $hit; - - return true; - } - - public function frequency(): float - { - $this->flush(); - - $hits = 0; - $count = 0; - foreach ($this->hits as $hit) { - ++$count; - $hits += $hit; - } - - return floatval($hits / $count); - } - - public function isLowFrequency(): bool - { - return $this->frequency() < $this->lowFrequency; - } - - protected function flush(): void - { - $now = time(); - $latest = $now - $this->time; - foreach ($this->hits as $time => $hit) { - if ($time < $latest) { - unset($this->hits[$time]); - } - } - - if (count($this->hits) < $this->time) { - $beginTime = $this->beginTime < $latest ? $latest : $this->beginTime; - for ($i = $beginTime; $i < $now; ++$i) { - $this->hits[$i] = $this->hits[$i] ?? 0; - } - } - } } diff --git a/src/devtool/composer.json b/src/devtool/composer.json index 01451d549..ab9710e89 100644 --- a/src/devtool/composer.json +++ b/src/devtool/composer.json @@ -12,10 +12,10 @@ }, "require": { "php": ">=7.2", - "hyperf/command": "~1.0.0", - "hyperf/contract": "~1.0.0", - "hyperf/di": "~1.0.0", - "hyperf/utils": "~1.0.0" + "hyperf/command": "~1.1.0", + "hyperf/contract": "~1.1.0", + "hyperf/di": "~1.1.0", + "hyperf/utils": "~1.1.0" }, "require-dev": { "malukenho/docheader": "^0.1.6", @@ -39,7 +39,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.1-dev" }, "hyperf": { "config": "Hyperf\\Devtool\\ConfigProvider" diff --git a/src/di/composer.json b/src/di/composer.json index a6f653ae4..146e6c99e 100644 --- a/src/di/composer.json +++ b/src/di/composer.json @@ -19,8 +19,8 @@ "symfony/finder": "^4.1", "php-di/phpdoc-reader": "^2.0.1", "doctrine/instantiator": "^1.0", - "hyperf/event": "~1.0.0", - "hyperf/framework": "~1.0.0" + "hyperf/event": "~1.1.0", + "hyperf/framework": "~1.1.0" }, "require-dev": { "malukenho/docheader": "^0.1.6", @@ -49,7 +49,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.1-dev" }, "hyperf": { "config": "Hyperf\\Di\\ConfigProvider" diff --git a/src/di/src/Annotation/Aspect.php b/src/di/src/Annotation/Aspect.php index 2a865446b..8b9da2e62 100644 --- a/src/di/src/Annotation/Aspect.php +++ b/src/di/src/Annotation/Aspect.php @@ -21,9 +21,6 @@ use Hyperf\Di\Aop\AroundInterface; */ class Aspect extends AbstractAnnotation { - /** - * {@inheritdoc} - */ public function collectClass(string $className): void { // @TODO Add order property. @@ -33,7 +30,9 @@ class Aspect extends AbstractAnnotation $instance = $instantitor->instantiate($className); switch ($instance) { case $instance instanceof AroundInterface: - AspectCollector::setAround($className, $instance->classes, $instance->annotations); + $classes = property_exists($instance, 'classes') ? $instance->classes : []; + $annotations = property_exists($instance, 'annotations') ? $instance->annotations : []; + AspectCollector::setAround($className, $classes, $annotations); break; } } diff --git a/src/di/src/Annotation/AspectCollector.php b/src/di/src/Annotation/AspectCollector.php index feac5a87d..b8aa6f8cc 100644 --- a/src/di/src/Annotation/AspectCollector.php +++ b/src/di/src/Annotation/AspectCollector.php @@ -28,8 +28,8 @@ class AspectCollector extends MetadataCollector public static function setAround(string $aspect, array $classes, array $annotations): void { - $classes && static::set('classes.' . $aspect, $classes); - $annotations && static::set('annotations.' . $aspect, $annotations); + static::set('classes.' . $aspect, $classes); + static::set('annotations.' . $aspect, $annotations); static::$aspectRules[$aspect] = [ 'classes' => $classes, 'annotations' => $annotations, diff --git a/src/di/src/Aop/ProceedingJoinPoint.php b/src/di/src/Aop/ProceedingJoinPoint.php index 1193b5f4f..67d4ec45e 100644 --- a/src/di/src/Aop/ProceedingJoinPoint.php +++ b/src/di/src/Aop/ProceedingJoinPoint.php @@ -14,6 +14,7 @@ namespace Hyperf\Di\Aop; use Closure; use Hyperf\Di\Annotation\AnnotationCollector; +use Hyperf\Di\Exception\Exception; class ProceedingJoinPoint { @@ -43,7 +44,7 @@ class ProceedingJoinPoint public $originalMethod; /** - * @var Closure + * @var null|Closure */ public $pipe; @@ -61,6 +62,10 @@ class ProceedingJoinPoint public function process() { $closure = $this->pipe; + if (! $closure instanceof Closure) { + throw new Exception('The pipe is not instanceof \Closure'); + } + return $closure($this); } diff --git a/src/di/src/Aop/ProxyCallVisitor.php b/src/di/src/Aop/ProxyCallVisitor.php index bd32755ce..9afbafb46 100644 --- a/src/di/src/Aop/ProxyCallVisitor.php +++ b/src/di/src/Aop/ProxyCallVisitor.php @@ -52,7 +52,7 @@ class ProxyCallVisitor extends NodeVisitorAbstract ]; /** - * @var Identifier + * @var null|Identifier */ private $class; @@ -78,7 +78,7 @@ class ProxyCallVisitor extends NodeVisitorAbstract continue; } if (! $namespace instanceof Namespace_) { - return; + break; } // Add current class namespace. $usedNamespace = [ @@ -112,6 +112,8 @@ class ProxyCallVisitor extends NodeVisitorAbstract } } } + + return null; } public function leaveNode(Node $node) @@ -134,7 +136,7 @@ class ProxyCallVisitor extends NodeVisitorAbstract break; case $node instanceof StaticPropertyFetch && $this->extends: // Rewrite parent::$staticProperty to ParentClass::$staticProperty. - if ($node->class && $node->class->toString() === 'parent') { + if ($node->class instanceof Node\Name && $node->class->toString() === 'parent') { $node->class = new Name($this->extends->toCodeString()); return $node; } @@ -225,17 +227,17 @@ class ProxyCallVisitor extends NodeVisitorAbstract $class = $this->class->toString(); $staticCall = new StaticCall(new Name('self'), '__proxyCall', [ // OriginalClass::class - new ClassConstFetch(new Name($class), new Identifier('class')), + new Node\Arg(new ClassConstFetch(new Name($class), new Identifier('class'))), // __FUNCTION__ - new MagicConstFunction(), + new Node\Arg(new MagicConstFunction()), // self::getParamMap(OriginalClass::class, __FUNCTION, func_get_args()) - new StaticCall(new Name('self'), 'getParamsMap', [ - new ClassConstFetch(new Name($class), new Identifier('class')), - new MagicConstFunction(), - new FuncCall(new Name('func_get_args')), - ]), + new Node\Arg(new StaticCall(new Name('self'), 'getParamsMap', [ + new Node\Arg(new ClassConstFetch(new Name($class), new Identifier('class'))), + new Node\Arg(new MagicConstFunction()), + new Node\Arg(new FuncCall(new Name('func_get_args'))), + ])), // A closure that wrapped original method code. - new Closure([ + new Node\Arg(new Closure([ 'params' => value(function () use ($node) { // Transfer the variadic variable to normal variable at closure argument. ...$params => $parms $params = $node->getParams(); @@ -253,7 +255,7 @@ class ProxyCallVisitor extends NodeVisitorAbstract new Variable('__method__'), ], 'stmts' => $node->stmts, - ]), + ])), ]); $magicConstFunction = new Expression(new Assign(new Variable('__function__'), new Node\Scalar\MagicConst\Function_())); $magicConstMethod = new Expression(new Assign(new Variable('__method__'), new Node\Scalar\MagicConst\Method())); diff --git a/src/di/src/Aop/ProxyClassNameVisitor.php b/src/di/src/Aop/ProxyClassNameVisitor.php index adda63a7f..7c6cfe05a 100644 --- a/src/di/src/Aop/ProxyClassNameVisitor.php +++ b/src/di/src/Aop/ProxyClassNameVisitor.php @@ -35,7 +35,7 @@ class ProxyClassNameVisitor extends NodeVisitorAbstract { // Rewirte the class name and extends the original class. if ($node instanceof Node\Stmt\Class_ && ! $node->isAnonymous()) { - $node->extends = $node->name; + $node->extends = new Node\Name($node->name->name); $node->name = new Node\Identifier($this->proxyClassName); return $node; } diff --git a/src/di/src/Command/InitProxyCommand.php b/src/di/src/Command/InitProxyCommand.php index 8e3c08f59..00c2cb838 100644 --- a/src/di/src/Command/InitProxyCommand.php +++ b/src/di/src/Command/InitProxyCommand.php @@ -57,7 +57,7 @@ class InitProxyCommand extends Command protected function clearRuntime($paths) { $finder = new Finder(); - $finder->files()->in($paths)->name('*.php'); + $finder->files()->in($paths)->name(['*.php', '*.cache']); /** @var SplFileInfo $file */ foreach ($finder as $file) { @@ -91,7 +91,7 @@ class InitProxyCommand extends Command { $scanDirs = $this->getScanDir(); - $runtime = BASE_PATH . '/runtime/container/proxy/'; + $runtime = BASE_PATH . '/runtime/container/'; if (is_dir($runtime)) { $this->clearRuntime($runtime); } diff --git a/src/di/src/ConfigProvider.php b/src/di/src/ConfigProvider.php index d706c551a..610b47cd8 100644 --- a/src/di/src/ConfigProvider.php +++ b/src/di/src/ConfigProvider.php @@ -12,6 +12,8 @@ declare(strict_types=1); namespace Hyperf\Di; +use Hyperf\Di\Annotation\AnnotationCollector; +use Hyperf\Di\Annotation\AspectCollector; use Hyperf\Di\Command\InitProxyCommand; use Hyperf\Di\Listener\BootApplicationListener; @@ -33,6 +35,10 @@ class ConfigProvider 'paths' => [ __DIR__, ], + 'collectors' => [ + AnnotationCollector::class, + AspectCollector::class, + ], ], ]; } diff --git a/src/di/src/Container.php b/src/di/src/Container.php index 8286d82e1..fe7998bce 100644 --- a/src/di/src/Container.php +++ b/src/di/src/Container.php @@ -17,7 +17,6 @@ use Hyperf\Di\Definition\ObjectDefinition; use Hyperf\Di\Exception\NotFoundException; use Hyperf\Di\Resolver\ResolverDispatcher; use Hyperf\Dispatcher\Exceptions\InvalidArgumentException; -use Psr\Container\ContainerExceptionInterface; use Psr\Container\ContainerInterface; use Psr\Container\NotFoundExceptionInterface; @@ -61,11 +60,12 @@ class Container implements ContainerInterface { $this->definitionSource = $definitionSource; $this->definitionResolver = new ResolverDispatcher($this); - $this->proxyFactory = new ProxyFactory($this); + $this->proxyFactory = new ProxyFactory(); // Auto-register the container. $this->resolvedEntries = [ self::class => $this, ContainerInterface::class => $this, + ProxyFactory::class => $this->proxyFactory, ]; } @@ -97,8 +97,6 @@ class Container implements ContainerInterface * Finds an entry of the container by its identifier and returns it. * * @param string $name identifier of the entry to look for - * @throws NotFoundExceptionInterface no entry was found for **this** identifier - * @throws ContainerExceptionInterface error while retrieving the entry * @return mixed entry */ public function get($name) diff --git a/src/di/src/Definition/DefinitionSource.php b/src/di/src/Definition/DefinitionSource.php index b7daba97d..00f27f6be 100644 --- a/src/di/src/Definition/DefinitionSource.php +++ b/src/di/src/Definition/DefinitionSource.php @@ -16,6 +16,7 @@ use Hyperf\Di\Annotation\AnnotationCollector; use Hyperf\Di\Annotation\AspectCollector; use Hyperf\Di\Annotation\Inject; use Hyperf\Di\Annotation\Scanner; +use Hyperf\Di\MetadataCacheCollector; use Hyperf\Di\ReflectionManager; use ReflectionClass; use ReflectionFunctionAbstract; @@ -67,13 +68,13 @@ class DefinitionSource implements DefinitionSourceInterface */ private $scanner; - public function __construct(array $source, array $scanDir, Scanner $scanner, bool $enableCache = false) + public function __construct(array $source, ScanConfig $scanConfig, bool $enableCache = false) { - $this->scanner = $scanner; + $this->scanner = new Scanner($scanConfig->getIgnoreAnnotations()); $this->enableCache = $enableCache; // Scan the specified paths and collect the ast and annotations. - $this->scan($scanDir); + $this->scan($scanConfig->getDirs(), $scanConfig->getCollectors()); $this->source = $this->normalizeSource($source); } @@ -149,8 +150,7 @@ class DefinitionSource implements DefinitionSourceInterface } /** - * @param array|callable|string $definitions - * @param mixed $definition + * @param array|callable|string $definition */ private function normalizeDefinition(string $identifier, $definition): ?DefinitionInterface { @@ -214,18 +214,17 @@ class DefinitionSource implements DefinitionSourceInterface return $definition; } - private function scan(array $paths): bool + private function scan(array $paths, array $collectors): bool { if (empty($paths)) { return true; } $pathsHash = md5(implode(',', $paths)); + $cacher = new MetadataCacheCollector($collectors); if ($this->hasAvailableCache($paths, $pathsHash, $this->cachePath)) { $this->printLn('Detected an available cache, skip the scan process.'); - [, $annotationMetadata, $aspectMetadata] = explode(PHP_EOL, file_get_contents($this->cachePath)); - // Deserialize metadata when the cache is valid. - AnnotationCollector::deserialize($annotationMetadata); - AspectCollector::deserialize($aspectMetadata); + [, $serialized] = explode(PHP_EOL, file_get_contents($this->cachePath)); + $cacher->unserialize($serialized); return false; } $this->printLn('Scanning ...'); @@ -243,7 +242,8 @@ class DefinitionSource implements DefinitionSourceInterface mkdir($dirPath, 0755, true); } } - $data = implode(PHP_EOL, [$pathsHash, AnnotationCollector::serialize(), AspectCollector::serialize()]); + + $data = implode(PHP_EOL, [$pathsHash, $cacher->serialize()]); file_put_contents($this->cachePath, $data); return true; } diff --git a/src/di/src/Definition/DefinitionSourceFactory.php b/src/di/src/Definition/DefinitionSourceFactory.php new file mode 100644 index 000000000..190465a6f --- /dev/null +++ b/src/di/src/Definition/DefinitionSourceFactory.php @@ -0,0 +1,71 @@ +enableCache = $enableCache; + + if (! defined('BASE_PATH')) { + throw new Exception('BASE_PATH is not defined.'); + } + + $this->baseUri = BASE_PATH; + } + + public function __invoke() + { + $configDir = $this->baseUri . '/config'; + + $configFromProviders = []; + if (class_exists(ProviderConfig::class)) { + $configFromProviders = ProviderConfig::load(); + } + + $serverDependencies = $configFromProviders['dependencies'] ?? []; + if (file_exists($configDir . '/dependencies.php')) { + $definitions = include $configDir . '/dependencies.php'; + $serverDependencies = array_replace($serverDependencies, $definitions['dependencies'] ?? []); + } + + $scanDirs = $configFromProviders['scan']['paths'] ?? []; + $ignoreAnnotations = []; + $collectors = $configFromProviders['scan']['collectors'] ?? []; + + if (file_exists($configDir . '/autoload/annotations.php')) { + $annotations = include $configDir . '/autoload/annotations.php'; + $scanDirs = array_merge($scanDirs, $annotations['scan']['paths'] ?? []); + $ignoreAnnotations = $annotations['scan']['ignore_annotations'] ?? []; + $collectors = array_merge($collectors, $annotations['scan']['collectors'] ?? []); + } + + $scanConfig = new ScanConfig($scanDirs, $ignoreAnnotations, $collectors); + + return new DefinitionSource($serverDependencies, $scanConfig, $this->enableCache); + } +} diff --git a/src/di/src/Definition/DefinitionSourceInterface.php b/src/di/src/Definition/DefinitionSourceInterface.php index 03bcd2078..eed3d0394 100644 --- a/src/di/src/Definition/DefinitionSourceInterface.php +++ b/src/di/src/Definition/DefinitionSourceInterface.php @@ -20,7 +20,7 @@ interface DefinitionSourceInterface * Returns the DI definition for the entry name. * * @throws InvalidDefinitionException an invalid definition was found - * @return null|array + * @return null|DefinitionInterface */ public function getDefinition(string $name); diff --git a/src/di/src/Definition/FactoryDefinition.php b/src/di/src/Definition/FactoryDefinition.php index 1e368161d..eb4b8b486 100644 --- a/src/di/src/Definition/FactoryDefinition.php +++ b/src/di/src/Definition/FactoryDefinition.php @@ -20,7 +20,7 @@ class FactoryDefinition implements DefinitionInterface private $name; /** - * @var callable + * @var callable|string */ private $factory; diff --git a/src/di/src/Definition/ScanConfig.php b/src/di/src/Definition/ScanConfig.php new file mode 100644 index 000000000..1b95d789d --- /dev/null +++ b/src/di/src/Definition/ScanConfig.php @@ -0,0 +1,71 @@ +dirs = $dirs; + $this->ignoreAnnotations = $ignoreAnnotations; + $this->collectors = $collectors; + } + + public function getDirs(): array + { + return $this->dirs; + } + + public function setDirs(array $dirs): self + { + $this->dirs = $dirs; + return $this; + } + + public function getIgnoreAnnotations(): array + { + return $this->ignoreAnnotations; + } + + public function setIgnoreAnnotations(array $ignoreAnnotations): self + { + $this->ignoreAnnotations = $ignoreAnnotations; + return $this; + } + + public function getCollectors(): array + { + return $this->collectors; + } + + public function setCollectors(array $collectors): self + { + $this->collectors = $collectors; + return $this; + } +} diff --git a/src/di/src/MetadataCacheCollector.php b/src/di/src/MetadataCacheCollector.php new file mode 100644 index 000000000..f7aff7992 --- /dev/null +++ b/src/di/src/MetadataCacheCollector.php @@ -0,0 +1,65 @@ +collectors = $collectors; + } + + public function addCollector(string $collector) + { + $this->collectors = array_unique(array_merge( + $this->collectors, + [$collector] + )); + } + + public function clear() + { + $this->collectors = []; + } + + public function serialize(): string + { + $metadata = []; + foreach ($this->collectors as $collector) { + if (is_string($collector) && method_exists($collector, 'serialize')) { + $metadata[$collector] = call([$collector, 'serialize']); + } + } + + return json_encode($metadata); + } + + public function unserialize($serialized): void + { + $metadatas = json_decode($serialized, true) ?? []; + $collectors = []; + foreach ($metadatas as $collector => $metadata) { + if (method_exists($collector, 'deserialize')) { + call([$collector, 'deserialize'], [$metadata]); + $collectors[] = $collector; + } + } + + $this->collectors = $collectors; + } +} diff --git a/src/di/src/MetadataCollectorInterface.php b/src/di/src/MetadataCollectorInterface.php index d86bbab0f..f1aef45f2 100644 --- a/src/di/src/MetadataCollectorInterface.php +++ b/src/di/src/MetadataCollectorInterface.php @@ -35,4 +35,9 @@ interface MetadataCollectorInterface * Deserialize the serialized metadata and set the metadata to holder. */ public static function deserialize(string $metadata): bool; + + /** + * Return all metadata array. + */ + public static function list(): array; } diff --git a/src/di/src/ProxyFactory.php b/src/di/src/ProxyFactory.php index 9605c2423..e54638a07 100644 --- a/src/di/src/ProxyFactory.php +++ b/src/di/src/ProxyFactory.php @@ -13,7 +13,6 @@ declare(strict_types=1); namespace Hyperf\Di; use Hyperf\Di\Aop\Ast; -use Hyperf\Di\Definition\FactoryDefinition; use Hyperf\Di\Definition\ObjectDefinition; use Hyperf\Utils\Coroutine\Locker as CoLocker; @@ -40,16 +39,11 @@ class ProxyFactory if (isset(static::$map[$identifier])) { return static::$map[$identifier]; } - $proxyIdentifier = null; - if ($definition instanceof FactoryDefinition) { - $proxyIdentifier = $definition->getFactory() . '_' . md5($definition->getFactory()); - $proxyIdentifier && $definition->setTarget($proxyIdentifier); - $this->loadProxy($definition->getName(), $definition->getFactory()); - } elseif ($definition instanceof ObjectDefinition) { - $proxyIdentifier = $definition->getClassName() . '_' . md5($definition->getClassName()); - $definition->setProxyClassName($proxyIdentifier); - $this->loadProxy($definition->getClassName(), $definition->getProxyClassName()); - } + + $proxyIdentifier = $definition->getClassName() . '_' . md5($definition->getClassName()); + $definition->setProxyClassName($proxyIdentifier); + $this->loadProxy($definition->getClassName(), $definition->getProxyClassName()); + static::$map[$identifier] = $definition; return static::$map[$identifier]; } diff --git a/src/di/src/Resolver/ObjectResolver.php b/src/di/src/Resolver/ObjectResolver.php index e556ba2bf..a7818f3e1 100644 --- a/src/di/src/Resolver/ObjectResolver.php +++ b/src/di/src/Resolver/ObjectResolver.php @@ -12,7 +12,6 @@ declare(strict_types=1); namespace Hyperf\Di\Resolver; -use Hyperf\Di\Container; use Hyperf\Di\Definition\DefinitionInterface; use Hyperf\Di\Definition\ObjectDefinition; use Hyperf\Di\Definition\PropertyInjection; @@ -48,14 +47,12 @@ class ObjectResolver implements ResolverInterface /** * ObjectResolver constructor. - * - * @param Container $container */ public function __construct(ContainerInterface $container, ResolverInterface $definitionResolver) { $this->container = $container; $this->definitionResolver = $definitionResolver; - $this->proxyFactory = $container->getProxyFactory(); + $this->proxyFactory = $container->get(ProxyFactory::class); $this->parameterResolver = new ParameterResolver($definitionResolver); } @@ -64,12 +61,18 @@ class ObjectResolver implements ResolverInterface * * @param DefinitionInterface $definition object that defines how the value should be obtained * @param array $parameters optional parameters to use to build the entry - * @throws DependencyException * @throws InvalidDefinitionException + * @throws DependencyException * @return mixed value obtained from the definition */ public function resolve(DefinitionInterface $definition, array $parameters = []) { + if (! $definition instanceof ObjectDefinition) { + throw InvalidDefinitionException::create( + $definition, + sprintf('Entry "%s" cannot be resolved: the class is not instanceof ObjectDefinition', $definition->getName()) + ); + } return $this->createInstance($definition, $parameters); } diff --git a/src/di/src/Resolver/ParameterResolver.php b/src/di/src/Resolver/ParameterResolver.php index bfee0b7af..6729bd503 100644 --- a/src/di/src/Resolver/ParameterResolver.php +++ b/src/di/src/Resolver/ParameterResolver.php @@ -21,7 +21,7 @@ use ReflectionParameter; class ParameterResolver { /** - * @var DefinitionInterface + * @var ResolverInterface */ private $definitionResolver; diff --git a/src/di/src/Resolver/ResolverDispatcher.php b/src/di/src/Resolver/ResolverDispatcher.php index baaf7d89b..e32ec3791 100644 --- a/src/di/src/Resolver/ResolverDispatcher.php +++ b/src/di/src/Resolver/ResolverDispatcher.php @@ -23,12 +23,12 @@ use RuntimeException; class ResolverDispatcher implements ResolverInterface { /** - * @var ObjectResolver + * @var null|ObjectResolver */ protected $objectResolver; /** - * @var FactoryResolver + * @var null|FactoryResolver */ protected $factoryResolver; diff --git a/src/di/tests/AopAspectTest.php b/src/di/tests/AopAspectTest.php index e459ca0dc..8755d4e81 100644 --- a/src/di/tests/AopAspectTest.php +++ b/src/di/tests/AopAspectTest.php @@ -12,11 +12,15 @@ declare(strict_types=1); namespace HyperfTest\Di; +use Hyperf\Di\Annotation\Aspect as AspectAnnotation; use Hyperf\Di\Aop\Aspect; use Hyperf\Di\Aop\RewriteCollection; use HyperfTest\Di\Stub\AnnotationCollector; use HyperfTest\Di\Stub\AspectCollector; use HyperfTest\Di\Stub\DemoAnnotation; +use HyperfTest\Di\Stub\Foo; +use HyperfTest\Di\Stub\Foo2Aspect; +use HyperfTest\Di\Stub\FooAspect; use PHPUnit\Framework\TestCase; /** @@ -203,4 +207,22 @@ class AopAspectTest extends TestCase $this->assertFalse(Aspect::isMatch('Foo/Bar/Baz', 'method', $rule)); $this->assertFalse(Aspect::isMatch('Foo/Bar', 'test', $rule)); } + + public function testAspectAnnotation() + { + $annotation = new AspectAnnotation(); + + $annotation->collectClass(FooAspect::class); + $annotation->collectClass(Foo2Aspect::class); + + $this->assertSame([ + 'classes' => [Foo::class], + 'annotations' => [DemoAnnotation::class], + ], AspectCollector::getRule(FooAspect::class)); + + $this->assertSame([ + 'classes' => [Foo::class], + 'annotations' => [], + ], AspectCollector::getRule(Foo2Aspect::class)); + } } diff --git a/src/di/tests/AstTest.php b/src/di/tests/AstTest.php new file mode 100644 index 000000000..20a08c4f1 --- /dev/null +++ b/src/di/tests/AstTest.php @@ -0,0 +1,125 @@ +proxy(Foo::class, $proxyClass); + + $this->assertEquals('proxy(Bar2::class, $proxyClass); + + $this->assertEquals('proxy(Bar3::class, $proxyClass); + + $this->assertEquals('assertFalse($container->has(FooInterface::class)); $this->assertFalse($container->has(NotExistClass::class)); $this->assertTrue($container->has(Foo::class)); diff --git a/src/di/tests/DefinitionSourceTest.php b/src/di/tests/DefinitionSourceTest.php index 6630ca941..984cb330b 100644 --- a/src/di/tests/DefinitionSourceTest.php +++ b/src/di/tests/DefinitionSourceTest.php @@ -12,9 +12,11 @@ declare(strict_types=1); namespace HyperfTest\Di; -use Hyperf\Di\Annotation\Scanner; use Hyperf\Di\Container; use Hyperf\Di\Definition\DefinitionSource; +use Hyperf\Di\Definition\ScanConfig; +use HyperfTest\Di\Stub\Foo; +use HyperfTest\Di\Stub\FooFactory; use PHPUnit\Framework\TestCase; /** @@ -25,10 +27,19 @@ class DefinitionSourceTest extends TestCase { public function testAddDefinition() { - $container = new Container(new DefinitionSource([], [], new Scanner())); + $container = new Container(new DefinitionSource([], new ScanConfig())); $container->getDefinitionSource()->addDefinition('Foo', function () { return 'bar'; }); $this->assertEquals('bar', $container->get('Foo')); } + + public function testDefinitionFactory() + { + $container = new Container(new DefinitionSource([], new ScanConfig())); + $container->getDefinitionSource()->addDefinition('Foo', FooFactory::class); + + $foo = $container->get('Foo'); + $this->assertInstanceOf(Foo::class, $foo); + } } diff --git a/src/di/tests/MakeTest.php b/src/di/tests/MakeTest.php index 3138b998c..fb9b372fd 100644 --- a/src/di/tests/MakeTest.php +++ b/src/di/tests/MakeTest.php @@ -12,9 +12,9 @@ declare(strict_types=1); namespace HyperfTest\Di; -use Hyperf\Di\Annotation\Scanner; use Hyperf\Di\Container; use Hyperf\Di\Definition\DefinitionSource; +use Hyperf\Di\Definition\ScanConfig; use Hyperf\Utils\ApplicationContext; use HyperfTest\Di\Stub\Bar; use HyperfTest\Di\Stub\Demo; @@ -29,7 +29,7 @@ class MakeTest extends TestCase { public function setUp() { - $container = new Container(new DefinitionSource([], [], new Scanner())); + $container = new Container(new DefinitionSource([], new ScanConfig())); ApplicationContext::setContainer($container); } diff --git a/src/di/tests/MetadataCollectorTest.php b/src/di/tests/MetadataCollectorTest.php new file mode 100644 index 000000000..3f57e6f71 --- /dev/null +++ b/src/di/tests/MetadataCollectorTest.php @@ -0,0 +1,76 @@ +serialize(); + AnnotationCollector::clear(); + $cacher->unserialize($string); + + $collector2 = AnnotationCollector::list(); + + $this->assertEquals($collector, $collector2); + } + + public function getContainer() + { + $container = Mockery::mock(ContainerInterface::class); + $container->shouldReceive('get')->with(ConfigInterface::class)->andReturn(new Config([ + 'scan' => [ + 'paths' => [ + __DIR__, + ], + 'cacheable' => [ + AnnotationCollector::class, + AspectCollector::class, + ], + ], + ])); + + return $container; + } +} diff --git a/src/di/tests/Stub/Ast/Bar.php b/src/di/tests/Stub/Ast/Bar.php new file mode 100644 index 000000000..9d43ad2b2 --- /dev/null +++ b/src/di/tests/Stub/Ast/Bar.php @@ -0,0 +1,46 @@ +id = $id; + } + + public function getId(): int + { + return $this->id; + } + + public function setId(int $id): self + { + $this->id = $id; + return $this; + } + + public static function make() + { + return new static(0); + } + + public static function getItems() + { + return static::items; + } +} diff --git a/src/di/tests/Stub/Ast/Bar2.php b/src/di/tests/Stub/Ast/Bar2.php new file mode 100644 index 000000000..341c91969 --- /dev/null +++ b/src/di/tests/Stub/Ast/Bar2.php @@ -0,0 +1,26 @@ +process(); + } +} diff --git a/src/di/tests/Stub/Ast/Foo.php b/src/di/tests/Stub/Ast/Foo.php new file mode 100644 index 000000000..69442fbfb --- /dev/null +++ b/src/di/tests/Stub/Ast/Foo.php @@ -0,0 +1,17 @@ +=7.0", - "hyperf/guzzle": "~1.0.0", + "hyperf/guzzle": "~1.1.0", "elasticsearch/elasticsearch": "^6.1" }, "require-dev": { - "swoft/swoole-ide-helper": "~1.0.0", + "swoft/swoole-ide-helper": "dev-master", "phpunit/phpunit": "^5.7", "guzzlehttp/ringphp": "~1.0" }, "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.1-dev" } }, "scripts": { diff --git a/src/etcd/composer.json b/src/etcd/composer.json index e7f6e9fbf..d066b19ff 100644 --- a/src/etcd/composer.json +++ b/src/etcd/composer.json @@ -20,12 +20,10 @@ }, "require": { "php": ">=7.2", - "ext-swoole": ">=4.3", - "hyperf/utils": "~1.0.0" + "hyperf/utils": "~1.1.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "^2.14", - "hyperf/testing": "1.0.*", "phpstan/phpstan": "^0.10.5", "swoft/swoole-ide-helper": "dev-master" }, @@ -43,7 +41,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.1-dev" }, "hyperf": { "config": "Hyperf\\Etcd\\ConfigProvider" diff --git a/src/event/composer.json b/src/event/composer.json index c1bd2e605..2a57fd76a 100644 --- a/src/event/composer.json +++ b/src/event/composer.json @@ -13,10 +13,10 @@ "require": { "php": ">=7.2", "psr/event-dispatcher": "^1.0", - "hyperf/contract": "~1.0.0" + "hyperf/contract": "~1.1.0" }, "require-dev": { - "hyperf/di": "~1.0.0", + "hyperf/di": "~1.1.0", "malukenho/docheader": "^0.1.6", "mockery/mockery": "^1.0", "phpunit/phpunit": "^7.0.0", @@ -40,7 +40,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.1-dev" }, "hyperf": { "config": "Hyperf\\Event\\ConfigProvider" diff --git a/src/exception-handler/composer.json b/src/exception-handler/composer.json index a2e4edb89..c8f57a66c 100644 --- a/src/exception-handler/composer.json +++ b/src/exception-handler/composer.json @@ -21,9 +21,9 @@ "php": ">=7.2", "psr/container": "^1.0", "psr/http-message": "^1.0", - "hyperf/contract": "~1.0.0", - "hyperf/dispatcher": "~1.0.0", - "hyperf/utils": "~1.0.0" + "hyperf/contract": "~1.1.0", + "hyperf/dispatcher": "~1.1.0", + "hyperf/utils": "~1.1.0" }, "require-dev": { "malukenho/docheader": "^0.1.6", @@ -35,7 +35,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.1-dev" }, "hyperf": { "config": "Hyperf\\ExceptionHandler\\ConfigProvider" diff --git a/src/framework/composer.json b/src/framework/composer.json index 470c8778a..03ec78908 100644 --- a/src/framework/composer.json +++ b/src/framework/composer.json @@ -14,10 +14,10 @@ }, "require": { "php": ">=7.2", - "ext-swoole": ">=4.3", + "ext-swoole": ">=4.4", "fig/http-message-util": "^1.1.2", - "hyperf/contract": "~1.0.0", - "hyperf/utils": "~1.0.0", + "hyperf/contract": "~1.1.0", + "hyperf/utils": "~1.1.0", "psr/container": "^1.0", "psr/event-dispatcher": "^1.0", "psr/log": "^1.0" @@ -48,7 +48,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.1-dev" }, "hyperf": { "config": "Hyperf\\Framework\\ConfigProvider" diff --git a/src/graphql/composer.json b/src/graphql/composer.json index 4c8a2e1a9..d5dd12ffc 100644 --- a/src/graphql/composer.json +++ b/src/graphql/composer.json @@ -25,7 +25,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.1-dev" }, "hyperf": { "config": "Hyperf\\GraphQL\\ConfigProvider" diff --git a/src/grpc-client/composer.json b/src/grpc-client/composer.json index f6dcd269f..3f91fd779 100644 --- a/src/grpc-client/composer.json +++ b/src/grpc-client/composer.json @@ -13,8 +13,8 @@ }, "require": { "php": ">=7.2", - "hyperf/grpc": "~1.0.0", - "hyperf/utils": "~1.0.0", + "hyperf/grpc": "~1.1.0", + "hyperf/utils": "~1.1.0", "jean85/pretty-package-versions": "^1.2" }, "require-dev": { @@ -40,7 +40,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.1-dev" }, "hyperf": { "config": "Hyperf\\GrpcClient\\ConfigProvider" diff --git a/src/grpc-server/composer.json b/src/grpc-server/composer.json index 4f1b74877..282b3daa5 100644 --- a/src/grpc-server/composer.json +++ b/src/grpc-server/composer.json @@ -13,11 +13,11 @@ }, "require": { "php": ">=7.2", - "hyperf/di": "~1.0.0", - "hyperf/utils": "~1.0.0", - "hyperf/http-server": "~1.0.0", - "hyperf/http-message": "~1.0.0", - "hyperf/grpc": "~1.0.0" + "hyperf/di": "~1.1.0", + "hyperf/utils": "~1.1.0", + "hyperf/http-server": "~1.1.0", + "hyperf/http-message": "~1.1.0", + "hyperf/grpc": "~1.1.0" }, "require-dev": { "malukenho/docheader": "^0.1.6", @@ -42,7 +42,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.1-dev" }, "hyperf": { "config": "Hyperf\\GrpcServer\\ConfigProvider" diff --git a/src/grpc-server/src/CoreMiddleware.php b/src/grpc-server/src/CoreMiddleware.php index 922c6b048..c7ca57c1e 100644 --- a/src/grpc-server/src/CoreMiddleware.php +++ b/src/grpc-server/src/CoreMiddleware.php @@ -20,6 +20,8 @@ use Hyperf\Di\ReflectionManager; use Hyperf\Grpc\Parser; use Hyperf\HttpMessage\Stream\SwooleStream; use Hyperf\HttpServer\CoreMiddleware as HttpCoreMiddleware; +use Hyperf\HttpServer\Router\Dispatched; +use Hyperf\Server\Exception\ServerException; use Hyperf\Utils\Context; use Psr\Container\ContainerInterface; use Psr\Http\Message\RequestInterface; @@ -45,26 +47,22 @@ class CoreMiddleware extends HttpCoreMiddleware */ public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { - /** @var ResponseInterface $response */ - $uri = $request->getUri(); + /** @var Dispatched $dispatched */ + $dispatched = $request->getAttribute(Dispatched::class); - /** - * @var array - * Returns array with one of the following formats: - * [self::NOT_FOUND] - * [self::METHOD_NOT_ALLOWED, ['GET', 'OTHER_ALLOWED_METHODS']] - * [self::FOUND, $handler, ['varName' => 'value', ...]] - */ - $routes = $this->dispatcher->dispatch($request->getMethod(), $uri->getPath()); - switch ($routes[0]) { + if (! $dispatched instanceof Dispatched) { + throw new ServerException(sprintf('The dispatched object is not a %s object.', Dispatched::class)); + } + + switch ($dispatched->status) { case Dispatcher::FOUND: - [$controller, $action] = $this->prepareHandler($routes[1]); + [$controller, $action] = $this->prepareHandler($dispatched->handler->callback); $controllerInstance = $this->container->get($controller); if (! method_exists($controller, $action)) { $grpcMessage = 'Action not exist.'; return $this->handleResponse(null, 500, '500', $grpcMessage); } - $parameters = $this->parseParameters($controller, $action, $routes[2]); + $parameters = $this->parseParameters($controller, $action, $dispatched->params); $result = $controllerInstance->{$action}(...$parameters); if (! $result instanceof Message) { $grpcMessage = 'The result is not a valid message.'; diff --git a/src/grpc-server/src/Server.php b/src/grpc-server/src/Server.php index 067f0eedd..3621e6beb 100644 --- a/src/grpc-server/src/Server.php +++ b/src/grpc-server/src/Server.php @@ -13,19 +13,11 @@ declare(strict_types=1); namespace Hyperf\GrpcServer; use Hyperf\Contract\ConfigInterface; -use Hyperf\Dispatcher\HttpDispatcher; use Hyperf\GrpcServer\Exception\Handler\GrpcExceptionHandler; use Hyperf\HttpServer\Server as HttpServer; -use Psr\Container\ContainerInterface; class Server extends HttpServer { - public function __construct(ContainerInterface $container) - { - $this->container = $container; - $this->dispatcher = $container->get(HttpDispatcher::class); - } - public function initCoreMiddleware(string $serverName): void { $this->serverName = $serverName; diff --git a/src/grpc/composer.json b/src/grpc/composer.json index 0d963ed97..b0a6e626f 100644 --- a/src/grpc/composer.json +++ b/src/grpc/composer.json @@ -37,7 +37,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.1-dev" }, "hyperf": { } diff --git a/src/guzzle/composer.json b/src/guzzle/composer.json index ca1ff8f83..fafe77c4d 100644 --- a/src/guzzle/composer.json +++ b/src/guzzle/composer.json @@ -26,7 +26,7 @@ "guzzlehttp/guzzle": "^6.3" }, "require-dev": { - "swoft/swoole-ide-helper": "~1.0.0", + "swoft/swoole-ide-helper": "dev-master", "phpunit/phpunit": "^5.7", "guzzlehttp/ringphp": "~1.0" }, @@ -35,7 +35,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.1-dev" } }, "scripts": { diff --git a/src/http-message/.gitattributes b/src/http-message/.gitattributes new file mode 100644 index 000000000..bdd4ea29c --- /dev/null +++ b/src/http-message/.gitattributes @@ -0,0 +1 @@ +/tests export-ignore \ No newline at end of file diff --git a/src/http-message/composer.json b/src/http-message/composer.json index 12d6f13ee..a236d1ad7 100755 --- a/src/http-message/composer.json +++ b/src/http-message/composer.json @@ -14,7 +14,7 @@ "zendframework/zend-mime": "^2.7" }, "require-dev": { - "swoft/swoole-ide-helper": "~1.0.0", + "swoft/swoole-ide-helper": "dev-master", "phpunit/phpunit": "^5.7" }, "autoload": { @@ -32,7 +32,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.1-dev" }, "hyperf": { } diff --git a/src/http-message/src/Server/Request.php b/src/http-message/src/Server/Request.php index fbab9013d..bd9a0f605 100755 --- a/src/http-message/src/Server/Request.php +++ b/src/http-message/src/Server/Request.php @@ -15,6 +15,7 @@ namespace Hyperf\HttpMessage\Server; use Hyperf\HttpMessage\Stream\SwooleStream; use Hyperf\HttpMessage\Upload\UploadedFile; use Hyperf\HttpMessage\Uri\Uri; +use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\UploadedFileInterface; @@ -80,7 +81,7 @@ class Request extends \Hyperf\HttpMessage\Base\Request implements ServerRequestI $request->cookieParams = ($swooleRequest->cookie ?? []); $request->queryParams = ($swooleRequest->get ?? []); $request->serverParams = ($server ?? []); - $request->parsedBody = ($swooleRequest->post ?? []); + $request->parsedBody = self::normalizeParsedBody($swooleRequest->post ?? [], $request); $request->uploadedFiles = self::normalizeFiles($swooleRequest->files ?? []); $request->swooleRequest = $swooleRequest; return $request; @@ -349,10 +350,10 @@ class Request extends \Hyperf\HttpMessage\Base\Request implements ServerRequestI * This method obviates the need for a hasAttribute() method, as it allows * specifying a default value to return if the attribute is not found. * - * @see getAttributes() * @param string $name the attribute name * @param mixed $default default value to return if the attribute does not exist * @return mixed + * @see getAttributes() */ public function getAttribute($name, $default = null) { @@ -367,10 +368,10 @@ class Request extends \Hyperf\HttpMessage\Base\Request implements ServerRequestI * immutability of the message, and MUST return an instance that has the * updated attribute. * - * @see getAttributes() * @param string $name the attribute name * @param mixed $value the value of the attribute * @return static + * @see getAttributes() */ public function withAttribute($name, $value) { @@ -387,9 +388,9 @@ class Request extends \Hyperf\HttpMessage\Base\Request implements ServerRequestI * immutability of the message, and MUST return an instance that removes * the attribute. * - * @see getAttributes() * @param string $name the attribute name * @return static + * @see getAttributes() */ public function withoutAttribute($name) { @@ -468,6 +469,20 @@ class Request extends \Hyperf\HttpMessage\Base\Request implements ServerRequestI return $this; } + protected static function normalizeParsedBody(array $data = [], ?RequestInterface $request = null) + { + if (! $request) { + return $data; + } + + $contentType = strtolower($request->getHeaderLine('Content-Type')); + if (strpos($contentType, 'application/json') === 0) { + $data = json_decode($request->getBody()->getContents(), true) ?? []; + } + + return $data; + } + /** * Return an UploadedFile instance array. * diff --git a/src/http-message/src/Stream/FileInterface.php b/src/http-message/src/Stream/FileInterface.php index e6a00b83f..fa8f9d104 100644 --- a/src/http-message/src/Stream/FileInterface.php +++ b/src/http-message/src/Stream/FileInterface.php @@ -15,4 +15,4 @@ namespace Hyperf\HttpMessage\Stream; interface FileInterface { public function getFilename(): string; -} \ No newline at end of file +} diff --git a/src/http-message/src/Stream/SwooleFileStream.php b/src/http-message/src/Stream/SwooleFileStream.php index e9026c041..2e11ea860 100644 --- a/src/http-message/src/Stream/SwooleFileStream.php +++ b/src/http-message/src/Stream/SwooleFileStream.php @@ -30,7 +30,7 @@ class SwooleFileStream implements StreamInterface, FileInterface /** * SwooleFileStream constructor. * - * @param string|\SplFileInfo $file + * @param \SplFileInfo|string $file */ public function __construct($file) { @@ -237,4 +237,4 @@ class SwooleFileStream implements StreamInterface, FileInterface { return $this->file->getPathname(); } -} \ No newline at end of file +} diff --git a/src/http-message/tests/ServerRequestTest.php b/src/http-message/tests/ServerRequestTest.php new file mode 100644 index 000000000..2e22973ef --- /dev/null +++ b/src/http-message/tests/ServerRequestTest.php @@ -0,0 +1,63 @@ + 1]; + $json = ['name' => 'Hyperf']; + + $request = Mockery::mock(RequestInterface::class); + $request->shouldReceive('getHeaderLine')->with('Content-Type')->andReturn(''); + + $this->assertSame($data, RequestStub::normalizeParsedBody($data)); + $this->assertSame($data, RequestStub::normalizeParsedBody($data, $request)); + + $request = Mockery::mock(RequestInterface::class); + $request->shouldReceive('getHeaderLine')->with('Content-Type')->andReturn('application/xml'); + $this->assertSame($data, RequestStub::normalizeParsedBody($data, $request)); + + $request = Mockery::mock(RequestInterface::class); + $request->shouldReceive('getHeaderLine')->with('Content-Type')->andReturn('application/json; charset=utf-8'); + $request->shouldReceive('getBody')->andReturn(new SwooleStream(json_encode($json))); + $this->assertSame($json, RequestStub::normalizeParsedBody($data, $request)); + + $request = Mockery::mock(RequestInterface::class); + $request->shouldReceive('getHeaderLine')->with('Content-Type')->andReturn('application/json; charset=utf-8'); + $request->shouldReceive('getBody')->andReturn(new SwooleStream('xxxx')); + $this->assertSame([], RequestStub::normalizeParsedBody($data, $request)); + } + + public function testNormalizeParsedBodyInvalidContentType() + { + $data = ['id' => 1]; + $json = ['name' => 'Hyperf']; + + $request = Mockery::mock(RequestInterface::class); + $request->shouldReceive('getHeaderLine')->with('Content-Type')->andReturn('application/JSON'); + $request->shouldReceive('getBody')->andReturn(new SwooleStream(json_encode($json))); + $this->assertSame($json, RequestStub::normalizeParsedBody($data, $request)); + } +} diff --git a/src/http-message/tests/Stub/Server/RequestStub.php b/src/http-message/tests/Stub/Server/RequestStub.php new file mode 100644 index 000000000..0b8d8a600 --- /dev/null +++ b/src/http-message/tests/Stub/Server/RequestStub.php @@ -0,0 +1,24 @@ +assertSame(null, $response->send()); } -} \ No newline at end of file +} diff --git a/src/http-server/composer.json b/src/http-server/composer.json index 4750b5e43..58f56303c 100644 --- a/src/http-server/composer.json +++ b/src/http-server/composer.json @@ -15,16 +15,16 @@ "php": ">=7.2", "psr/container": "^1.0", "nikic/fast-route": "^1.3", - "hyperf/contract": "~1.0.0", - "hyperf/dispatcher": "~1.0.0", - "hyperf/event": "~1.0.0", - "hyperf/exception-handler": "~1.0.0", - "hyperf/http-message": "~1.0.0", - "hyperf/server": "~1.0.0", - "hyperf/utils": "~1.0.0" + "hyperf/contract": "~1.1.0", + "hyperf/dispatcher": "~1.1.0", + "hyperf/event": "~1.1.0", + "hyperf/exception-handler": "~1.1.0", + "hyperf/http-message": "~1.1.0", + "hyperf/server": "~1.1.0", + "hyperf/utils": "~1.1.0" }, "require-dev": { - "hyperf/di": "~1.0.0", + "hyperf/di": "~1.1.0", "malukenho/docheader": "^0.1.6", "mockery/mockery": "^1.0", "phpunit/phpunit": "^7.0.0", @@ -48,7 +48,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.1-dev" }, "hyperf": { "config": "Hyperf\\HttpServer\\ConfigProvider" diff --git a/src/http-server/src/ConfigProvider.php b/src/http-server/src/ConfigProvider.php index ce42420de..2bd8340bb 100644 --- a/src/http-server/src/ConfigProvider.php +++ b/src/http-server/src/ConfigProvider.php @@ -22,7 +22,6 @@ class ConfigProvider { return [ 'dependencies' => [ - Server::class => ServerFactory::class, RequestInterface::class => Request::class, ServerRequestInterface::class => Request::class, ResponseInterface::class => Response::class, diff --git a/src/http-server/src/Contract/CoreMiddlewareInterface.php b/src/http-server/src/Contract/CoreMiddlewareInterface.php new file mode 100644 index 000000000..587519b35 --- /dev/null +++ b/src/http-server/src/Contract/CoreMiddlewareInterface.php @@ -0,0 +1,21 @@ +methodDefinitionCollector = $this->container->get(MethodDefinitionCollectorInterface::class); } + public function dispatch(ServerRequestInterface $request): ServerRequestInterface + { + $routes = $this->dispatcher->dispatch($request->getMethod(), $request->getUri()->getPath()); + + $dispatched = new Dispatched($routes); + + return Context::set(ServerRequestInterface::class, $request->withAttribute(Dispatched::class, $dispatched)); + } + /** * Process an incoming server request and return a response, optionally delegating * response creation to a handler. */ public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { - /** @var ResponseInterface $response */ - $uri = $request->getUri(); - /** - * @var array - * Returns array with one of the following formats: - * [self::NOT_FOUND] - * [self::METHOD_NOT_ALLOWED, ['GET', 'OTHER_ALLOWED_METHODS']] - * [self::FOUND, $handler, ['varName' => 'value', ...]] - */ - $routes = $this->dispatcher->dispatch($request->getMethod(), $uri->getPath()); - switch ($routes[0]) { + /** @var Dispatched $dispatched */ + $dispatched = $request->getAttribute(Dispatched::class); + + if (! $dispatched instanceof Dispatched) { + throw new ServerException(sprintf('The dispatched object is not a %s object.', Dispatched::class)); + } + + switch ($dispatched->status) { case Dispatcher::NOT_FOUND: $response = $this->handleNotFound($request); break; case Dispatcher::METHOD_NOT_ALLOWED: - $response = $this->handleMethodNotAllowed($routes, $request); + $response = $this->handleMethodNotAllowed($dispatched->params, $request); break; case Dispatcher::FOUND: - $response = $this->handleFound($routes, $request); + $response = $this->handleFound($dispatched, $request); break; } if (! $response instanceof ResponseInterface) { @@ -117,18 +126,18 @@ class CoreMiddleware implements MiddlewareInterface * * @return array|Arrayable|mixed|ResponseInterface|string */ - protected function handleFound(array $routes, ServerRequestInterface $request) + protected function handleFound(Dispatched $dispatched, ServerRequestInterface $request) { - if ($routes[1] instanceof Closure) { - $response = call($routes[1]); + if ($dispatched->handler->callback instanceof Closure) { + $response = call($dispatched->handler->callback); } else { - [$controller, $action] = $this->prepareHandler($routes[1]); + [$controller, $action] = $this->prepareHandler($dispatched->handler->callback); $controllerInstance = $this->container->get($controller); if (! method_exists($controller, $action)) { // Route found, but the handler does not exist. return $this->response()->withStatus(500)->withBody(new SwooleStream('Method of class does not exist.')); } - $parameters = $this->parseParameters($controller, $action, $routes[2]); + $parameters = $this->parseParameters($controller, $action, $dispatched->params); $response = $controllerInstance->{$action}(...$parameters); } return $response; @@ -149,9 +158,9 @@ class CoreMiddleware implements MiddlewareInterface * * @return array|Arrayable|mixed|ResponseInterface|string */ - protected function handleMethodNotAllowed(array $routes, ServerRequestInterface $request) + protected function handleMethodNotAllowed(array $methods, ServerRequestInterface $request) { - return $this->response()->withStatus(405)->withAddedHeader('Allow', implode(', ', $routes[1])); + return $this->response()->withStatus(405)->withAddedHeader('Allow', implode(', ', $methods)); } /** diff --git a/src/http-server/src/Exception/Handler/HttpExceptionHandler.php b/src/http-server/src/Exception/Handler/HttpExceptionHandler.php index ae171adf9..9f55db5df 100644 --- a/src/http-server/src/Exception/Handler/HttpExceptionHandler.php +++ b/src/http-server/src/Exception/Handler/HttpExceptionHandler.php @@ -16,7 +16,6 @@ use Hyperf\Contract\StdoutLoggerInterface; use Hyperf\ExceptionHandler\ExceptionHandler; use Hyperf\ExceptionHandler\Formatter\FormatterInterface; use Hyperf\HttpMessage\Stream\SwooleStream; -use Hyperf\Server\Exception\ServerException; use Psr\Http\Message\ResponseInterface; use Throwable; diff --git a/src/http-server/src/Exception/Http/FileException.php b/src/http-server/src/Exception/Http/FileException.php index 56c8c735d..172a4ea2e 100644 --- a/src/http-server/src/Exception/Http/FileException.php +++ b/src/http-server/src/Exception/Http/FileException.php @@ -16,4 +16,4 @@ use Hyperf\Server\Exception\ServerException; class FileException extends ServerException { -} \ No newline at end of file +} diff --git a/src/http-server/src/Exception/Http/InvalidResponseException.php b/src/http-server/src/Exception/Http/InvalidResponseException.php new file mode 100644 index 000000000..75b40c93c --- /dev/null +++ b/src/http-server/src/Exception/Http/InvalidResponseException.php @@ -0,0 +1,19 @@ +storeParsedData(function () { $request = $this->getRequest(); - $contentType = $request->getHeaderLine('Content-Type'); - if ($contentType && Str::startsWith($contentType, 'application/json')) { - $body = $request->getBody(); - $data = json_decode($body->getContents(), true) ?? []; - } elseif (is_array($request->getParsedBody())) { + if (is_array($request->getParsedBody())) { $data = $request->getParsedBody(); } else { $data = []; diff --git a/src/http-server/src/Response.php b/src/http-server/src/Response.php index b338a4cfb..f68844319 100644 --- a/src/http-server/src/Response.php +++ b/src/http-server/src/Response.php @@ -13,18 +13,20 @@ declare(strict_types=1); namespace Hyperf\HttpServer; use BadMethodCallException; +use Hyperf\HttpMessage\Cookie\Cookie; use Hyperf\HttpMessage\Stream\SwooleFileStream; use Hyperf\HttpMessage\Stream\SwooleStream; use Hyperf\HttpServer\Contract\ResponseInterface; use Hyperf\HttpServer\Exception\Http\EncodingException; use Hyperf\HttpServer\Exception\Http\FileException; -use Hyperf\Utils\MimeTypeExtensionGuesser; +use Hyperf\HttpServer\Exception\Http\InvalidResponseException; use Hyperf\Utils\ApplicationContext; use Hyperf\Utils\ClearStatCache; use Hyperf\Utils\Context; use Hyperf\Utils\Contracts\Arrayable; use Hyperf\Utils\Contracts\Jsonable; use Hyperf\Utils\Contracts\Xmlable; +use Hyperf\Utils\MimeTypeExtensionGuesser; use Hyperf\Utils\Str; use Hyperf\Utils\Traits\Macroable; use Psr\Http\Message\ResponseInterface as PsrResponseInterface; @@ -37,6 +39,16 @@ class Response implements PsrResponseInterface, ResponseInterface { use Macroable; + /** + * @var null|PsrResponseInterface + */ + protected $response; + + public function __construct(?PsrResponseInterface $response = null) + { + $this->response = $response; + } + public function __call($name, $arguments) { $response = $this->getResponse(); @@ -118,8 +130,8 @@ class Response implements PsrResponseInterface, ResponseInterface /** * Create a file download response. * - * @param string $file The file path which want to send to client. - * @param string $name The alias name of the file that client receive. + * @param string $file the file path which want to send to client + * @param string $name the alias name of the file that client receive */ public function download(string $file, string $name = ''): PsrResponseInterface { @@ -161,6 +173,11 @@ class Response implements PsrResponseInterface, ResponseInterface ->withBody(new SwooleFileStream($file)); } + public function withCookie(Cookie $cookie): ResponseInterface + { + return $this->call(__FUNCTION__, func_get_args()); + } + /** * Retrieves the HTTP protocol version as a string. * The string MUST contain only the HTTP version number (e.g., "1.1", "1.0"). @@ -185,7 +202,7 @@ class Response implements PsrResponseInterface, ResponseInterface */ public function withProtocolVersion($version) { - return $this->getResponse()->withProtocolVersion($version); + return $this->call(__FUNCTION__, func_get_args()); } /** @@ -280,7 +297,7 @@ class Response implements PsrResponseInterface, ResponseInterface */ public function withHeader($name, $value) { - return $this->getResponse()->withHeader($name, $value); + return $this->call(__FUNCTION__, func_get_args()); } /** @@ -299,7 +316,7 @@ class Response implements PsrResponseInterface, ResponseInterface */ public function withAddedHeader($name, $value) { - return $this->getResponse()->withAddedHeader($name, $value); + return $this->call(__FUNCTION__, func_get_args()); } /** @@ -314,7 +331,7 @@ class Response implements PsrResponseInterface, ResponseInterface */ public function withoutHeader($name) { - return $this->getResponse()->withoutHeader($name); + return $this->call(__FUNCTION__, func_get_args()); } /** @@ -340,7 +357,7 @@ class Response implements PsrResponseInterface, ResponseInterface */ public function withBody(StreamInterface $body) { - return $this->getResponse()->withBody($body); + return $this->call(__FUNCTION__, func_get_args()); } /** @@ -375,7 +392,7 @@ class Response implements PsrResponseInterface, ResponseInterface */ public function withStatus($code, $reasonPhrase = '') { - return $this->getResponse()->withStatus($code, $reasonPhrase); + return $this->call(__FUNCTION__, func_get_args()); } /** @@ -395,6 +412,21 @@ class Response implements PsrResponseInterface, ResponseInterface return $this->getResponse()->getReasonPhrase(); } + protected function call($name, $arguments) + { + $response = $this->getResponse(); + + if (! $response instanceof PsrResponseInterface) { + throw new InvalidResponseException('The response is not instanceof ' . PsrResponseInterface::class); + } + + if (! method_exists($response, $name)) { + throw new BadMethodCallException(sprintf('Call to undefined method %s::%s()', get_class($this), $name)); + } + + return new static($response->{$name}(...$arguments)); + } + /** * Get ETag header according to the checksum of the file. */ @@ -478,6 +510,10 @@ class Response implements PsrResponseInterface, ResponseInterface */ protected function getResponse() { + if ($this->response instanceof PsrResponseInterface) { + return $this->response; + } + return Context::get(PsrResponseInterface::class); } } diff --git a/src/http-server/src/Router/Dispatched.php b/src/http-server/src/Router/Dispatched.php new file mode 100644 index 000000000..335f071e1 --- /dev/null +++ b/src/http-server/src/Router/Dispatched.php @@ -0,0 +1,61 @@ + 'value', ...]] + */ + public function __construct(array $array) + { + $this->status = $array[0]; + switch ($this->status) { + case Dispatcher::METHOD_NOT_ALLOWED: + $this->params = $array[1]; + break; + case Dispatcher::FOUND: + $this->handler = $array[1]; + $this->params = $array[2]; + break; + } + } + + public function isFound(): bool + { + return $this->status === Dispatcher::FOUND; + } +} diff --git a/src/http-server/src/Router/DispatcherFactory.php b/src/http-server/src/Router/DispatcherFactory.php index 22edb7163..85320bb45 100644 --- a/src/http-server/src/Router/DispatcherFactory.php +++ b/src/http-server/src/Router/DispatcherFactory.php @@ -30,7 +30,6 @@ use Hyperf\HttpServer\Annotation\PatchMapping; use Hyperf\HttpServer\Annotation\PostMapping; use Hyperf\HttpServer\Annotation\PutMapping; use Hyperf\HttpServer\Annotation\RequestMapping; -use Hyperf\HttpServer\MiddlewareManager; use Hyperf\Utils\Str; use ReflectionMethod; @@ -121,7 +120,6 @@ class DispatcherFactory if (substr($methodName, 0, 2) === '__') { continue; } - $router->addRoute($autoMethods, $path, [$className, $methodName, $annotation->server]); $methodMiddlewares = $middlewares; // Handle method level middlewares. @@ -130,16 +128,15 @@ class DispatcherFactory $methodMiddlewares = array_unique($methodMiddlewares); } - // Register middlewares. - foreach ($autoMethods as $autoMethod) { - MiddlewareManager::addMiddlewares($annotation->server, $path, $autoMethod, $methodMiddlewares); - } + $router->addRoute($autoMethods, $path, [$className, $methodName], [ + 'middleware' => $methodMiddlewares, + ]); + if (Str::endsWith($path, $defaultAction)) { $path = Str::replaceLast($defaultAction, '', $path); - $router->addRoute($autoMethods, $path, [$className, $methodName, $annotation->server]); - foreach ($autoMethods as $autoMethod) { - MiddlewareManager::addMiddlewares($annotation->server, $path, $autoMethod, $methodMiddlewares); - } + $router->addRoute($autoMethods, $path, [$className, $methodName], [ + 'middleware' => $methodMiddlewares, + ]); } } } @@ -185,16 +182,9 @@ class DispatcherFactory } elseif ($path[0] !== '/') { $path = $prefix . '/' . $path; } - $router->addRoute($mapping->methods, $path, [ - $className, - $methodName, - $annotation->server, + $router->addRoute($mapping->methods, $path, [$className, $methodName], [ + 'middleware' => $methodMiddlewares, ]); - - // Register middlewares. - foreach ($mapping->methods as $mappingMethod) { - MiddlewareManager::addMiddlewares($annotation->server, $path, $mappingMethod, $methodMiddlewares); - } } } } diff --git a/src/http-server/src/Router/Handler.php b/src/http-server/src/Router/Handler.php new file mode 100644 index 000000000..565132c1e --- /dev/null +++ b/src/http-server/src/Router/Handler.php @@ -0,0 +1,32 @@ +callback = $callback; + $this->route = $route; + } +} diff --git a/src/http-server/src/Router/RouteCollector.php b/src/http-server/src/Router/RouteCollector.php index 933328113..4faf783d2 100644 --- a/src/http-server/src/Router/RouteCollector.php +++ b/src/http-server/src/Router/RouteCollector.php @@ -74,8 +74,8 @@ class RouteCollector foreach ((array) $httpMethod as $method) { $method = strtoupper($method); foreach ($routeDatas as $routeData) { - $this->dataGenerator->addRoute($method, $routeData, $handler); - MiddlewareManager::addMiddlewares($this->server, $routeData[0], $method, $options['middleware'] ?? []); + $this->dataGenerator->addRoute($method, $routeData, new Handler($handler, $route)); + MiddlewareManager::addMiddlewares($this->server, $route, $method, $options['middleware'] ?? []); } } } diff --git a/src/http-server/src/Server.php b/src/http-server/src/Server.php index bedfe9de1..7cc447906 100644 --- a/src/http-server/src/Server.php +++ b/src/http-server/src/Server.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Hyperf\HttpServer; +use FastRoute\Dispatcher; use Hyperf\Contract\ConfigInterface; use Hyperf\Contract\MiddlewareInitializerInterface; use Hyperf\Contract\OnRequestInterface; @@ -19,12 +20,15 @@ use Hyperf\Dispatcher\HttpDispatcher; use Hyperf\ExceptionHandler\ExceptionHandlerDispatcher; use Hyperf\HttpMessage\Server\Request as Psr7Request; use Hyperf\HttpMessage\Server\Response as Psr7Response; +use Hyperf\HttpServer\Contract\CoreMiddlewareInterface; use Hyperf\HttpServer\Exception\Handler\HttpExceptionHandler; +use Hyperf\HttpServer\Router\Dispatched; +use Hyperf\HttpServer\Router\DispatcherFactory; +use Hyperf\HttpServer\Router\Handler; use Hyperf\Utils\Context; use Psr\Container\ContainerInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; -use Psr\Http\Server\MiddlewareInterface; use Swoole\Http\Request as SwooleRequest; use Swoole\Http\Response as SwooleResponse; use Throwable; @@ -32,27 +36,7 @@ use Throwable; class Server implements OnRequestInterface, MiddlewareInitializerInterface { /** - * @var array - */ - protected $middlewares; - - /** - * @var string - */ - protected $coreHandler; - - /** - * @var MiddlewareInterface - */ - protected $coreMiddleware; - - /** - * @var array - */ - protected $exceptionHandlers; - - /** - * @var \Psr\Container\ContainerInterface + * @var ContainerInterface */ protected $container; @@ -61,27 +45,48 @@ class Server implements OnRequestInterface, MiddlewareInitializerInterface */ protected $dispatcher; + /** + * @var ExceptionHandlerDispatcher + */ + protected $exceptionHandlerDispatcher; + + /** + * @var array + */ + protected $middlewares; + + /** + * @var CoreMiddlewareInterface + */ + protected $coreMiddleware; + + /** + * @var array + */ + protected $exceptionHandlers; + + /** + * @var Dispatcher + */ + protected $routerDispatcher; + /** * @var string */ protected $serverName; - public function __construct( - string $serverName, - string $coreHandler, - ContainerInterface $container, - $dispatcher - ) { - $this->serverName = $serverName; - $this->coreHandler = $coreHandler; + public function __construct(ContainerInterface $container, HttpDispatcher $dispatcher, ExceptionHandlerDispatcher $exceptionHandlerDispatcher) + { $this->container = $container; $this->dispatcher = $dispatcher; + $this->exceptionHandlerDispatcher = $exceptionHandlerDispatcher; } public function initCoreMiddleware(string $serverName): void { $this->serverName = $serverName; $this->coreMiddleware = $this->createCoreMiddleware(); + $this->routerDispatcher = $this->createDispatcher($serverName); $config = $this->container->get(ConfigInterface::class); $this->middlewares = $config->get('middlewares.' . $serverName, []); @@ -93,13 +98,19 @@ class Server implements OnRequestInterface, MiddlewareInitializerInterface try { [$psr7Request, $psr7Response] = $this->initRequestAndResponse($request, $response); - $middlewares = array_merge($this->middlewares, MiddlewareManager::get($this->serverName, $psr7Request->getUri()->getPath(), $psr7Request->getMethod())); + $psr7Request = $this->coreMiddleware->dispatch($psr7Request); + /** @var Dispatched $dispatched */ + $dispatched = $psr7Request->getAttribute(Dispatched::class); + $middlewares = $this->middlewares; + if ($dispatched->isFound()) { + $registedMiddlewares = MiddlewareManager::get($this->serverName, $dispatched->handler->route, $psr7Request->getMethod()); + $middlewares = array_merge($middlewares, $registedMiddlewares); + } $psr7Response = $this->dispatcher->dispatch($psr7Request, $middlewares, $this->coreMiddleware); } catch (Throwable $throwable) { // Delegate the exception to exception handler. - $exceptionHandlerDispatcher = $this->container->get(ExceptionHandlerDispatcher::class); - $psr7Response = $exceptionHandlerDispatcher->dispatch($throwable, $this->exceptionHandlers); + $psr7Response = $this->exceptionHandlerDispatcher->dispatch($throwable, $this->exceptionHandlers); } finally { // Send the Response to client. if (! isset($psr7Response) || ! $psr7Response instanceof Psr7Response) { @@ -123,6 +134,12 @@ class Server implements OnRequestInterface, MiddlewareInitializerInterface return $this; } + protected function createDispatcher(string $serverName): Dispatcher + { + $factory = $this->container->get(DispatcherFactory::class); + return $factory->getDispatcher($serverName); + } + protected function getDefaultExceptionHandler(): array { return [ @@ -130,10 +147,9 @@ class Server implements OnRequestInterface, MiddlewareInitializerInterface ]; } - protected function createCoreMiddleware(): MiddlewareInterface + protected function createCoreMiddleware(): CoreMiddlewareInterface { - $coreHandler = $this->coreHandler; - return new $coreHandler($this->container, $this->serverName); + return new CoreMiddleware($this->container, $this->serverName); } protected function initRequestAndResponse(SwooleRequest $request, SwooleResponse $response): array diff --git a/src/http-server/src/ServerFactory.php b/src/http-server/src/ServerFactory.php deleted file mode 100644 index 42323ea11..000000000 --- a/src/http-server/src/ServerFactory.php +++ /dev/null @@ -1,29 +0,0 @@ -coreMiddleware, $container, $container->get(HttpDispatcher::class)); - } -} diff --git a/src/http-server/tests/CoreMiddlewareTest.php b/src/http-server/tests/CoreMiddlewareTest.php index 2c2d97cf2..c4c4719b6 100644 --- a/src/http-server/tests/CoreMiddlewareTest.php +++ b/src/http-server/tests/CoreMiddlewareTest.php @@ -15,8 +15,12 @@ namespace HyperfTest\HttpServer; use Hyperf\Contract\NormalizerInterface; use Hyperf\Di\MethodDefinitionCollector; use Hyperf\Di\MethodDefinitionCollectorInterface; +use Hyperf\HttpMessage\Server\Request; +use Hyperf\HttpMessage\Uri\Uri; use Hyperf\HttpServer\CoreMiddleware; +use Hyperf\HttpServer\Router\Dispatched; use Hyperf\HttpServer\Router\DispatcherFactory; +use Hyperf\HttpServer\Router\Handler; use Hyperf\Utils\Contracts\Arrayable; use Hyperf\Utils\Contracts\Jsonable; use Hyperf\Utils\Serializer\SimpleNormalizer; @@ -99,6 +103,47 @@ class CoreMiddlewareTest extends TestCase $this->assertSame('text/plain', $response->getHeaderLine('content-type')); } + public function testDispatch() + { + $container = $this->getContainer(); + + $router = $container->get(DispatcherFactory::class)->getRouter('http'); + $router->addRoute('GET', '/user', 'UserController::index'); + $router->addRoute('GET', '/user/{id:\d+}', 'UserController::info'); + + $middleware = new CoreMiddleware($container, 'http'); + + $request = new Request('GET', new Uri('/user')); + $request = $middleware->dispatch($request); + $dispatched = $request->getAttribute(Dispatched::class); + $this->assertInstanceOf(Request::class, $request); + $this->assertInstanceOf(Dispatched::class, $dispatched); + $this->assertInstanceOf(Handler::class, $dispatched->handler); + $this->assertSame($dispatched, $request->getAttribute(Dispatched::class)); + $this->assertSame('/user', $dispatched->handler->route); + $this->assertSame('UserController::index', $dispatched->handler->callback); + $this->assertTrue($dispatched->isFound()); + + $request = new Request('GET', new Uri('/user/123')); + $request = $middleware->dispatch($request); + $dispatched = $request->getAttribute(Dispatched::class); + $this->assertInstanceOf(Request::class, $request); + $this->assertInstanceOf(Dispatched::class, $dispatched); + $this->assertInstanceOf(Handler::class, $dispatched->handler); + $this->assertSame($dispatched, $request->getAttribute(Dispatched::class)); + $this->assertSame('/user/{id:\d+}', $dispatched->handler->route); + $this->assertSame('UserController::info', $dispatched->handler->callback); + $this->assertTrue($dispatched->isFound()); + + $request = new Request('GET', new Uri('/users')); + $request = $middleware->dispatch($request); + $dispatched = $request->getAttribute(Dispatched::class); + $this->assertInstanceOf(Request::class, $request); + $this->assertInstanceOf(Dispatched::class, $dispatched); + $this->assertSame($dispatched, $request->getAttribute(Dispatched::class)); + $this->assertFalse($dispatched->isFound()); + } + protected function getContainer() { $container = Mockery::mock(ContainerInterface::class); diff --git a/src/http-server/tests/ResponseTest.php b/src/http-server/tests/ResponseTest.php index cded50ff2..73489f7ed 100644 --- a/src/http-server/tests/ResponseTest.php +++ b/src/http-server/tests/ResponseTest.php @@ -12,9 +12,11 @@ declare(strict_types=1); namespace HyperfTest\HttpServer; +use Hyperf\HttpMessage\Cookie\Cookie; use Hyperf\HttpMessage\Stream\SwooleStream; use Hyperf\HttpMessage\Uri\Uri; use Hyperf\HttpServer\Contract\RequestInterface; +use Hyperf\HttpServer\Contract\ResponseInterface; use Hyperf\HttpServer\Response; use Hyperf\Utils\ApplicationContext; use Hyperf\Utils\Context; @@ -24,6 +26,7 @@ use Mockery; use PHPUnit\Framework\TestCase; use Psr\Container\ContainerInterface; use Psr\Http\Message\ResponseInterface as PsrResponseInterface; +use Swoole\Http\Response as SwooleResponse; /** * @internal @@ -187,5 +190,49 @@ class ResponseTest extends TestCase $response = $response->withBody(new SwooleStream('xxx')); $this->assertInstanceOf(PsrResponseInterface::class, $response); + $this->assertInstanceOf(ResponseInterface::class, $response); + } + + public function testCookiesAndHeaders() + { + $container = Mockery::mock(ContainerInterface::class); + ApplicationContext::setContainer($container); + + $swooleResponse = Mockery::mock(SwooleResponse::class); + $id = uniqid(); + $cookie1 = new Cookie('Name', 'Hyperf'); + $cookie2 = new Cookie('Request-Id', $id); + $swooleResponse->shouldReceive('status')->with(Mockery::any())->andReturnUsing(function ($code) { + $this->assertSame($code, 200); + }); + $swooleResponse->shouldReceive('header')->withAnyArgs()->twice()->andReturnUsing(function ($name, $value) { + if ($name == 'X-Token') { + $this->assertSame($value, 'xxx'); + } + return true; + }); + $swooleResponse->shouldReceive('rawcookie')->withAnyArgs()->twice()->andReturnUsing(function ($name, $value, ...$args) use ($id) { + $this->assertTrue($name == 'Name' || $name == 'Request-Id'); + $this->assertTrue($value == 'Hyperf' || $value == $id); + return true; + }); + $swooleResponse->shouldReceive('end')->once()->andReturn(true); + + Context::set(PsrResponseInterface::class, $psrResponse = new \Hyperf\HttpMessage\Server\Response($swooleResponse)); + + $response = new Response(); + $response = $response->withCookie($cookie1)->withCookie($cookie2)->withHeader('X-Token', 'xxx')->withStatus(200); + + $this->assertInstanceOf(Response::class, $response); + $this->assertInstanceOf(ResponseInterface::class, $response); + + $response = $response->raw('Hello Hyperf.'); + $this->assertNotInstanceOf(Response::class, $response); + $this->assertNotInstanceOf(ResponseInterface::class, $response); + $this->assertInstanceOf(PsrResponseInterface::class, $response); + + $response->send(); + + $this->assertSame($psrResponse, Context::get(PsrResponseInterface::class)); } } diff --git a/src/http-server/tests/Router/RouteCollectorTest.php b/src/http-server/tests/Router/RouteCollectorTest.php index 44799be00..cdf78a3ff 100644 --- a/src/http-server/tests/Router/RouteCollectorTest.php +++ b/src/http-server/tests/Router/RouteCollectorTest.php @@ -46,10 +46,10 @@ class RouteCollectorTest extends TestCase }); $data = $collector->getData()[0]; - $this->assertSame('Handler::Get', $data['GET']['/']); - $this->assertSame('Handler::ApiGet', $data['GET']['/api/']); - $this->assertSame('Handler::Post', $data['POST']['/']); - $this->assertSame('Handler::ApiPost', $data['POST']['/api/']); + $this->assertSame('Handler::Get', $data['GET']['/']->callback); + $this->assertSame('Handler::ApiGet', $data['GET']['/api/']->callback); + $this->assertSame('Handler::Post', $data['POST']['/']->callback); + $this->assertSame('Handler::ApiPost', $data['POST']['/api/']->callback); } public function testAddGroupMiddleware() @@ -71,16 +71,20 @@ class RouteCollectorTest extends TestCase $collector->post('/', 'Handler::Post', [ 'middleware' => ['PostMiddleware'], ]); + $collector->post('/user/{id:\d+}', 'Handler::Post', [ + 'middleware' => ['PostMiddleware'], + ]); $data = $collector->getData()[0]; - $this->assertSame('Handler::Get', $data['GET']['/']); - $this->assertSame('Handler::ApiGet', $data['GET']['/api/']); - $this->assertSame('Handler::Post', $data['POST']['/']); + $this->assertSame('Handler::Get', $data['GET']['/']->callback); + $this->assertSame('Handler::ApiGet', $data['GET']['/api/']->callback); + $this->assertSame('Handler::Post', $data['POST']['/']->callback); $middle = MiddlewareManager::$container; $this->assertSame(['GetMiddleware'], $middle['http']['/']['GET']); $this->assertSame(['PostMiddleware'], $middle['http']['/']['POST']); $this->assertSame(['ApiGetMiddleware', 'ApiSelfGetMiddleware'], $middle['http']['/api/']['GET']); + $this->assertSame(['PostMiddleware'], $middle['http']['/user/{id:\d+}']['POST']); } public function testAddGroupMiddlewareFromAnotherServer() diff --git a/src/http-server/tests/Stub/DemoController.php b/src/http-server/tests/Stub/DemoController.php index 9561eae5e..2219d8b33 100644 --- a/src/http-server/tests/Stub/DemoController.php +++ b/src/http-server/tests/Stub/DemoController.php @@ -18,13 +18,13 @@ class DemoController { } - public function index(int $id, string $name = 'Hyperf', array $params = []) - { - return $this->__return($id, $name, $params); - } - public function __return(...$args) { return $args; } + + public function index(int $id, string $name = 'Hyperf', array $params = []) + { + return $this->__return($id, $name, $params); + } } diff --git a/src/json-rpc/composer.json b/src/json-rpc/composer.json index a4bd74bef..57819055b 100644 --- a/src/json-rpc/composer.json +++ b/src/json-rpc/composer.json @@ -14,11 +14,11 @@ "php": ">=7.2", "psr/container": "^1.0", "psr/log": "^1.0", - "hyperf/contract": "~1.0.0", - "hyperf/load-balancer": "~1.0.0", - "hyperf/http-message": "~1.0.0", - "hyperf/rpc": "~1.0.0", - "hyperf/utils": "~1.0.0" + "hyperf/contract": "~1.1.0", + "hyperf/load-balancer": "~1.1.0", + "hyperf/http-message": "~1.1.0", + "hyperf/rpc": "~1.1.0", + "hyperf/utils": "~1.1.0" }, "require-dev": { "malukenho/docheader": "^0.1.6", @@ -48,7 +48,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.1-dev" }, "hyperf": { "config": "Hyperf\\JsonRpc\\ConfigProvider" diff --git a/src/json-rpc/src/ConfigProvider.php b/src/json-rpc/src/ConfigProvider.php index 20df9878d..5bf155385 100644 --- a/src/json-rpc/src/ConfigProvider.php +++ b/src/json-rpc/src/ConfigProvider.php @@ -22,8 +22,6 @@ class ConfigProvider { return [ 'dependencies' => [ - TcpServer::class => TcpServerFactory::class, - HttpServer::class => HttpServerFactory::class, DataFormatter::class => DataFormatterFactory::class, ], 'commands' => [ diff --git a/src/json-rpc/src/CoreMiddleware.php b/src/json-rpc/src/CoreMiddleware.php index 81a00d1a9..5958eee3b 100644 --- a/src/json-rpc/src/CoreMiddleware.php +++ b/src/json-rpc/src/CoreMiddleware.php @@ -13,6 +13,7 @@ declare(strict_types=1); namespace Hyperf\JsonRpc; use Closure; +use Hyperf\HttpServer\Router\Dispatched; use Hyperf\Rpc\Protocol; use Psr\Container\ContainerInterface; use Psr\Http\Message\ResponseInterface; @@ -34,12 +35,12 @@ class CoreMiddleware extends \Hyperf\RpcServer\CoreMiddleware ]); } - protected function handleFound(array $routes, ServerRequestInterface $request) + protected function handleFound(Dispatched $dispatched, ServerRequestInterface $request) { - if ($routes[1] instanceof Closure) { - $response = call($routes[1]); + if ($dispatched->handler->callback instanceof Closure) { + $response = call($dispatched->handler->callback); } else { - [$controller, $action] = $this->prepareHandler($routes[1]); + [$controller, $action] = $this->prepareHandler($dispatched->handler->callback); $controllerInstance = $this->container->get($controller); if (! method_exists($controller, $action)) { // Route found, but the handler does not exist. diff --git a/src/json-rpc/src/HttpCoreMiddleware.php b/src/json-rpc/src/HttpCoreMiddleware.php index ae49a7c13..809d21c1f 100644 --- a/src/json-rpc/src/HttpCoreMiddleware.php +++ b/src/json-rpc/src/HttpCoreMiddleware.php @@ -14,9 +14,6 @@ namespace Hyperf\JsonRpc; use Psr\Http\Message\ServerRequestInterface; -/** - * {@inheritdoc} - */ class HttpCoreMiddleware extends CoreMiddleware { protected function handleNotFound(ServerRequestInterface $request) diff --git a/src/json-rpc/src/HttpServer.php b/src/json-rpc/src/HttpServer.php index ff8dcf0bf..a45a3d9ba 100644 --- a/src/json-rpc/src/HttpServer.php +++ b/src/json-rpc/src/HttpServer.php @@ -12,18 +12,20 @@ declare(strict_types=1); namespace Hyperf\JsonRpc; +use Hyperf\ExceptionHandler\ExceptionHandlerDispatcher; use Hyperf\HttpMessage\Server\Request as Psr7Request; use Hyperf\HttpMessage\Server\Response as Psr7Response; +use Hyperf\HttpServer\Contract\CoreMiddlewareInterface; use Hyperf\HttpServer\Server; use Hyperf\JsonRpc\Exception\Handler\HttpExceptionHandler; use Hyperf\Rpc\Protocol; use Hyperf\Rpc\ProtocolManager; +use Hyperf\RpcServer\RequestDispatcher; use Hyperf\Utils\Context; use Psr\Container\ContainerInterface; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; -use Psr\Http\Server\MiddlewareInterface; use Swoole\Http\Request as SwooleRequest; use Swoole\Http\Response as SwooleResponse; @@ -35,7 +37,7 @@ class HttpServer extends Server protected $protocol; /** - * @var \Hyperf\Rpc\Contract\PackerInterface + * @var \Hyperf\Contract\PackerInterface */ protected $packer; @@ -45,13 +47,12 @@ class HttpServer extends Server protected $responseBuilder; public function __construct( - string $serverName, - string $coreHandler, ContainerInterface $container, - $dispatcher, + RequestDispatcher $dispatcher, + ExceptionHandlerDispatcher $exceptionHandlerDispatcher, ProtocolManager $protocolManager ) { - parent::__construct($serverName, $coreHandler, $container, $dispatcher); + parent::__construct($container, $dispatcher, $exceptionHandlerDispatcher); $this->protocol = new Protocol($container, $protocolManager, 'jsonrpc-http'); $this->packer = $this->protocol->getPacker(); $this->responseBuilder = make(ResponseBuilder::class, [ @@ -67,10 +68,9 @@ class HttpServer extends Server ]; } - protected function createCoreMiddleware(): MiddlewareInterface + protected function createCoreMiddleware(): CoreMiddlewareInterface { - $coreHandler = $this->coreHandler; - return new $coreHandler($this->container, $this->protocol, $this->serverName); + return new HttpCoreMiddleware($this->container, $this->protocol, $this->serverName); } protected function initRequestAndResponse(SwooleRequest $request, SwooleResponse $response): array diff --git a/src/json-rpc/src/HttpServerFactory.php b/src/json-rpc/src/HttpServerFactory.php deleted file mode 100644 index 5c162ddfb..000000000 --- a/src/json-rpc/src/HttpServerFactory.php +++ /dev/null @@ -1,32 +0,0 @@ -get(RequestDispatcher::class); - $protocolManager = $container->get(ProtocolManager::class); - return new HttpServer('jsonrpc-http', $this->coreMiddleware, $container, $dispatcher, $protocolManager); - } -} diff --git a/src/json-rpc/src/JsonRpcTransporter.php b/src/json-rpc/src/JsonRpcTransporter.php index a3f081fd7..a5e901d0a 100644 --- a/src/json-rpc/src/JsonRpcTransporter.php +++ b/src/json-rpc/src/JsonRpcTransporter.php @@ -43,6 +43,12 @@ class JsonRpcTransporter implements TransporterInterface */ private $recvTimeout = 5; + /** + * TODO: Set config. + * @var array + */ + private $config; + public function send(string $data) { $client = retry(2, function () use ($data) { @@ -67,7 +73,7 @@ class JsonRpcTransporter implements TransporterInterface $result = $client->connect($node->host, $node->port, $this->connectTimeout); if ($result === false && ($client->errCode == 114 or $client->errCode == 115)) { // Force close and reconnect to server. - $client->close(true); + $client->close(); throw new RuntimeException('Connect to server failed.'); } return $client; diff --git a/src/json-rpc/src/TcpServer.php b/src/json-rpc/src/TcpServer.php index 0d4769b8f..6830a44a3 100644 --- a/src/json-rpc/src/TcpServer.php +++ b/src/json-rpc/src/TcpServer.php @@ -13,17 +13,20 @@ declare(strict_types=1); namespace Hyperf\JsonRpc; use Hyperf\Contract\PackerInterface; +use Hyperf\Contract\StdoutLoggerInterface; +use Hyperf\ExceptionHandler\ExceptionHandlerDispatcher; use Hyperf\HttpMessage\Server\Request as Psr7Request; use Hyperf\HttpMessage\Server\Response as Psr7Response; use Hyperf\HttpMessage\Uri\Uri; +use Hyperf\HttpServer\Contract\CoreMiddlewareInterface; use Hyperf\Rpc\Protocol; use Hyperf\Rpc\ProtocolManager; +use Hyperf\RpcServer\RequestDispatcher; use Hyperf\RpcServer\Server; use Hyperf\Server\ServerManager; use Psr\Container\ContainerInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; -use Psr\Log\LoggerInterface; use Swoole\Server as SwooleServer; class TcpServer extends Server @@ -39,15 +42,16 @@ class TcpServer extends Server protected $packer; public function __construct( - string $serverName, - string $coreHandler, ContainerInterface $container, - $dispatcher, - LoggerInterface $logger, - ProtocolManager $protocolManager + RequestDispatcher $dispatcher, + ExceptionHandlerDispatcher $exceptionDispatcher, + ProtocolManager $protocolManager, + StdoutLoggerInterface $logger ) { $protocol = new Protocol($container, $protocolManager, 'jsonrpc'); - parent::__construct($serverName, $coreHandler, $container, $protocol, $dispatcher, $logger); + + parent::__construct($container, $dispatcher, $exceptionDispatcher, $protocol, $logger); + $this->packer = $protocol->getPacker(); $this->responseBuilder = make(ResponseBuilder::class, [ 'dataFormatter' => $protocol->getDataFormatter(), @@ -55,6 +59,11 @@ class TcpServer extends Server ]); } + protected function createCoreMiddleware(): CoreMiddlewareInterface + { + return new CoreMiddleware($this->container, $this->protocol, $this->serverName); + } + protected function buildResponse(int $fd, SwooleServer $server): ResponseInterface { $response = new Psr7Response(); diff --git a/src/json-rpc/src/TcpServerFactory.php b/src/json-rpc/src/TcpServerFactory.php deleted file mode 100644 index 3877b9698..000000000 --- a/src/json-rpc/src/TcpServerFactory.php +++ /dev/null @@ -1,34 +0,0 @@ -get(RequestDispatcher::class); - $logger = $container->get(StdoutLoggerInterface::class); - $protocolManager = $container->get(ProtocolManager::class); - return new TcpServer('jsonrpc', $this->coreMiddleware, $container, $dispatcher, $logger, $protocolManager); - } -} diff --git a/src/json-rpc/tests/AnyParamCoreMiddlewareTest.php b/src/json-rpc/tests/AnyParamCoreMiddlewareTest.php index 297ebed61..ba8837815 100644 --- a/src/json-rpc/tests/AnyParamCoreMiddlewareTest.php +++ b/src/json-rpc/tests/AnyParamCoreMiddlewareTest.php @@ -24,6 +24,7 @@ use Hyperf\HttpMessage\Server\Request; use Hyperf\HttpMessage\Uri\Uri; use Hyperf\JsonRpc\CoreMiddleware; use Hyperf\JsonRpc\DataFormatter; +use Hyperf\JsonRpc\JsonRpcHttpTransporter; use Hyperf\JsonRpc\JsonRpcTransporter; use Hyperf\JsonRpc\NormalizeDataFormatter; use Hyperf\JsonRpc\PathGenerator; @@ -31,6 +32,7 @@ use Hyperf\JsonRpc\ResponseBuilder; use Hyperf\Logger\Logger; use Hyperf\Rpc\Protocol; use Hyperf\Rpc\ProtocolManager; +use Hyperf\RpcServer\RequestDispatcher; use Hyperf\RpcServer\Router\DispatcherFactory; use Hyperf\Utils\ApplicationContext; use Hyperf\Utils\Context; @@ -65,6 +67,8 @@ class AnyParamCoreMiddlewareTest extends TestCase ['value' => 1], ['value' => 2], ]); + + $request = $middleware->dispatch($request); Context::set(ResponseInterface::class, new Response()); $response = $middleware->process($request, $handler); @@ -88,6 +92,7 @@ class AnyParamCoreMiddlewareTest extends TestCase ->withParsedBody([3, 0]); Context::set(ResponseInterface::class, new Response()); + $request = $middleware->dispatch($request); try { $response = $middleware->process($request, $handler); } catch (\Throwable $exception) { @@ -119,6 +124,12 @@ class AnyParamCoreMiddlewareTest extends TestCase 'path-generator' => PathGenerator::class, 'data-formatter' => DataFormatter::class, ], + 'jsonrpc-http' => [ + 'packer' => JsonPacker::class, + 'transporter' => JsonRpcHttpTransporter::class, + 'path-generator' => PathGenerator::class, + 'data-formatter' => DataFormatter::class, + ], ], ])); $container->shouldReceive('has')->andReturn(true); @@ -146,6 +157,7 @@ class AnyParamCoreMiddlewareTest extends TestCase ->andReturnUsing(function ($class, $args) { return new ResponseBuilder(...array_values($args)); }); + $container->shouldReceive('get')->with(RequestDispatcher::class)->andReturn(new RequestDispatcher($container)); ApplicationContext::setContainer($container); return $container; diff --git a/src/json-rpc/tests/CoreMiddlewareTest.php b/src/json-rpc/tests/CoreMiddlewareTest.php index 27e31aae2..520864a20 100644 --- a/src/json-rpc/tests/CoreMiddlewareTest.php +++ b/src/json-rpc/tests/CoreMiddlewareTest.php @@ -64,6 +64,7 @@ class CoreMiddlewareTest extends TestCase ->withParsedBody([1, 2]); Context::set(ResponseInterface::class, new Response()); + $request = $middleware->dispatch($request); $response = $middleware->process($request, $handler); $this->assertEquals(200, $response->getStatusCode()); $ret = json_decode((string) $response->getBody(), true); @@ -86,6 +87,8 @@ class CoreMiddlewareTest extends TestCase ->withParsedBody([3, 0]); Context::set(ResponseInterface::class, new Response()); + $request = $middleware->dispatch($request); + try { $response = $middleware->process($request, $handler); } catch (\Throwable $exception) { @@ -115,6 +118,8 @@ class CoreMiddlewareTest extends TestCase ->withParsedBody([3, 0]); Context::set(ResponseInterface::class, new Response()); + $request = $middleware->dispatch($request); + try { $response = $middleware->process($request, $handler); } catch (\Throwable $exception) { diff --git a/src/json-rpc/tests/RpcServiceClientTest.php b/src/json-rpc/tests/RpcServiceClientTest.php index 34029dd5a..69c5a2563 100644 --- a/src/json-rpc/tests/RpcServiceClientTest.php +++ b/src/json-rpc/tests/RpcServiceClientTest.php @@ -16,9 +16,9 @@ use Hyperf\Config\Config; use Hyperf\Contract\ConfigInterface; use Hyperf\Contract\NormalizerInterface; use Hyperf\Contract\StdoutLoggerInterface; -use Hyperf\Di\Annotation\Scanner; use Hyperf\Di\Container; use Hyperf\Di\Definition\DefinitionSource; +use Hyperf\Di\Definition\ScanConfig; use Hyperf\Di\MethodDefinitionCollector; use Hyperf\Di\MethodDefinitionCollectorInterface; use Hyperf\JsonRpc\DataFormatter; @@ -142,7 +142,7 @@ class RpcServiceClientTest extends TestCase JsonRpcTransporter::class => function () use ($transporter) { return $transporter; }, - ], [], new Scanner())); + ], new ScanConfig())); ApplicationContext::setContainer($container); return $container; } diff --git a/src/load-balancer/composer.json b/src/load-balancer/composer.json index 880ef2606..9e8ddaba0 100644 --- a/src/load-balancer/composer.json +++ b/src/load-balancer/composer.json @@ -37,7 +37,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.1-dev" }, "hyperf": { "config": "Hyperf\\LoadBalancer\\ConfigProvider" diff --git a/src/logger/composer.json b/src/logger/composer.json index 9f57ef603..434dfba6f 100644 --- a/src/logger/composer.json +++ b/src/logger/composer.json @@ -13,8 +13,8 @@ "php": ">=7.2", "psr/log": "^1.0", "psr/container": "^1.0", - "hyperf/contract": "~1.0.0", - "hyperf/utils": "~1.0.0", + "hyperf/contract": "~1.1.0", + "hyperf/utils": "~1.1.0", "monolog/monolog": "^1.24" }, "require-dev": { @@ -40,7 +40,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.1-dev" }, "hyperf": { "config": "Hyperf\\Logger\\ConfigProvider" diff --git a/src/memory/composer.json b/src/memory/composer.json index c70f69090..c104bfb17 100644 --- a/src/memory/composer.json +++ b/src/memory/composer.json @@ -35,7 +35,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.1-dev" }, "hyperf": { "config": "Hyperf\\Memory\\ConfigProvider" diff --git a/src/model-cache/composer.json b/src/model-cache/composer.json index 9331ae9f7..8872f84ae 100644 --- a/src/model-cache/composer.json +++ b/src/model-cache/composer.json @@ -13,12 +13,12 @@ "php": ">=7.2", "psr/simple-cache": "^1.0", "psr/container": "^1.0", - "hyperf/contract": "~1.0.0", - "hyperf/db-connection": "~1.0.0", - "hyperf/utils": "~1.0.0" + "hyperf/contract": "~1.1.0", + "hyperf/db-connection": "~1.1.0", + "hyperf/utils": "~1.1.0" }, "require-dev": { - "hyperf/event": "~1.0.0", + "hyperf/event": "~1.1.0", "malukenho/docheader": "^0.1.6", "mockery/mockery": "^1.0", "phpunit/phpunit": "^7.0.0", @@ -42,7 +42,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.1-dev" }, "hyperf": { "config": "Hyperf\\ModelCache\\ConfigProvider" diff --git a/src/model-listener/.gitattributes b/src/model-listener/.gitattributes new file mode 100644 index 000000000..bdd4ea29c --- /dev/null +++ b/src/model-listener/.gitattributes @@ -0,0 +1 @@ +/tests export-ignore \ No newline at end of file diff --git a/src/model-listener/LICENSE.md b/src/model-listener/LICENSE.md new file mode 100644 index 000000000..eb14702a3 --- /dev/null +++ b/src/model-listener/LICENSE.md @@ -0,0 +1,9 @@ +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. \ No newline at end of file diff --git a/src/model-listener/composer.json b/src/model-listener/composer.json new file mode 100644 index 000000000..2b938586d --- /dev/null +++ b/src/model-listener/composer.json @@ -0,0 +1,58 @@ +{ + "name": "hyperf/model-listener", + "description": "A model listener for Hyperf.", + "license": "MIT", + "keywords": [ + "php", + "swoole", + "hyperf", + "model-listener" + ], + "support": { + }, + "require": { + "php": ">=7.2", + "hyperf/contract": "~1.1.0", + "hyperf/database": "~1.1.0", + "hyperf/di": "~1.1.0", + "hyperf/event": "~1.1.0", + "hyperf/utils": "~1.1.0", + "psr/container": "^1.0", + "psr/log": "^1.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.9", + "malukenho/docheader": "^0.1.6", + "mockery/mockery": "^1.0", + "phpunit/phpunit": "^7.0" + }, + "suggest": { + }, + "autoload": { + "psr-4": { + "Hyperf\\ModelListener\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "HyperfTest\\ModelListener\\": "tests/" + } + }, + "config": { + "sort-packages": true + }, + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + }, + "hyperf": { + "config": "Hyperf\\ModelListener\\ConfigProvider" + } + }, + "bin": [ + ], + "scripts": { + "cs-fix": "php-cs-fixer fix $1", + "test": "phpunit --colors=always" + } +} diff --git a/src/model-listener/src/AbstractListener.php b/src/model-listener/src/AbstractListener.php new file mode 100644 index 000000000..40c813a24 --- /dev/null +++ b/src/model-listener/src/AbstractListener.php @@ -0,0 +1,44 @@ +models = [$value]; + } elseif (is_array($value) && ! Arr::isAssoc($value)) { + $this->models = $value; + } + } + } + + public function collectClass(string $className): void + { + parent::collectClass($className); + + foreach ($this->models as $model) { + ListenerCollector::register($model, $className); + } + } +} diff --git a/src/model-listener/src/Collector/ListenerCollector.php b/src/model-listener/src/Collector/ListenerCollector.php new file mode 100644 index 000000000..73d035cda --- /dev/null +++ b/src/model-listener/src/Collector/ListenerCollector.php @@ -0,0 +1,58 @@ + [ + ], + 'commands' => [ + ], + 'scan' => [ + 'paths' => [ + __DIR__, + ], + 'collectors' => [ + ListenerCollector::class, + ], + ], + ]; + } +} diff --git a/src/model-listener/src/Listener/ModelEventListener.php b/src/model-listener/src/Listener/ModelEventListener.php new file mode 100644 index 000000000..b9e09589b --- /dev/null +++ b/src/model-listener/src/Listener/ModelEventListener.php @@ -0,0 +1,63 @@ +container = $container; + } + + public function listen(): array + { + return [ + Event::class, + ]; + } + + /** + * @param Event $event + */ + public function process(object $event) + { + $model = $event->getModel(); + $modelName = get_class($model); + + $listeners = ListenerCollector::getListenersForModel($modelName); + foreach ($listeners as $name) { + if (! $this->container->has($name)) { + continue; + } + + $listener = $this->container->get($name); + if (method_exists($listener, $event->getMethod())) { + $listener->{$event->getMethod()}($event); + } + } + } +} diff --git a/src/model-listener/src/Listener/ModelHookEventListener.php b/src/model-listener/src/Listener/ModelHookEventListener.php new file mode 100644 index 000000000..dd87262a6 --- /dev/null +++ b/src/model-listener/src/Listener/ModelHookEventListener.php @@ -0,0 +1,38 @@ +handle(); + } +} diff --git a/src/model-listener/tests/AnnotationTest.php b/src/model-listener/tests/AnnotationTest.php new file mode 100644 index 000000000..f3f3c59f3 --- /dev/null +++ b/src/model-listener/tests/AnnotationTest.php @@ -0,0 +1,55 @@ + ModelStub::class]); + $annotation->collectClass('Foo'); + + $this->assertSame(['Foo'], ListenerCollector::getListenersForModel(ModelStub::class)); + } + + public function testAnnotationCollectAssocArray() + { + $annotation = new ModelListener(['models' => [ModelStub::class]]); + $annotation->collectClass('Foo'); + $this->assertSame(['Foo'], ListenerCollector::getListenersForModel(ModelStub::class)); + } + + public function testAnnotationCollectArray() + { + $annotation = new ModelListener(['value' => [ModelStub::class, 'ModelStub']]); + $annotation->collectClass('Foo'); + $this->assertSame(['Foo'], ListenerCollector::getListenersForModel(ModelStub::class)); + $this->assertSame(['Foo'], ListenerCollector::getListenersForModel('ModelStub')); + } +} diff --git a/src/model-listener/tests/ListenerCollectorTest.php b/src/model-listener/tests/ListenerCollectorTest.php new file mode 100644 index 000000000..aceb46335 --- /dev/null +++ b/src/model-listener/tests/ListenerCollectorTest.php @@ -0,0 +1,56 @@ +assertSame(['ObserverClass'], ListenerCollector::getListenersForModel($class)); + } + + public function testRegisterMoreThanOneObserver() + { + $class = 'HyperfTest\ModelListener\Stub\ModelStub'; + ListenerCollector::register($class, 'ObserverClass'); + ListenerCollector::register($class, 'ObserverClass2'); + ListenerCollector::register($class, 'ObserverClass3'); + $this->assertSame(['ObserverClass', 'ObserverClass2', 'ObserverClass3'], ListenerCollector::getListenersForModel($class)); + } + + public function testClearObservables() + { + $class = 'HyperfTest\ModelListener\Stub\ModelStub'; + ListenerCollector::register($class, 'ObserverClass'); + + ListenerCollector::clearListeners(); + + $this->assertSame([], ListenerCollector::getListenersForModel($class)); + } +} diff --git a/src/model-listener/tests/ModelObserverTest.php b/src/model-listener/tests/ModelObserverTest.php new file mode 100644 index 000000000..f3c28113e --- /dev/null +++ b/src/model-listener/tests/ModelObserverTest.php @@ -0,0 +1,74 @@ +getContainer(); + $listenerProvider = new ListenerProvider(); + $listenerProvider->on(Event::class, [new ModelEventListener($container), 'process']); + + Register::setEventDispatcher(new EventDispatcher($listenerProvider)); + + $model = $this->getMockBuilder(ModelStub::class)->setMethods(['newModelQuery', 'updateTimestamps'])->getMock(); + $query = Mockery::mock(Builder::class); + $query->shouldReceive('where')->once()->with('id', '=', 1); + $query->shouldReceive('update')->once()->with(['foo' => 'bar'])->andReturn(1); + $model->expects($this->once())->method('newModelQuery')->will($this->returnValue($query)); + $model->expects($this->once())->method('updateTimestamps'); + + ListenerCollector::register(get_class($model), ModelListenerStub::class); + + $model->id = 1; + $model->syncOriginal(); + $model->foo = 'foo'; + $model->exists = true; + + $this->assertTrue($model->save()); + } + + protected function getContainer() + { + $container = Mockery::mock(ContainerInterface::class); + $container->shouldReceive('has')->andReturn(true); + $container->shouldReceive('get')->with(ModelListenerStub::class)->andReturn(new ModelListenerStub()); + + return $container; + } +} diff --git a/src/model-listener/tests/Stub/ModelListenerStub.php b/src/model-listener/tests/Stub/ModelListenerStub.php new file mode 100644 index 000000000..9fcf83946 --- /dev/null +++ b/src/model-listener/tests/Stub/ModelListenerStub.php @@ -0,0 +1,23 @@ +getModel()->foo = 'bar'; + } +} diff --git a/src/model-listener/tests/Stub/ModelStub.php b/src/model-listener/tests/Stub/ModelStub.php new file mode 100644 index 000000000..eea02b809 --- /dev/null +++ b/src/model-listener/tests/Stub/ModelStub.php @@ -0,0 +1,20 @@ +=7.2", - "hyperf/contract": "~1.0.0", - "hyperf/utils": "~1.0.0" + "hyperf/contract": "~1.1.0", + "hyperf/utils": "~1.1.0" }, "require-dev": { - "hyperf/event": "~1.0.0", - "hyperf/http-server": "~1.0.0", - "hyperf/framework": "~1.0.0", + "hyperf/event": "~1.1.0", + "hyperf/http-server": "~1.1.0", + "hyperf/framework": "~1.1.0", "malukenho/docheader": "^0.1.6", "mockery/mockery": "^1.0", "phpunit/phpunit": "^7.0.0", @@ -43,7 +43,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.1-dev" }, "hyperf": { "config": "Hyperf\\Paginator\\ConfigProvider" diff --git a/src/pool/.gitattributes b/src/pool/.gitattributes new file mode 100644 index 000000000..bdd4ea29c --- /dev/null +++ b/src/pool/.gitattributes @@ -0,0 +1 @@ +/tests export-ignore \ No newline at end of file diff --git a/src/pool/composer.json b/src/pool/composer.json index 181e305ed..ba49b49f1 100644 --- a/src/pool/composer.json +++ b/src/pool/composer.json @@ -13,8 +13,8 @@ "require": { "php": ">=7.2", "psr/container": "^1.0", - "hyperf/contract": "~1.0.0", - "hyperf/utils": "~1.0.0" + "hyperf/contract": "~1.1.0", + "hyperf/utils": "~1.1.0" }, "require-dev": { "malukenho/docheader": "^0.1.6", @@ -39,7 +39,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.1-dev" }, "hyperf": { "config": "Hyperf\\Pool\\ConfigProvider" diff --git a/src/pool/src/Frequency.php b/src/pool/src/Frequency.php new file mode 100644 index 000000000..8908ea4f7 --- /dev/null +++ b/src/pool/src/Frequency.php @@ -0,0 +1,92 @@ +beginTime = time(); + } + + public function hit(int $number = 1): bool + { + $this->flush(); + + $now = time(); + $hit = $this->hits[$now] ?? 0; + $this->hits[$now] = $number + $hit; + + return true; + } + + public function frequency(): float + { + $this->flush(); + + $hits = 0; + $count = 0; + foreach ($this->hits as $hit) { + ++$count; + $hits += $hit; + } + + return floatval($hits / $count); + } + + public function isLowFrequency(): bool + { + return $this->frequency() < $this->lowFrequency; + } + + protected function flush(): void + { + $now = time(); + $latest = $now - $this->time; + foreach ($this->hits as $time => $hit) { + if ($time < $latest) { + unset($this->hits[$time]); + } + } + + if (count($this->hits) < $this->time) { + $beginTime = max($this->beginTime, $latest); + for ($i = $beginTime; $i < $now; ++$i) { + $this->hits[$i] = $this->hits[$i] ?? 0; + } + } + } +} diff --git a/src/pool/tests/FrequencyTest.php b/src/pool/tests/FrequencyTest.php new file mode 100644 index 000000000..94a482906 --- /dev/null +++ b/src/pool/tests/FrequencyTest.php @@ -0,0 +1,75 @@ +setBeginTime($now - 4); + $frequency->setHits([ + $now => 1, + $now - 1 => 10, + $now - 2 => 10, + $now - 3 => 10, + $now - 4 => 10, + ]); + + $num = $frequency->frequency(); + $this->assertSame(41 / 5, $num); + + $frequency->hit(); + $num = $frequency->frequency(); + $this->assertSame(42 / 5, $num); + } + + public function testFrequencyHitOneSecondAfter() + { + $frequency = new FrequencyStub(); + $now = time(); + + $frequency->setBeginTime($now - 4); + $frequency->setHits([ + $now => 1, + $now - 1 => 10, + $now - 2 => 10, + $now - 4 => 10, + ]); + $num = $frequency->frequency(); + $this->assertSame(31 / 5, $num); + $frequency->hit(); + $num = $frequency->frequency(); + $this->assertSame(32 / 5, $num); + + $frequency->setHits([ + $now => 1, + $now - 1 => 10, + $now - 2 => 10, + $now - 3 => 10, + ]); + $num = $frequency->frequency(); + $this->assertSame(31 / 5, $num); + $frequency->hit(); + $num = $frequency->frequency(); + $this->assertSame(32 / 5, $num); + } +} diff --git a/src/pool/tests/Stub/FrequencyStub.php b/src/pool/tests/Stub/FrequencyStub.php new file mode 100644 index 000000000..a6eed5929 --- /dev/null +++ b/src/pool/tests/Stub/FrequencyStub.php @@ -0,0 +1,33 @@ +beginTime = $time; + } + + public function setHits($hits) + { + $this->hits = $hits; + } + + public function getHits() + { + return $this->hits; + } +} diff --git a/src/process/composer.json b/src/process/composer.json index a644f64e4..873bf227c 100644 --- a/src/process/composer.json +++ b/src/process/composer.json @@ -13,11 +13,11 @@ "php": ">=7.2", "psr/container": "^1.0", "psr/event-dispatcher": "^1.0", - "hyperf/contract": "~1.0.0" + "hyperf/contract": "~1.1.0" }, "require-dev": { - "hyperf/di": "~1.0.0", - "hyperf/event": "~1.0.0", + "hyperf/di": "~1.1.0", + "hyperf/event": "~1.1.0", "malukenho/docheader": "^0.1.6", "mockery/mockery": "^1.0", "phpunit/phpunit": "^7.0.0", @@ -43,7 +43,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.1-dev" }, "hyperf": { "config": "Hyperf\\Process\\ConfigProvider" diff --git a/src/rate-limit/composer.json b/src/rate-limit/composer.json index ca59a6b38..7c009f32d 100644 --- a/src/rate-limit/composer.json +++ b/src/rate-limit/composer.json @@ -14,7 +14,7 @@ "php": ">=7.2", "psr/container": "^1.0", "psr/simple-cache": "^1.0", - "hyperf/utils": "~1.0.0", + "hyperf/utils": "~1.1.0", "bandwidth-throttle/token-bucket": "^2.0" }, "require-dev": { @@ -22,10 +22,10 @@ "mockery/mockery": "^1.0", "phpunit/phpunit": "^7.0.0", "friendsofphp/php-cs-fixer": "^2.9", - "hyperf/contract": "~1.0.0", - "hyperf/di": "~1.0.0", - "hyperf/http-server": "~1.0.0", - "hyperf/redis": "~1.0.0" + "hyperf/contract": "~1.1.0", + "hyperf/di": "~1.1.0", + "hyperf/http-server": "~1.1.0", + "hyperf/redis": "~1.1.0" }, "suggest": { "hyperf/contract": "Required to use annotations.", @@ -47,7 +47,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.1-dev" }, "hyperf": { "config": "Hyperf\\RateLimit\\ConfigProvider" diff --git a/src/redis/composer.json b/src/redis/composer.json index 17b39f7e9..02ba71bcd 100644 --- a/src/redis/composer.json +++ b/src/redis/composer.json @@ -12,13 +12,13 @@ }, "require": { "php": ">=7.2", - "psr/container": "^1.0", - "hyperf/contract": "~1.0.0", - "hyperf/pool": "~1.0.0", - "hyperf/utils": "~1.0.0" + "hyperf/contract": "~1.1.0", + "hyperf/pool": "~1.1.0", + "hyperf/utils": "~1.1.0", + "psr/container": "^1.0" }, "require-dev": { - "hyperf/di": "~1.0.0", + "hyperf/di": "~1.1.0", "malukenho/docheader": "^0.1.6", "mockery/mockery": "^1.0", "phpunit/phpunit": "^7.0.0", @@ -42,7 +42,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.1-dev" }, "hyperf": { "config": "Hyperf\\Redis\\ConfigProvider" diff --git a/src/redis/src/Frequency.php b/src/redis/src/Frequency.php new file mode 100644 index 000000000..6d331514d --- /dev/null +++ b/src/redis/src/Frequency.php @@ -0,0 +1,19 @@ +config = $config->get($key); $options = Arr::get($this->config, 'pool', []); + $this->frequency = make(Frequency::class); + parent::__construct($container, $options); } diff --git a/src/redis/src/RedisConnection.php b/src/redis/src/RedisConnection.php index 968c699f4..42903b9b7 100644 --- a/src/redis/src/RedisConnection.php +++ b/src/redis/src/RedisConnection.php @@ -98,6 +98,8 @@ class RedisConnection extends BaseConnection implements ConnectionInterface public function close(): bool { + unset($this->connection); + return true; } diff --git a/src/redis/tests/RedisConnectionTest.php b/src/redis/tests/RedisConnectionTest.php index 0ea2074c9..7cd18bb89 100644 --- a/src/redis/tests/RedisConnectionTest.php +++ b/src/redis/tests/RedisConnectionTest.php @@ -15,6 +15,11 @@ namespace HyperfTest\Redis; use Hyperf\Config\Config; use Hyperf\Contract\ConfigInterface; use Hyperf\Di\Container; +use Hyperf\Pool\Channel; +use Hyperf\Pool\LowFrequencyInterface; +use Hyperf\Pool\PoolOption; +use Hyperf\Redis\Frequency; +use Hyperf\Utils\ApplicationContext; use HyperfTest\Redis\Stub\RedisPoolStub; use Mockery; use PHPUnit\Framework\TestCase; @@ -69,10 +74,33 @@ class RedisConnectionTest extends TestCase $this->assertSame(null, $connection->getDatabase()); } + public function testRedisCloseInLowFrequency() + { + $pool = $this->getRedisPool(); + + $connection1 = $pool->get()->getConnection(); + $connection2 = $pool->get()->getConnection(); + $connection3 = $pool->get()->getConnection(); + + $this->assertSame(3, $pool->getCurrentConnections()); + + $connection1->release(); + $connection2->release(); + $connection3->release(); + + $this->assertSame(3, $pool->getCurrentConnections()); + + $connection = $pool->get()->getConnection(); + + $this->assertSame(1, $pool->getCurrentConnections()); + + $connection->release(); + } + private function getRedisPool() { $container = Mockery::mock(Container::class); - $container->shouldReceive('get')->once()->with(ConfigInterface::class)->andReturn(new Config([ + $container->shouldReceive('get')->with(ConfigInterface::class)->andReturn(new Config([ 'redis' => [ 'default' => [ 'host' => 'redis', @@ -90,6 +118,18 @@ class RedisConnectionTest extends TestCase ], ])); + $frequency = Mockery::mock(LowFrequencyInterface::class); + $frequency->shouldReceive('isLowFrequency')->andReturn(true); + $container->shouldReceive('make')->with(Frequency::class, Mockery::any())->andReturn($frequency); + $container->shouldReceive('make')->with(PoolOption::class, Mockery::any())->andReturnUsing(function ($class, $args) { + return new PoolOption(...array_values($args)); + }); + $container->shouldReceive('make')->with(Channel::class, Mockery::any())->andReturnUsing(function ($class, $args) { + return new Channel($args['size']); + }); + + ApplicationContext::setContainer($container); + return new RedisPoolStub($container, 'default'); } } diff --git a/src/rpc-client/composer.json b/src/rpc-client/composer.json index cbe19cda5..12c321dde 100644 --- a/src/rpc-client/composer.json +++ b/src/rpc-client/composer.json @@ -15,9 +15,9 @@ "require": { "php": ">=7.2", "psr/container": "^1.0", - "hyperf/rpc": "~1.0.0", - "hyperf/load-balancer": "~1.0.0", - "hyperf/utils": "~1.0.0" + "hyperf/rpc": "~1.1.0", + "hyperf/load-balancer": "~1.1.0", + "hyperf/utils": "~1.1.0" }, "require-dev": { "malukenho/docheader": "^0.1.6", @@ -44,7 +44,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.1-dev" }, "hyperf": { "config": "Hyperf\\RpcClient\\ConfigProvider" diff --git a/src/rpc-server/composer.json b/src/rpc-server/composer.json index 92d31f4e6..39d6d7cc1 100644 --- a/src/rpc-server/composer.json +++ b/src/rpc-server/composer.json @@ -13,8 +13,8 @@ }, "require": { "php": ">=7.2", - "hyperf/http-server": "~1.0.0", - "hyperf/rpc": "~1.0.0" + "hyperf/http-server": "~1.1.0", + "hyperf/rpc": "~1.1.0" }, "require-dev": { "malukenho/docheader": "^0.1.6", @@ -39,7 +39,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.1-dev" }, "hyperf": { "config": "Hyperf\\RpcServer\\ConfigProvider" diff --git a/src/rpc-server/src/CoreMiddleware.php b/src/rpc-server/src/CoreMiddleware.php index d4e591943..9d00513ed 100644 --- a/src/rpc-server/src/CoreMiddleware.php +++ b/src/rpc-server/src/CoreMiddleware.php @@ -15,6 +15,7 @@ namespace Hyperf\RpcServer; use Closure; use FastRoute\Dispatcher; use Hyperf\HttpMessage\Stream\SwooleStream; +use Hyperf\HttpServer\Router\Dispatched; use Hyperf\Rpc\Protocol; use Hyperf\RpcServer\Router\DispatcherFactory; use Psr\Container\ContainerInterface; @@ -47,12 +48,12 @@ class CoreMiddleware extends \Hyperf\HttpServer\CoreMiddleware return $factory->getDispatcher($serverName); } - protected function handleFound(array $routes, ServerRequestInterface $request) + protected function handleFound(Dispatched $dispatched, ServerRequestInterface $request) { - if ($routes[1] instanceof Closure) { - $response = call($routes[1]); + if ($dispatched->handler->callback instanceof Closure) { + $response = call($dispatched->handler->callback); } else { - [$controller, $action] = $this->prepareHandler($routes[1]); + [$controller, $action] = $this->prepareHandler($dispatched->handler->callback); $controllerInstance = $this->container->get($controller); if (! method_exists($controller, $action)) { // Route found, but the handler does not exist. diff --git a/src/rpc-server/src/RequestDispatcher.php b/src/rpc-server/src/RequestDispatcher.php index c83e75ff6..b88ef1384 100644 --- a/src/rpc-server/src/RequestDispatcher.php +++ b/src/rpc-server/src/RequestDispatcher.php @@ -13,12 +13,13 @@ declare(strict_types=1); namespace Hyperf\RpcServer; use Hyperf\Dispatcher\AbstractDispatcher; +use Hyperf\Dispatcher\HttpDispatcher; use Hyperf\Dispatcher\HttpRequestHandler; use Psr\Container\ContainerInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; -class RequestDispatcher extends AbstractDispatcher +class RequestDispatcher extends HttpDispatcher { /** * @var ContainerInterface diff --git a/src/rpc-server/src/Router/RouteCollector.php b/src/rpc-server/src/Router/RouteCollector.php index d17e2f438..7d1ee5e3d 100644 --- a/src/rpc-server/src/Router/RouteCollector.php +++ b/src/rpc-server/src/Router/RouteCollector.php @@ -15,6 +15,7 @@ namespace Hyperf\RpcServer\Router; use FastRoute\DataGenerator; use FastRoute\RouteParser; use Hyperf\HttpServer\MiddlewareManager; +use Hyperf\HttpServer\Router\Handler; class RouteCollector { @@ -68,8 +69,8 @@ class RouteCollector $server = $options['server'] ?? 'rpc'; foreach ($routeDatas as $routeData) { // Use 'GET' method for RPC. - $this->dataGenerator->addRoute('POST', $routeData, $handler); - MiddlewareManager::addMiddlewares($server, $routeData[0], 'GET', $options['middleware'] ?? []); + $this->dataGenerator->addRoute('POST', $routeData, new Handler($handler, $route)); + MiddlewareManager::addMiddlewares($server, $route, 'GET', $options['middleware'] ?? []); } } diff --git a/src/rpc-server/src/Server.php b/src/rpc-server/src/Server.php index 91a762a5f..5c64ebe70 100644 --- a/src/rpc-server/src/Server.php +++ b/src/rpc-server/src/Server.php @@ -18,6 +18,7 @@ use Hyperf\Contract\MiddlewareInitializerInterface; use Hyperf\Contract\OnReceiveInterface; use Hyperf\ExceptionHandler\ExceptionHandlerDispatcher; use Hyperf\HttpMessage\Stream\SwooleStream; +use Hyperf\HttpServer\Contract\CoreMiddlewareInterface; use Hyperf\HttpServer\Exception\Handler\HttpExceptionHandler; use Hyperf\Rpc\Protocol; use Hyperf\Server\ServerManager; @@ -25,33 +26,12 @@ use Hyperf\Utils\Context; use Psr\Container\ContainerInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; -use Psr\Http\Server\MiddlewareInterface; use Psr\Log\LoggerInterface; use Swoole\Server as SwooleServer; use Throwable; abstract class Server implements OnReceiveInterface, MiddlewareInitializerInterface { - /** - * @var array - */ - protected $middlewares; - - /** - * @var string - */ - protected $coreHandler; - - /** - * @var MiddlewareInterface - */ - protected $coreMiddleware; - - /** - * @var array - */ - protected $exceptionHandlers; - /** * @var ContainerInterface */ @@ -62,6 +42,26 @@ abstract class Server implements OnReceiveInterface, MiddlewareInitializerInterf */ protected $dispatcher; + /** + * @var ExceptionHandlerDispatcher + */ + protected $exceptionHandlerDispatcher; + + /** + * @var array + */ + protected $middlewares; + + /** + * @var CoreMiddlewareInterface + */ + protected $coreMiddleware; + + /** + * @var array + */ + protected $exceptionHandlers; + /** * @var string */ @@ -78,18 +78,16 @@ abstract class Server implements OnReceiveInterface, MiddlewareInitializerInterf protected $logger; public function __construct( - string $serverName, - string $coreHandler, ContainerInterface $container, + DispatcherInterface $dispatcher, + ExceptionHandlerDispatcher $exceptionDispatcher, Protocol $protocol, - $dispatcher, LoggerInterface $logger ) { - $this->serverName = $serverName; - $this->coreHandler = $coreHandler; $this->container = $container; - $this->protocol = $protocol; $this->dispatcher = $dispatcher; + $this->exceptionHandlerDispatcher = $exceptionDispatcher; + $this->protocol = $protocol; $this->logger = $logger; } @@ -116,6 +114,8 @@ abstract class Server implements OnReceiveInterface, MiddlewareInitializerInterf // $middlewares = array_merge($this->middlewares, MiddlewareManager::get()); $middlewares = $this->middlewares; + $request = $this->coreMiddleware->dispatch($request); + $response = $this->dispatcher->dispatch($request, $middlewares, $this->coreMiddleware); } catch (Throwable $throwable) { // Delegate the exception to exception handler. @@ -139,11 +139,7 @@ abstract class Server implements OnReceiveInterface, MiddlewareInitializerInterf $this->logger->debug(sprintf('Connect to %s:%d', $port->host, $port->port)); } - protected function createCoreMiddleware(): MiddlewareInterface - { - $coreHandler = $this->coreHandler; - return new $coreHandler($this->container, $this->protocol, $this->serverName); - } + abstract protected function createCoreMiddleware(): CoreMiddlewareInterface; abstract protected function buildRequest(int $fd, int $fromId, string $data): ServerRequestInterface; diff --git a/src/rpc/composer.json b/src/rpc/composer.json index 8ca2a799e..09088c335 100644 --- a/src/rpc/composer.json +++ b/src/rpc/composer.json @@ -12,8 +12,8 @@ }, "require": { "php": ">=7.2", - "hyperf/contract": "~1.0.0", - "hyperf/utils": "~1.0.0" + "hyperf/contract": "~1.1.0", + "hyperf/utils": "~1.1.0" }, "require-dev": { "malukenho/docheader": "^0.1.6", @@ -38,7 +38,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.1-dev" }, "hyperf": { } diff --git a/src/server/composer.json b/src/server/composer.json index 4f6932530..c83aa8191 100644 --- a/src/server/composer.json +++ b/src/server/composer.json @@ -12,13 +12,12 @@ }, "require": { "php": ">=7.2", - "ext-swoole": ">=4.3", "psr/container": "^1.0", "psr/log": "^1.0", "psr/event-dispatcher": "^1.0", "symfony/console": "^4.2", - "hyperf/contract": "~1.0.0", - "hyperf/utils": "~1.0.0" + "hyperf/contract": "~1.1.0", + "hyperf/utils": "~1.1.0" }, "require-dev": { "malukenho/docheader": "^0.1.6", @@ -45,7 +44,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.1-dev" }, "hyperf": { "config": "Hyperf\\Server\\ConfigProvider" diff --git a/src/server/src/Command/StartServer.php b/src/server/src/Command/StartServer.php index da5c68fa0..5a3e2cb89 100644 --- a/src/server/src/Command/StartServer.php +++ b/src/server/src/Command/StartServer.php @@ -42,7 +42,7 @@ class StartServer extends SymfonyCommand protected function execute(InputInterface $input, OutputInterface $output) { - \Swoole\Runtime::enableCoroutine(true); + \Swoole\Runtime::enableCoroutine(true, swoole_hook_flags()); $this->checkEnvironment($output); diff --git a/src/server/src/ConfigProvider.php b/src/server/src/ConfigProvider.php index 518e2a2a5..449f30222 100644 --- a/src/server/src/ConfigProvider.php +++ b/src/server/src/ConfigProvider.php @@ -13,6 +13,7 @@ declare(strict_types=1); namespace Hyperf\Server; use Hyperf\Server\Listener\InitProcessTitleListener; +use Swoole\Server as SwooleServer; class ConfigProvider { @@ -20,6 +21,7 @@ class ConfigProvider { return [ 'dependencies' => [ + SwooleServer::class => SwooleServerFactory::class, ], 'commands' => [ ], diff --git a/src/server/src/Port.php b/src/server/src/Port.php index 3786740e2..9d2823349 100644 --- a/src/server/src/Port.php +++ b/src/server/src/Port.php @@ -51,6 +51,8 @@ class Port public static function build(array $config) { + $config = self::filter($config); + $port = new static(); isset($config['name']) && $port->setName($config['name']); isset($config['type']) && $port->setType($config['type']); @@ -139,4 +141,18 @@ class Port $this->settings = $settings; return $this; } + + private static function filter(array $config): array + { + if ((int) $config['type'] === ServerInterface::SERVER_TCP) { + $default = [ + 'open_http2_protocol' => false, + 'open_http_protocol' => false, + ]; + + $config['settings'] = array_merge($default, $config['settings'] ?? []); + } + + return $config; + } } diff --git a/src/server/src/Server.php b/src/server/src/Server.php index 31ca86158..14b332db8 100644 --- a/src/server/src/Server.php +++ b/src/server/src/Server.php @@ -112,6 +112,9 @@ class Server implements ServerInterface } else { /** @var \Swoole\Server\Port $slaveServer */ $slaveServer = $this->server->addlistener($host, $port, $sockType); + if (! $slaveServer) { + throw new \RuntimeException("Failed to listen server port [{$host}:{$port}]"); + } $server->getSettings() && $slaveServer->set($server->getSettings()); $this->registerSwooleEvents($slaveServer, $callbacks, $name); ServerManager::add($name, [$type, $slaveServer]); @@ -177,7 +180,7 @@ class Server implements ServerInterface } /** - * @param Port|SwooleServer $server + * @param \Swoole\Server\Port|SwooleServer $server */ protected function registerSwooleEvents($server, array $events, string $serverName): void { diff --git a/src/grpc-server/src/ServerFactory.php b/src/server/src/SwooleServerFactory.php similarity index 57% rename from src/grpc-server/src/ServerFactory.php rename to src/server/src/SwooleServerFactory.php index e7a963d51..3cc2d32a0 100644 --- a/src/grpc-server/src/ServerFactory.php +++ b/src/server/src/SwooleServerFactory.php @@ -10,17 +10,16 @@ declare(strict_types=1); * @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE */ -namespace Hyperf\GrpcServer; +namespace Hyperf\Server; use Psr\Container\ContainerInterface; -/** - * @deprecated v1.1 - */ -class ServerFactory +class SwooleServerFactory { - public function __invoke(ContainerInterface $container): Server + public function __invoke(ContainerInterface $container) { - return new Server($container); + $factory = $container->get(ServerFactory::class); + + return $factory->getServer()->getServer(); } } diff --git a/src/server/tests/PortTest.php b/src/server/tests/PortTest.php new file mode 100644 index 000000000..ae6ee9d1d --- /dev/null +++ b/src/server/tests/PortTest.php @@ -0,0 +1,57 @@ + 'http', + 'type' => Server::SERVER_HTTP, + ]); + + $this->assertSame([], $port->getSettings()); + + $port = Port::build([ + 'name' => 'tcp', + 'type' => Server::SERVER_TCP, + ]); + + $this->assertSame([ + 'open_http2_protocol' => false, + 'open_http_protocol' => false, + ], $port->getSettings()); + + $port = Port::build([ + 'name' => 'tcp', + 'type' => Server::SERVER_TCP, + 'settings' => [ + 'open_http2_protocol' => true, + ], + ]); + + $this->assertSame([ + 'open_http2_protocol' => true, + 'open_http_protocol' => false, + ], $port->getSettings()); + } +} diff --git a/src/service-governance/composer.json b/src/service-governance/composer.json index 1a7aa8ad2..4d646331d 100644 --- a/src/service-governance/composer.json +++ b/src/service-governance/composer.json @@ -12,11 +12,11 @@ }, "require": { "php": ">=7.2", - "hyperf/consul": "~1.0.0", - "hyperf/contract": "~1.0.0" + "hyperf/consul": "~1.1.0", + "hyperf/contract": "~1.1.0" }, "require-dev": { - "hyperf/event": "~1.0.0", + "hyperf/event": "~1.1.0", "malukenho/docheader": "^0.1.6", "mockery/mockery": "^1.0", "phpunit/phpunit": "^7.0.0", @@ -41,7 +41,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.1-dev" }, "hyperf": { "config": "Hyperf\\ServiceGovernance\\ConfigProvider" diff --git a/src/snowflake/composer.json b/src/snowflake/composer.json index c9801ef22..8001e7d6a 100644 --- a/src/snowflake/composer.json +++ b/src/snowflake/composer.json @@ -37,7 +37,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.1-dev" }, "hyperf": { "config": "Hyperf\\Snowflake\\ConfigProvider" diff --git a/src/snowflake/tests/RedisMetaGeneratorTest.php b/src/snowflake/tests/RedisMetaGeneratorTest.php index 696d172aa..9a43d7679 100644 --- a/src/snowflake/tests/RedisMetaGeneratorTest.php +++ b/src/snowflake/tests/RedisMetaGeneratorTest.php @@ -17,6 +17,7 @@ use Hyperf\Contract\ConfigInterface; use Hyperf\Di\Container; use Hyperf\Pool\Channel; use Hyperf\Pool\PoolOption; +use Hyperf\Redis\Frequency; use Hyperf\Redis\Pool\PoolFactory; use Hyperf\Redis\Pool\RedisPool; use Hyperf\Redis\RedisProxy; @@ -178,6 +179,7 @@ class RedisMetaGeneratorTest extends TestCase $container->shouldReceive('make')->with(PoolOption::class, Mockery::any())->andReturnUsing(function ($class, $args) { return new PoolOption(...array_values($args)); }); + $container->shouldReceive('make')->with(Frequency::class, Mockery::any())->andReturn(new Frequency()); $container->shouldReceive('make')->with(RedisPool::class, Mockery::any())->andReturnUsing(function ($class, $args) use ($container) { return new RedisPool($container, $args['name']); }); diff --git a/src/swagger/composer.json b/src/swagger/composer.json index 28bcebac6..2d03c34fc 100644 --- a/src/swagger/composer.json +++ b/src/swagger/composer.json @@ -12,7 +12,7 @@ }, "require": { "php": ">=7.2", - "hyperf/command": "~1.0.0", + "hyperf/command": "~1.1.0", "zircote/swagger-php": "^3.0" }, "require-dev": { @@ -37,7 +37,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.1-dev" }, "hyperf": { "config": "Hyperf\\Swagger\\ConfigProvider" diff --git a/src/swoole-enterprise/composer.json b/src/swoole-enterprise/composer.json index ac67a084f..14d9054cb 100644 --- a/src/swoole-enterprise/composer.json +++ b/src/swoole-enterprise/composer.json @@ -12,9 +12,8 @@ }, "require": { "php": ">=7.2", - "ext-swoole": ">=4.3", - "hyperf/contract": "~1.0.0", - "hyperf/utils": "~1.0.0", + "hyperf/contract": "~1.1.0", + "hyperf/utils": "~1.1.0", "psr/container": "^1.0", "psr/http-server-middleware": "^1.0", "psr/log": "^1.0" @@ -41,7 +40,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.1-dev" }, "hyperf": { "config": "Hyperf\\SwooleEnterprise\\ConfigProvider" diff --git a/src/swoole-tracker/LICENSE.md b/src/swoole-tracker/LICENSE.md new file mode 100644 index 000000000..eb14702a3 --- /dev/null +++ b/src/swoole-tracker/LICENSE.md @@ -0,0 +1,9 @@ +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. \ No newline at end of file diff --git a/src/swoole-tracker/README.md b/src/swoole-tracker/README.md new file mode 100644 index 000000000..ed1d2a86b --- /dev/null +++ b/src/swoole-tracker/README.md @@ -0,0 +1,31 @@ +# Swoole Tracker + +[Swoole Tracker](https://www.swoole-cloud.com/tracker.html)作为`Swoole`官方出品的一整套企业级`PHP`和`Swoole`分析调试工具,更专一、更专业。 + +* 时刻掌握应用架构模型 + +> 自动发现应用依赖拓扑结构和展示,时刻掌握应用的架构模型 + +* 分布式跨应用链路追踪 + +> 支持无侵入的分布式跨应用链路追踪,让每个请求一目了然,全面支持协程/非协程环境,数据实时可视化 + +* 全面分析报告服务状况 + +> 各种维度统计服务上报的调用信息, 比如总流量、平均耗时、超时率等,并全面分析报告服务状况 + +* 拥有强大的调试工具链 + +> 本系统支持远程调试,可在系统后台远程开启检测内存泄漏、阻塞检测和代码性能分析 + +* 同时支持FPM和Swoole + +> 完美支持PHP-FPM环境,不仅限于在Swoole中使用 + +* 完善的系统监控 + +> 支持完善的系统监控,零成本部署,监控机器的CPU、内存、网络、磁盘等资源,可以很方便的集成到现有报警系统 + +* 零成本接入系统 + +> 本系统的客户端提供脚本可一键部署,服务端可在Docker环境中运行,简单快捷 diff --git a/src/swoole-tracker/composer.json b/src/swoole-tracker/composer.json new file mode 100644 index 000000000..230279e61 --- /dev/null +++ b/src/swoole-tracker/composer.json @@ -0,0 +1,55 @@ +{ + "name": "hyperf/swoole-tracker", + "description": "A swoole tracker library for Hyperf.", + "license": "MIT", + "keywords": [ + "php", + "swoole", + "hyperf", + "swoole-tracker" + ], + "support": { + }, + "require": { + "php": ">=7.2", + "hyperf/contract": "~1.1.0", + "hyperf/utils": "~1.1.0", + "psr/container": "^1.0", + "psr/http-server-middleware": "^1.0", + "psr/log": "^1.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.9", + "malukenho/docheader": "^0.1.6", + "mockery/mockery": "^1.0", + "phpunit/phpunit": "^7.0" + }, + "suggest": { + }, + "autoload": { + "psr-4": { + "Hyperf\\SwooleTracker\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + } + }, + "config": { + "sort-packages": true + }, + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + }, + "hyperf": { + "config": "Hyperf\\SwooleTracker\\ConfigProvider" + } + }, + "bin": [ + ], + "scripts": { + "cs-fix": "php-cs-fixer fix $1", + "test": "phpunit --colors=always" + } +} diff --git a/src/swoole-tracker/src/ConfigProvider.php b/src/swoole-tracker/src/ConfigProvider.php new file mode 100644 index 000000000..448dce5c2 --- /dev/null +++ b/src/swoole-tracker/src/ConfigProvider.php @@ -0,0 +1,30 @@ + [ + ], + 'commands' => [ + ], + 'scan' => [ + 'paths' => [ + ], + ], + ]; + } +} diff --git a/src/swoole-tracker/src/Middleware/HttpServerMiddleware.php b/src/swoole-tracker/src/Middleware/HttpServerMiddleware.php new file mode 100644 index 000000000..92dfd7061 --- /dev/null +++ b/src/swoole-tracker/src/Middleware/HttpServerMiddleware.php @@ -0,0 +1,54 @@ +name = $config->get('app_name', 'hyperf-skeleton'); + } + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + if (class_exists(Stats::class)) { + $path = $request->getUri()->getPath(); + $ip = current(swoole_get_local_ip()); + + $tick = Stats::beforeExecRpc($path, $this->name, $ip); + try { + $response = $handler->handle($request); + Stats::afterExecRpc($tick, true, $response->getStatusCode()); + } catch (\Throwable $exception) { + Stats::afterExecRpc($tick, false, $exception->getCode()); + throw $exception; + } + } else { + $response = $handler->handle($request); + } + + return $response; + } +} diff --git a/src/task/composer.json b/src/task/composer.json index 1813dc74b..1c7b864aa 100644 --- a/src/task/composer.json +++ b/src/task/composer.json @@ -12,9 +12,9 @@ }, "require": { "php": ">=7.2", - "hyperf/contract": "~1.0.0", - "hyperf/framework": "~1.0.0", - "hyperf/utils": "~1.0.0", + "hyperf/contract": "~1.1.0", + "hyperf/framework": "~1.1.0", + "hyperf/utils": "~1.1.0", "psr/container": "^1.0", "psr/log": "^1.0", "psr/event-dispatcher": "^1.0" @@ -43,7 +43,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.1-dev" }, "hyperf": { "config": "Hyperf\\Task\\ConfigProvider" diff --git a/src/testing/.gitattributes b/src/testing/.gitattributes new file mode 100644 index 000000000..bdd4ea29c --- /dev/null +++ b/src/testing/.gitattributes @@ -0,0 +1 @@ +/tests export-ignore \ No newline at end of file diff --git a/src/testing/composer.json b/src/testing/composer.json index 5fd663efd..d7801c4c4 100644 --- a/src/testing/composer.json +++ b/src/testing/composer.json @@ -15,16 +15,17 @@ }, "autoload-dev": { "psr-4": { + "HyperfTest\\Testing\\": "tests/" } }, "require": { "php": ">=7.2", "psr/container": "^1.0", "phpunit/phpunit": "^7.0", - "hyperf/contract": "~1.0.0", - "hyperf/http-message": "~1.0.0", - "hyperf/http-server": "~1.0.0", - "hyperf/utils": "~1.0.0" + "hyperf/contract": "~1.1.0", + "hyperf/http-message": "~1.1.0", + "hyperf/http-server": "~1.1.0", + "hyperf/utils": "~1.1.0" }, "require-dev": { "malukenho/docheader": "^0.1.6", @@ -35,7 +36,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.1-dev" } }, "bin": [ diff --git a/src/testing/src/Client.php b/src/testing/src/Client.php index cc333e085..247156962 100644 --- a/src/testing/src/Client.php +++ b/src/testing/src/Client.php @@ -14,13 +14,14 @@ namespace Hyperf\Testing; use Hyperf\Contract\PackerInterface; use Hyperf\Dispatcher\HttpDispatcher; +use Hyperf\ExceptionHandler\ExceptionHandlerDispatcher; use Hyperf\HttpMessage\Server\Request as Psr7Request; use Hyperf\HttpMessage\Server\Response as Psr7Response; use Hyperf\HttpMessage\Stream\SwooleStream; use Hyperf\HttpMessage\Upload\UploadedFile; use Hyperf\HttpMessage\Uri\Uri; -use Hyperf\HttpServer\CoreMiddleware; use Hyperf\HttpServer\MiddlewareManager; +use Hyperf\HttpServer\Router\Dispatched; use Hyperf\HttpServer\Server; use Hyperf\Utils\Arr; use Hyperf\Utils\Context; @@ -49,7 +50,7 @@ class Client extends Server public function __construct(ContainerInterface $container, PackerInterface $packer = null, $server = 'http') { - parent::__construct('http', CoreMiddleware::class, $container, $container->get(HttpDispatcher::class)); + parent::__construct($container, $container->get(HttpDispatcher::class), $container->get(ExceptionHandlerDispatcher::class)); $this->packer = $packer ?? new JsonPacker(); $this->initCoreMiddleware($server); @@ -118,9 +119,23 @@ class Client extends Server */ [$psr7Request, $psr7Response] = $this->init($method, $path, $options); - $middlewares = array_merge($this->middlewares, MiddlewareManager::get($this->serverName, $psr7Request->getUri()->getPath(), $psr7Request->getMethod())); + $psr7Request = $this->coreMiddleware->dispatch($psr7Request); + /** @var Dispatched $dispatched */ + $dispatched = $psr7Request->getAttribute(Dispatched::class); + $middlewares = $this->middlewares; + if ($dispatched->isFound()) { + $registedMiddlewares = MiddlewareManager::get($this->serverName, $dispatched->handler->route, $psr7Request->getMethod()); + $middlewares = array_merge($middlewares, $registedMiddlewares); + } - return $this->dispatcher->dispatch($psr7Request, $middlewares, $this->coreMiddleware); + try { + $psr7Response = $this->dispatcher->dispatch($psr7Request, $middlewares, $this->coreMiddleware); + } catch (\Throwable $throwable) { + // Delegate the exception to exception handler. + $psr7Response = $this->exceptionHandlerDispatcher->dispatch($throwable, $this->exceptionHandlers); + } + + return $psr7Response; } protected function init(string $method, string $path, array $options = []): array diff --git a/src/testing/tests/ClientTest.php b/src/testing/tests/ClientTest.php new file mode 100644 index 000000000..566d40098 --- /dev/null +++ b/src/testing/tests/ClientTest.php @@ -0,0 +1,92 @@ +getContainer(); + + $client = new Client($container); + + $data = $client->get('/'); + + $this->assertSame(0, $data['code']); + $this->assertSame('Hello Hyperf!', $data['data']); + } + + public function testClientException() + { + $container = $this->getContainer(); + + $client = new Client($container); + + $data = $client->get('/exception'); + + $this->assertSame(500, $data['code']); + $this->assertSame('Server Error', $data['message']); + } + + public function getContainer() + { + $container = Mockery::mock(ContainerInterface::class); + + $container->shouldReceive('get')->with(HttpDispatcher::class)->andReturn(new HttpDispatcher($container)); + $container->shouldReceive('get')->with(ExceptionHandlerDispatcher::class)->andReturn(new ExceptionHandlerDispatcher($container)); + $container->shouldReceive('get')->with(DispatcherFactory::class)->andReturn($factory = new DispatcherFactory()); + $container->shouldReceive('get')->with(NormalizerInterface::class)->andReturn(new SimpleNormalizer()); + $container->shouldReceive('get')->with(MethodDefinitionCollectorInterface::class)->andReturn(new MethodDefinitionCollector()); + $container->shouldReceive('get')->with(ConfigInterface::class)->andReturn(new Config([ + 'exceptions' => [ + 'handler' => [ + 'http' => [ + FooExceptionHandler::class, + ], + ], + ], + ])); + $container->shouldReceive('get')->with(Filesystem::class)->andReturn(new Filesystem()); + $container->shouldReceive('get')->with(FooController::class)->andReturn(new FooController()); + $container->shouldReceive('has')->andReturn(true); + $container->shouldReceive('get')->with(FooExceptionHandler::class)->andReturn(new FooExceptionHandler()); + + Router::init($factory); + Router::get('/', [FooController::class, 'index']); + Router::get('/exception', [FooController::class, 'exception']); + + return $container; + } +} diff --git a/src/testing/tests/Stub/Exception/Handler/FooExceptionHandler.php b/src/testing/tests/Stub/Exception/Handler/FooExceptionHandler.php new file mode 100644 index 000000000..8a8386d54 --- /dev/null +++ b/src/testing/tests/Stub/Exception/Handler/FooExceptionHandler.php @@ -0,0 +1,33 @@ + $throwable->getCode(), 'message' => $throwable->getMessage()]; + + return $response->withBody(new SwooleStream(json_encode($data))); + } + + public function isValid(Throwable $throwable): bool + { + return true; + } +} diff --git a/src/testing/tests/Stub/FooController.php b/src/testing/tests/Stub/FooController.php new file mode 100644 index 000000000..7d3984710 --- /dev/null +++ b/src/testing/tests/Stub/FooController.php @@ -0,0 +1,26 @@ + 0, 'data' => 'Hello Hyperf!']; + } + + public function exception() + { + throw new \RuntimeException('Server Error', 500); + } +} diff --git a/src/tracer/composer.json b/src/tracer/composer.json index 444f7c586..fabaf0584 100644 --- a/src/tracer/composer.json +++ b/src/tracer/composer.json @@ -13,14 +13,14 @@ "require": { "php": ">=7.2", "psr/http-message": "^1.0", - "hyperf/contract": "~1.0.0", - "hyperf/di": "~1.0.0", - "hyperf/guzzle": "~1.0.0", - "hyperf/utils": "~1.0.0", + "hyperf/contract": "~1.1.0", + "hyperf/di": "~1.1.0", + "hyperf/guzzle": "~1.1.0", + "hyperf/utils": "~1.1.0", "openzipkin/zipkin": "^1.3.2" }, "require-dev": { - "hyperf/event": "~1.0.0", + "hyperf/event": "~1.1.0", "malukenho/docheader": "^0.1.6", "mockery/mockery": "^1.0", "phpunit/phpunit": "^7.0.0", @@ -43,7 +43,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.1-dev" }, "hyperf": { "config": "Hyperf\\Tracer\\ConfigProvider" diff --git a/src/translation/composer.json b/src/translation/composer.json index 0527ee253..9b21b8bfa 100644 --- a/src/translation/composer.json +++ b/src/translation/composer.json @@ -22,13 +22,12 @@ }, "require": { "php": ">=7.2", - "hyperf/contract": "1.0.*", - "hyperf/utils": "1.0.*", + "hyperf/contract": "~1.1.0", + "hyperf/utils": "~1.1.0", "psr/container": "^1.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "^2.14", - "hyperf/testing": "1.0.*", "mockery/mockery": "^1.2", "phpstan/phpstan": "^0.10.5" }, diff --git a/src/utils/composer.json b/src/utils/composer.json index df0319fc2..08e89704b 100644 --- a/src/utils/composer.json +++ b/src/utils/composer.json @@ -12,8 +12,9 @@ }, "require": { "php": ">=7.2", + "ext-swoole": ">=4.4", "doctrine/inflector": "^1.3", - "hyperf/contract": "~1.0.0" + "hyperf/contract": "~1.1.0" }, "require-dev": { "symfony/var-dumper": "^4.1", @@ -50,7 +51,7 @@ "config": "Hyperf\\Utils\\ConfigProvider" }, "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.1-dev" } }, "bin": [ diff --git a/src/utils/src/ClearStatCache.php b/src/utils/src/ClearStatCache.php index 67f46dddb..6c4b2b050 100644 --- a/src/utils/src/ClearStatCache.php +++ b/src/utils/src/ClearStatCache.php @@ -60,4 +60,4 @@ class ClearStatCache { self::$interval = $interval; } -} \ No newline at end of file +} diff --git a/src/utils/src/Functions.php b/src/utils/src/Functions.php index e8170893f..1081c7cda 100644 --- a/src/utils/src/Functions.php +++ b/src/utils/src/Functions.php @@ -422,14 +422,19 @@ if (! function_exists('run')) { \Swoole\Runtime::enableCoroutine(true, $flags); - if (version_compare(swoole_version(), '4.4.0', '>=')) { - $result = \Swoole\Coroutine\Run($callback); - } else { - go($callback); - $result = true; - } + $result = \Swoole\Coroutine\Run($callback); \Swoole\Runtime::enableCoroutine(false); return $result; } } + +if (! function_exists('swoole_hook_flags')) { + /** + * Return the default swoole hook flags, you can rewrite it by defining `SWOOLE_HOOK_FLAGS`. + */ + function swoole_hook_flags(): int + { + return defined('SWOOLE_HOOK_FLAGS') ? SWOOLE_HOOK_FLAGS : SWOOLE_HOOK_ALL; + } +} diff --git a/src/utils/src/MimeTypeExtensionGuesser.php b/src/utils/src/MimeTypeExtensionGuesser.php index 4c06a5dfa..57ee4f414 100644 --- a/src/utils/src/MimeTypeExtensionGuesser.php +++ b/src/utils/src/MimeTypeExtensionGuesser.php @@ -812,4 +812,4 @@ class MimeTypeExtensionGuesser { return isset($this->defaultMineTypes[$extension]) ? $this->defaultMineTypes[$extension] : null; } -} \ No newline at end of file +} diff --git a/src/utils/src/Str.php b/src/utils/src/Str.php index 7938d1622..e3a486951 100644 --- a/src/utils/src/Str.php +++ b/src/utils/src/Str.php @@ -324,7 +324,7 @@ class Str /** * Replace the first occurrence of a given value in the string. */ - public static function replaceFirst(string $search, string $replace, string $subject): string + public static function replaceFirst(string $search, string $replace, string $subject): string { if ($search == '') { return $subject; @@ -342,7 +342,7 @@ class Str /** * Replace the last occurrence of a given value in the string. */ - public static function replaceLast(string $search, string $replace, string $subject): string + public static function replaceLast(string $search, string $replace, string $subject): string { $position = strrpos($subject, $search); diff --git a/src/utils/tests/FunctionTest.php b/src/utils/tests/FunctionTest.php index 29d957658..f750bd045 100644 --- a/src/utils/tests/FunctionTest.php +++ b/src/utils/tests/FunctionTest.php @@ -105,4 +105,9 @@ class FunctionTest extends TestCase $this->assertSame(1, $result); } } + + public function testSwooleHookFlags() + { + $this->assertSame(SWOOLE_HOOK_ALL, swoole_hook_flags()); + } } diff --git a/src/view/composer.json b/src/view/composer.json index 9d350c040..89d9baca3 100644 --- a/src/view/composer.json +++ b/src/view/composer.json @@ -12,8 +12,8 @@ }, "require": { "php": ">=7.2", - "hyperf/contract": "~1.0.0", - "hyperf/utils": "~1.0.0", + "hyperf/contract": "~1.1.0", + "hyperf/utils": "~1.1.0", "psr/container": "^1.0", "psr/log": "^1.0" }, @@ -42,7 +42,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.1-dev" }, "hyperf": { "config": "Hyperf\\View\\ConfigProvider" diff --git a/src/websocket-client/composer.json b/src/websocket-client/composer.json index 2aa245059..47b15c275 100644 --- a/src/websocket-client/composer.json +++ b/src/websocket-client/composer.json @@ -12,10 +12,9 @@ }, "require": { "php": ">=7.2", - "ext-swoole": ">=4.3", - "hyperf/contract": "~1.0.0", - "hyperf/http-message": "~1.0.0", - "hyperf/utils": "~1.0.0", + "hyperf/contract": "~1.1.0", + "hyperf/http-message": "~1.1.0", + "hyperf/utils": "~1.1.0", "psr/container": "^1.0", "psr/log": "^1.0" }, @@ -42,7 +41,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.1-dev" }, "hyperf": { "config": "Hyperf\\WebSocketClient\\ConfigProvider" diff --git a/src/websocket-server/composer.json b/src/websocket-server/composer.json index 289d11db5..f6d2f7d6f 100644 --- a/src/websocket-server/composer.json +++ b/src/websocket-server/composer.json @@ -12,11 +12,10 @@ }, "require": { "php": ">=7.2", - "ext-swoole": ">=4.3", - "hyperf/contract": "~1.0.0", - "hyperf/exception-handler": "~1.0.0", - "hyperf/http-server": "~1.0.0", - "hyperf/utils": "~1.0.0", + "hyperf/contract": "~1.1.0", + "hyperf/exception-handler": "~1.1.0", + "hyperf/http-server": "~1.1.0", + "hyperf/utils": "~1.1.0", "psr/container": "^1.0", "psr/event-dispatcher": "^1.0", "psr/log": "^1.0" @@ -43,7 +42,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "1.1-dev" }, "hyperf": { "config": "Hyperf\\WebSocketServer\\ConfigProvider" diff --git a/src/websocket-server/src/ConfigProvider.php b/src/websocket-server/src/ConfigProvider.php index ae41ec441..d31e94369 100644 --- a/src/websocket-server/src/ConfigProvider.php +++ b/src/websocket-server/src/ConfigProvider.php @@ -19,7 +19,9 @@ class ConfigProvider return [ 'dependencies' => [ ], - 'commands' => [ + 'listeners' => [ + Listener\InitSenderListener::class, + Listener\OnPipeMessageListener::class, ], 'scan' => [ 'paths' => [ diff --git a/src/websocket-server/src/CoreMiddleware.php b/src/websocket-server/src/CoreMiddleware.php index e3a397717..db6513873 100644 --- a/src/websocket-server/src/CoreMiddleware.php +++ b/src/websocket-server/src/CoreMiddleware.php @@ -13,6 +13,7 @@ declare(strict_types=1); namespace Hyperf\WebSocketServer; use Hyperf\HttpServer\CoreMiddleware as HttpCoreMiddleware; +use Hyperf\HttpServer\Router\Dispatched; use Hyperf\Utils\Context; use Hyperf\Utils\Contracts\Arrayable; use Hyperf\WebSocketServer\Exception\WebSocketHandeShakeException; @@ -26,9 +27,9 @@ class CoreMiddleware extends HttpCoreMiddleware * * @return array|Arrayable|mixed|ResponseInterface|string */ - protected function handleFound(array $routes, ServerRequestInterface $request): ResponseInterface + protected function handleFound(Dispatched $dispatched, ServerRequestInterface $request): ResponseInterface { - [$controller,] = $this->prepareHandler($routes[1]); + [$controller,] = $this->prepareHandler($dispatched->handler->callback); if (! $this->container->has($controller)) { throw new WebSocketHandeShakeException('Router not exist.'); } diff --git a/src/websocket-server/src/Exception/InvalidMethodException.php b/src/websocket-server/src/Exception/InvalidMethodException.php new file mode 100644 index 000000000..f6187bcdb --- /dev/null +++ b/src/websocket-server/src/Exception/InvalidMethodException.php @@ -0,0 +1,19 @@ +container = $container; } + /** + * @return string[] returns the events that you want to listen + */ public function listen(): array { return [ - BeforeMainServerStart::class, + AfterWorkerStart::class, ]; } - /** - * @param BeforeMainServerStart $event - */ public function process(object $event) { - if (! $this->container->has(Server::class)) { - return; + if ($this->container->has(Sender::class)) { + $sender = $this->container->get(Sender::class); + $sender->setWorkerId($event->workerId); } - $server = $this->container->get(Server::class); - $event->server instanceof \Swoole\WebSocket\Server && $server->setServer($event->server); } } diff --git a/src/websocket-server/src/Listener/OnPipeMessageListener.php b/src/websocket-server/src/Listener/OnPipeMessageListener.php new file mode 100644 index 000000000..c13495671 --- /dev/null +++ b/src/websocket-server/src/Listener/OnPipeMessageListener.php @@ -0,0 +1,75 @@ +container = $container; + $this->logger = $logger; + $this->sender = $sender; + } + + /** + * @return string[] returns the events that you want to listen + */ + public function listen(): array + { + return [ + OnPipeMessage::class, + ]; + } + + /** + * Handle the Event when the event is triggered, all listeners will + * complete before the event is returned to the EventDispatcher. + */ + public function process(object $event) + { + if ($event instanceof OnPipeMessage && $event->data instanceof SenderPipeMessage) { + /** @var SenderPipeMessage $message */ + $message = $event->data; + + try { + $this->sender->proxy($message->name, $message->arguments); + } catch (\Throwable $exception) { + $formatter = $this->container->get(FormatterInterface::class); + $this->logger->warning($formatter->format($exception)); + } + } + } +} diff --git a/src/websocket-server/src/Sender.php b/src/websocket-server/src/Sender.php new file mode 100644 index 000000000..15c6e2e05 --- /dev/null +++ b/src/websocket-server/src/Sender.php @@ -0,0 +1,107 @@ +container = $container; + $this->logger = $container->get(StdoutLoggerInterface::class); + } + + public function __call($name, $arguments) + { + if (! $this->proxy($name, $arguments)) { + $this->sendPipeMessage($name, $arguments); + } + } + + public function proxy(string $name, array $arguments): bool + { + $fd = $this->getFdFromProxyMethod($name, $arguments); + + $result = $this->check($fd); + if ($result) { + $this->getServer()->push(...$arguments); + $this->logger->debug("[WebSocket] Worker.{$this->workerId} send to #{$fd}"); + } + + return $result; + } + + public function setWorkerId(int $workerId): void + { + $this->workerId = $workerId; + } + + public function check($fd): bool + { + $info = $this->getServer()->connection_info($fd); + + if ($info && $info['websocket_status'] === WEBSOCKET_STATUS_ACTIVE) { + return true; + } + + return false; + } + + protected function getFdFromProxyMethod(string $method, array $arguments): int + { + if (! in_array($method, ['push', 'send', 'sendto'])) { + throw new InvalidMethodException(sprintf('Method [%s] is not allowed.', $method)); + } + + return (int) $arguments[0]; + } + + protected function getServer(): Server + { + return $this->container->get(Server::class); + } + + protected function sendPipeMessage(string $name, array $arguments): void + { + $server = $this->getServer(); + $workerCount = $server->setting['worker_num'] - 1; + for ($workerId = 0; $workerId <= $workerCount; ++$workerId) { + if ($workerId !== $this->workerId) { + $server->sendMessage(new SenderPipeMessage($name, $arguments), $workerId); + $this->logger->debug("[WebSocket] Let Worker.{$workerId} try to {$name}."); + } + } + } +} diff --git a/src/websocket-server/src/SenderPipeMessage.php b/src/websocket-server/src/SenderPipeMessage.php new file mode 100644 index 000000000..21c7496c4 --- /dev/null +++ b/src/websocket-server/src/SenderPipeMessage.php @@ -0,0 +1,32 @@ +name = $name; + $this->arguments = $arguments; + } +} diff --git a/src/websocket-server/src/Server.php b/src/websocket-server/src/Server.php index 5e6086d87..1903c2ca4 100644 --- a/src/websocket-server/src/Server.php +++ b/src/websocket-server/src/Server.php @@ -23,7 +23,9 @@ use Hyperf\Dispatcher\HttpDispatcher; use Hyperf\ExceptionHandler\ExceptionHandlerDispatcher; use Hyperf\HttpMessage\Server\Request as Psr7Request; use Hyperf\HttpMessage\Server\Response as Psr7Response; +use Hyperf\HttpServer\Contract\CoreMiddlewareInterface; use Hyperf\HttpServer\MiddlewareManager; +use Hyperf\HttpServer\Router\Dispatched; use Hyperf\Utils\Context; use Hyperf\WebSocketServer\Collector\FdCollector; use Hyperf\WebSocketServer\Exception\Handler\WebSocketExceptionHandler; @@ -34,6 +36,7 @@ use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Swoole\Http\Request as SwooleRequest; use Swoole\Http\Response as SwooleResponse; +use Swoole\Server as SwooleServer; use Swoole\Websocket\Frame; use Swoole\WebSocket\Server as WebSocketServer; @@ -50,7 +53,12 @@ class Server implements MiddlewareInitializerInterface, OnHandShakeInterface, On protected $dispatcher; /** - * @var CoreMiddleware + * @var ExceptionHandlerDispatcher + */ + protected $exceptionHandlerDispatcher; + + /** + * @var CoreMiddlewareInterface */ protected $coreMiddleware; @@ -64,11 +72,6 @@ class Server implements MiddlewareInitializerInterface, OnHandShakeInterface, On */ protected $logger; - /** - * @var WebSocketServer - */ - protected $server; - /** * @var array */ @@ -79,11 +82,16 @@ class Server implements MiddlewareInitializerInterface, OnHandShakeInterface, On */ protected $serverName = 'websocket'; - public function __construct(ContainerInterface $container) - { + public function __construct( + ContainerInterface $container, + HttpDispatcher $dispatcher, + ExceptionHandlerDispatcher $exceptionHandlerDispatcher, + StdoutLoggerInterface $logger + ) { $this->container = $container; - $this->dispatcher = $container->get(HttpDispatcher::class); - $this->logger = $container->get(StdoutLoggerInterface::class); + $this->dispatcher = $dispatcher; + $this->exceptionHandlerDispatcher = $exceptionHandlerDispatcher; + $this->logger = $logger; } public function initCoreMiddleware(string $serverName): void @@ -98,9 +106,9 @@ class Server implements MiddlewareInitializerInterface, OnHandShakeInterface, On ]); } - public function setServer(WebSocketServer $server): void + public function getServer(): WebSocketServer { - $this->server = $server; + return $this->container->get(SwooleServer::class); } public function onHandShake(SwooleRequest $request, SwooleResponse $response): void @@ -118,7 +126,14 @@ class Server implements MiddlewareInitializerInterface, OnHandShakeInterface, On throw new WebSocketHandeShakeException('sec-websocket-key is invalid!'); } - $middlewares = array_merge($this->middlewares, MiddlewareManager::get($this->serverName, $psr7Request->getUri()->getPath(), $psr7Request->getMethod())); + $psr7Request = $this->coreMiddleware->dispatch($psr7Request); + /** @var Dispatched $dispatched */ + $dispatched = $psr7Request->getAttribute(Dispatched::class); + $middlewares = $this->middlewares; + if ($dispatched->isFound()) { + $registedMiddlewares = MiddlewareManager::get($this->serverName, $dispatched->handler->route, $psr7Request->getMethod()); + $middlewares = array_merge($middlewares, $registedMiddlewares); + } $psr7Response = $this->dispatcher->dispatch($psr7Request, $middlewares, $this->coreMiddleware); @@ -130,14 +145,13 @@ class Server implements MiddlewareInitializerInterface, OnHandShakeInterface, On defer(function () use ($request, $class) { $instance = $this->container->get($class); if ($instance instanceof OnOpenInterface) { - $instance->onOpen($this->server, $request); + $instance->onOpen($this->getServer(), $request); } }); } } catch (\Throwable $throwable) { // Delegate the exception to exception handler. - $exceptionHandlerDispatcher = $this->container->get(ExceptionHandlerDispatcher::class); - $psr7Response = $exceptionHandlerDispatcher->dispatch($throwable, $this->exceptionHandlers); + $psr7Response = $this->exceptionHandlerDispatcher->dispatch($throwable, $this->exceptionHandlers); } finally { // Send the Response to client. if (! $psr7Response || ! $psr7Response instanceof Psr7Response) { @@ -147,7 +161,7 @@ class Server implements MiddlewareInitializerInterface, OnHandShakeInterface, On } } - public function onMessage(\Swoole\Server $server, Frame $frame): void + public function onMessage(SwooleServer $server, Frame $frame): void { $fdObj = FdCollector::get($frame->fd); if (! $fdObj) { @@ -165,7 +179,7 @@ class Server implements MiddlewareInitializerInterface, OnHandShakeInterface, On $instance->onMessage($server, $frame); } - public function onClose(\Swoole\Server $server, int $fd, int $reactorId): void + public function onClose(SwooleServer $server, int $fd, int $reactorId): void { $this->logger->debug(sprintf('WebSocket: fd[%d] closed.', $fd));