Merge branch 'master' into 2.2-merge

# Conflicts:
#	composer.json
#	phpunit.xml
This commit is contained in:
李铭昕 2021-05-24 15:50:44 +08:00
commit c0c0c47a22
31 changed files with 655 additions and 30 deletions

View File

@ -20,7 +20,7 @@ jobs:
fail-fast: false
env:
SW_VERSION: ${{ matrix.sw-version }}
YASD_VERSION: 'v0.3.7'
YASD_VERSION: 'v0.3.8'
steps:
- name: Checkout
uses: actions/checkout@v2

View File

@ -1,4 +1,22 @@
# v2.1.17 - TBD
# v2.1.19 - TBD
# v2.1.18 - 2021-05-24
## Fixed
- [#3598](https://github.com/hyperf/hyperf/pull/3598) Fixed bug that `increment/decrement` does not works as expect when used in transaction for model-cache.
- [#3607](https://github.com/hyperf/hyperf/pull/3607) Fixed bug that coroutine won't destruct when using `onOpen` in coroutine style websocket server.
- [#3610](https://github.com/hyperf/hyperf/pull/3610) Fixed bug that `fromSub()` and `joinSub()` don't work with table prefix.
# v2.1.17 - 2021-05-17
## Fixed
- [#3856](https://github.com/hyperf/hyperf/pull/3586) Fixed bug that coroutine won't destruct for keepalive request in swow server.
## Added
- [#3329](https://github.com/hyperf/hyperf/pull/3329) The `enable` parameter of the `@Crontab` supports `array`, which you can dynamically control whether the task is executed or not.
# v2.1.16 - 2021-04-26

View File

@ -35,7 +35,7 @@
## 数据库
- [hyperf/database](https://github.com/hyperf/database) Hyperf 官方提供的基于 Eloquent 衍生的数据库 ORM可复用于其它框架
- [hyperf/model](https://github.com/hyperf/model) Hyperf 官方提供的基于 [hyperf/database](https://github.com/hyperf/database) 组件的自动模型缓存组件
- [hyperf/model-cache](https://github.com/hyperf/model-cache) Hyperf 官方提供的基于 [hyperf/database](https://github.com/hyperf/database) 组件的自动模型缓存组件
- [reasno/fastmongo](https://github.com/Reasno/fastmongo) 基于 `hyperf/gotask` 实现的协程化 `MongoDB` 客户端
- [hyperf-ext/translatable](https://github.com/hyperf-ext/translatable) 为模型提供多语言能力

View File

@ -1,5 +1,23 @@
# 版本更新记录
# v2.1.18 - 2021-05-24
## 修复
- [#3598](https://github.com/hyperf/hyperf/pull/3598) 修复事务回滚时,模型累加、累减操作会导致模型缓存产生脏数据的问题。
- [#3607](https://github.com/hyperf/hyperf/pull/3607) 修复在使用协程风格的 `WebSocket` 服务时,`onOpen` 事件无法在事件结束后销毁协程的问题。
- [#3610](https://github.com/hyperf/hyperf/pull/3610) 修复数据库存在前缀时,`fromSub()` 和 `joinSub()` 无法正常使用的问题。
# v2.1.17 - 2021-05-17
## 修复
- [#3856](https://github.com/hyperf/hyperf/pull/3586) 修复 `Swow` 服务处理 `keepalive` 的请求时,协程无法在每个请求后结束的问题。
## 新增
- [#3329](https://github.com/hyperf/hyperf/pull/3329) `@Crontab` 注解的 `enable` 参数增加支持设置数组, 你可以通过它动态的控制定时任务是否启动。
# v2.1.16 - 2021-04-26
## 修复

View File

@ -36,7 +36,7 @@ php bin/hyperf.php vendor:publish hyperf/metric
`default`:配置文件内的 `default` 对应的值则为使用的驱动名称。驱动的具体配置在 `metric` 项下定义,使用与 `key` 相同的驱动。
```php
'default' => env('TELEMETRY_DRIVER', 'prometheus'),
'default' => env('METRIC_DRIVER', 'prometheus'),
```
* `use_standalone_process`: 是否使用 `独立监控进程`。推荐开启。关闭后将在 `Worker 进程` 中处理指标收集与上报。
@ -63,7 +63,7 @@ php bin/hyperf.php vendor:publish hyperf/metric
use Hyperf\Metric\Adapter\Prometheus\Constants;
return [
'default' => env('TELEMETRY_DRIVER', 'prometheus'),
'default' => env('METRIC_DRIVER', 'prometheus'),
'use_standalone_process' => env('TELEMETRY_USE_STANDALONE_PROCESS', true),
'enable_default_metric' => env('TELEMETRY_ENABLE_DEFAULT_TELEMETRY', true),
'default_metric_interval' => env('DEFAULT_METRIC_INTERVAL', 5),
@ -116,7 +116,7 @@ Prometheus 有两种工作模式,爬模式与推模式(通过 Prometheus Pus
```php
return [
'default' => env('TELEMETRY_DRIVER', 'statd'),
'default' => env('METRIC_DRIVER', 'statd'),
'use_standalone_process' => env('TELEMETRY_USE_STANDALONE_PROCESS', true),
'enable_default_metric' => env('TELEMETRY_ENABLE_DEFAULT_TELEMETRY', true),
'metric' => [
@ -141,7 +141,7 @@ StatsD 目前只支持 UDP 模式,需要配置 UDP 地址 `udp_host`UDP 端
```php
return [
'default' => env('TELEMETRY_DRIVER', 'influxdb'),
'default' => env('METRIC_DRIVER', 'influxdb'),
'use_standalone_process' => env('TELEMETRY_USE_STANDALONE_PROCESS', true),
'enable_default_metric' => env('TELEMETRY_ENABLE_DEFAULT_TELEMETRY', true),
'metric' => [

View File

@ -35,7 +35,7 @@
## 數據庫
- [hyperf/database](https://github.com/hyperf/database) Hyperf 官方提供的基於 Eloquent 衍生的數據庫 ORM可複用於其它框架
- [hyperf/model](https://github.com/hyperf/model) Hyperf 官方提供的基於 [hyperf/database](https://github.com/hyperf/database) 組件的自動模型緩存組件
- [hyperf/model-cache](https://github.com/hyperf/model-cache) Hyperf 官方提供的基於 [hyperf/database](https://github.com/hyperf/database) 組件的自動模型緩存組件
- [reasno/fastmongo](https://github.com/Reasno/fastmongo) 基於 `hyperf/gotask` 實現的協程化 `MongoDB` 客户端
- [hyperf-ext/translatable](https://github.com/hyperf-ext/translatable) 為模型提供多語言能力

View File

@ -1,5 +1,23 @@
# 版本更新記錄
# v2.1.18 - 2021-05-24
## 修復
- [#3598](https://github.com/hyperf/hyperf/pull/3598) 修復事務回滾時,模型累加、累減操作會導致模型緩存產生髒數據的問題。
- [#3607](https://github.com/hyperf/hyperf/pull/3607) 修復在使用協程風格的 `WebSocket` 服務時,`onOpen` 事件無法在事件結束後銷燬協程的問題。
- [#3610](https://github.com/hyperf/hyperf/pull/3610) 修復數據庫存在前綴時,`fromSub()` 和 `joinSub()` 無法正常使用的問題。
# v2.1.17 - 2021-05-17
## 修復
- [#3856](https://github.com/hyperf/hyperf/pull/3586) 修復 `Swow` 服務處理 `keepalive` 的請求時,協程無法在每個請求後結束的問題。
## 新增
- [#3329](https://github.com/hyperf/hyperf/pull/3329) `@Crontab` 註解的 `enable` 參數增加支持設置數組, 你可以通過它動態的控制定時任務是否啟動。
# v2.1.16 - 2021-04-26
## 修復

View File

@ -39,7 +39,7 @@ php bin/hyperf.php vendor:publish hyperf/metric
`default`:配置文件內的 `default` 對應的值則為使用的驅動名稱。驅動的具體配置在 `metric` 項下定義,使用與 `key` 相同的驅動。
```php
'default' => env('TELEMETRY_DRIVER', 'prometheus'),
'default' => env('METRIC_DRIVER', 'prometheus'),
```
* `use_standalone_process`: 是否使用 `獨立監控進程`。推薦開啟。關閉後將在 `Worker 進程` 中處理指標收集與上報。
@ -66,7 +66,7 @@ php bin/hyperf.php vendor:publish hyperf/metric
use Hyperf\Metric\Adapter\Prometheus\Constants;
return [
'default' => env('TELEMETRY_DRIVER', 'prometheus'),
'default' => env('METRIC_DRIVER', 'prometheus'),
'use_standalone_process' => env('TELEMETRY_USE_STANDALONE_PROCESS', true),
'enable_default_metric' => env('TELEMETRY_ENABLE_DEFAULT_TELEMETRY', true),
'default_metric_interval' => env('DEFAULT_METRIC_INTERVAL', 5),
@ -119,7 +119,7 @@ Prometheus 有兩種工作模式,爬模式與推模式(通過 Prometheus Pus
```php
return [
'default' => env('TELEMETRY_DRIVER', 'statd'),
'default' => env('METRIC_DRIVER', 'statd'),
'use_standalone_process' => env('TELEMETRY_USE_STANDALONE_PROCESS', true),
'enable_default_metric' => env('TELEMETRY_ENABLE_DEFAULT_TELEMETRY', true),
'metric' => [
@ -144,7 +144,7 @@ StatsD 目前只支持 UDP 模式,需要配置 UDP 地址 `udp_host`UDP 端
```php
return [
'default' => env('TELEMETRY_DRIVER', 'influxdb'),
'default' => env('METRIC_DRIVER', 'influxdb'),
'use_standalone_process' => env('TELEMETRY_USE_STANDALONE_PROCESS', true),
'enable_default_metric' => env('TELEMETRY_ENABLE_DEFAULT_TELEMETRY', true),
'metric' => [

View File

@ -35,7 +35,7 @@
## 資料庫
- [hyperf/database](https://github.com/hyperf/database) Hyperf 官方提供的基於 Eloquent 衍生的資料庫 ORM可複用於其它框架
- [hyperf/model](https://github.com/hyperf/model) Hyperf 官方提供的基於 [hyperf/database](https://github.com/hyperf/database) 元件的自動模型快取元件
- [hyperf/model-cache](https://github.com/hyperf/model-cache) Hyperf 官方提供的基於 [hyperf/database](https://github.com/hyperf/database) 元件的自動模型快取元件
- [reasno/fastmongo](https://github.com/Reasno/fastmongo) 基於 `hyperf/gotask` 實現的協程化 `MongoDB` 客戶端
- [hyperf-ext/translatable](https://github.com/hyperf-ext/translatable) 為模型提供多語言能力

View File

@ -1,5 +1,23 @@
# 版本更新記錄
# v2.1.18 - 2021-05-24
## 修復
- [#3598](https://github.com/hyperf/hyperf/pull/3598) 修復事務回滾時,模型累加、累減操作會導致模型快取產生髒資料的問題。
- [#3607](https://github.com/hyperf/hyperf/pull/3607) 修復在使用協程風格的 `WebSocket` 服務時,`onOpen` 事件無法在事件結束後銷燬協程的問題。
- [#3610](https://github.com/hyperf/hyperf/pull/3610) 修復資料庫存在字首時,`fromSub()` 和 `joinSub()` 無法正常使用的問題。
# v2.1.17 - 2021-05-17
## 修復
- [#3856](https://github.com/hyperf/hyperf/pull/3586) 修復 `Swow` 服務處理 `keepalive` 的請求時,協程無法在每個請求後結束的問題。
## 新增
- [#3329](https://github.com/hyperf/hyperf/pull/3329) `@Crontab` 註解的 `enable` 引數增加支援設定陣列, 你可以通過它動態的控制定時任務是否啟動。
# v2.1.16 - 2021-04-26
## 修復

View File

@ -36,7 +36,7 @@ php bin/hyperf.php vendor:publish hyperf/metric
`default`:配置檔案內的 `default` 對應的值則為使用的驅動名稱。驅動的具體配置在 `metric` 項下定義,使用與 `key` 相同的驅動。
```php
'default' => env('TELEMETRY_DRIVER', 'prometheus'),
'default' => env('METRIC_DRIVER', 'prometheus'),
```
* `use_standalone_process`: 是否使用 `獨立監控程序`。推薦開啟。關閉後將在 `Worker 程序` 中處理指標收集與上報。
@ -63,7 +63,7 @@ php bin/hyperf.php vendor:publish hyperf/metric
use Hyperf\Metric\Adapter\Prometheus\Constants;
return [
'default' => env('TELEMETRY_DRIVER', 'prometheus'),
'default' => env('METRIC_DRIVER', 'prometheus'),
'use_standalone_process' => env('TELEMETRY_USE_STANDALONE_PROCESS', true),
'enable_default_metric' => env('TELEMETRY_ENABLE_DEFAULT_TELEMETRY', true),
'default_metric_interval' => env('DEFAULT_METRIC_INTERVAL', 5),
@ -116,7 +116,7 @@ Prometheus 有兩種工作模式,爬模式與推模式(通過 Prometheus Pus
```php
return [
'default' => env('TELEMETRY_DRIVER', 'statd'),
'default' => env('METRIC_DRIVER', 'statd'),
'use_standalone_process' => env('TELEMETRY_USE_STANDALONE_PROCESS', true),
'enable_default_metric' => env('TELEMETRY_ENABLE_DEFAULT_TELEMETRY', true),
'metric' => [
@ -141,7 +141,7 @@ StatsD 目前只支援 UDP 模式,需要配置 UDP 地址 `udp_host`UDP 埠
```php
return [
'default' => env('TELEMETRY_DRIVER', 'influxdb'),
'default' => env('METRIC_DRIVER', 'influxdb'),
'use_standalone_process' => env('TELEMETRY_USE_STANDALONE_PROCESS', true),
'enable_default_metric' => env('TELEMETRY_ENABLE_DEFAULT_TELEMETRY', true),
'metric' => [

View File

@ -18,6 +18,7 @@
<directory suffix="Test.php">./src/config-zookeeper/tests</directory>
<directory suffix="Test.php">./src/constants/tests</directory>
<directory suffix="Test.php">./src/consul/tests</directory>
<directory suffix="Test.php">./src/crontab/tests</directory>
<directory suffix="Test.php">./src/dag/tests</directory>
<directory suffix="Test.php">./src/database/tests</directory>
<directory suffix="Test.php">./src/db/tests</directory>
@ -83,6 +84,7 @@
<directory suffix=".php">./src/config/src</directory>
<directory suffix=".php">./src/constants/src</directory>
<directory suffix=".php">./src/consul/src</directory>
<directory suffix=".php">./src/crontab/src</directory>
<directory suffix=".php">./src/dag/src</directory>
<directory suffix=".php">./src/database/src</directory>
<directory suffix=".php">./src/db-connection/src</directory>
@ -113,6 +115,7 @@
<directory suffix=".php">./src/task/src</directory>
<directory suffix=".php">./src/utils/src</directory>
<directory suffix=".php">./src/websocket-client/src</directory>
<directory suffix=".php">./src/websocket-server/src</directory>
</whitelist>
</filter>
</phpunit>

View File

@ -68,7 +68,7 @@ class Crontab extends AbstractAnnotation
public $memo = '';
/**
* @var bool
* @var array|bool
*/
public $enable = true;
@ -99,10 +99,23 @@ class Crontab extends AbstractAnnotation
}
public function collectClass(string $className): void
{
$this->parseName($className);
$this->parseCallback($className);
$this->parseEnable($className);
parent::collectClass($className);
}
protected function parseName(string $className): void
{
if (! $this->name) {
$this->name = $className;
}
}
protected function parseCallback(string $className): void
{
if (! $this->callback) {
$reflectionClass = ReflectionManager::reflectClass($className);
$reflectionMethods = $reflectionClass->getMethods(ReflectionMethod::IS_PUBLIC);
@ -127,6 +140,22 @@ class Crontab extends AbstractAnnotation
} elseif (is_string($this->callback)) {
$this->callback = [$className, $this->callback];
}
parent::collectClass($className);
}
protected function parseEnable(string $className): void
{
if (is_string($this->enable) && $this->enable === 'true') {
$this->enable = true;
return;
}
if (is_string($this->enable) && $this->enable === 'false') {
$this->enable = false;
return;
}
if (is_string($this->enable)) {
$this->enable = [$className, $this->enable];
}
}
}

View File

@ -17,9 +17,11 @@ use Hyperf\Crontab\Annotation\Crontab as CrontabAnnotation;
use Hyperf\Crontab\Crontab;
use Hyperf\Crontab\CrontabManager;
use Hyperf\Di\Annotation\AnnotationCollector;
use Hyperf\Di\ReflectionManager;
use Hyperf\Event\Contract\ListenerInterface;
use Hyperf\Process\Event\BeforeCoroutineHandle;
use Hyperf\Process\Event\BeforeProcessHandle;
use Hyperf\Utils\ApplicationContext;
class CrontabRegisterListener implements ListenerInterface
{
@ -64,9 +66,8 @@ class CrontabRegisterListener implements ListenerInterface
{
$crontabs = $this->parseCrontabs();
foreach ($crontabs as $crontab) {
if ($crontab instanceof Crontab) {
if ($crontab instanceof Crontab && $this->crontabManager->register($crontab)) {
$this->logger->debug(sprintf('Crontab %s have been registered.', $crontab->getName()));
$this->crontabManager->register($crontab);
}
}
}
@ -88,7 +89,7 @@ class CrontabRegisterListener implements ListenerInterface
return array_values($crontabs);
}
private function getCrontabsFromMethod()
private function getCrontabsFromMethod(): array
{
$result = AnnotationCollector::getMethodsByAnnotation(CrontabAnnotation::class);
$crontabs = [];
@ -110,7 +111,43 @@ class CrontabRegisterListener implements ListenerInterface
isset($annotation->onOneServer) && $crontab->setOnOneServer($annotation->onOneServer);
isset($annotation->callback) && $crontab->setCallback($annotation->callback);
isset($annotation->memo) && $crontab->setMemo($annotation->memo);
isset($annotation->enable) && $crontab->setEnable($annotation->enable);
isset($annotation->enable) && $crontab->setEnable($this->resolveCrontabEnableMethod($annotation->enable));
return $crontab;
}
/**
* @param array|bool $enable
*/
private function resolveCrontabEnableMethod($enable): bool
{
if (is_bool($enable)) {
return $enable;
}
$className = reset($enable);
$method = end($enable);
try {
$reflectionClass = ReflectionManager::reflectClass($className);
$reflectionMethod = $reflectionClass->getMethod($method);
if ($reflectionMethod->isPublic()) {
if ($reflectionMethod->isStatic()) {
return $className::$method();
}
$container = ApplicationContext::getContainer();
if ($container->has($className)) {
return $container->get($className)->{$method}();
}
}
$this->logger->info('Crontab enable method is not public, skip register.');
} catch (\ReflectionException $e) {
$this->logger->error('Resolve crontab enable failed, skip register.');
}
return false;
}
}

View File

@ -0,0 +1,66 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace HyperfTest\Crontab;
use Hyperf\Crontab\Annotation\Crontab;
use HyperfTest\Crontab\Stub\FooCron;
use PHPUnit\Framework\TestCase;
/**
* @internal
* @coversNothing
*/
class CrontabAnnotationTest extends TestCase
{
public function testCallableNotExist()
{
$this->expectException(\InvalidArgumentException::class);
$annotation = new Crontab();
$annotation->collectClass(FooCron::class);
}
public function testCallableNormal()
{
$annotation = new Crontab();
$annotation->callback = 'execute';
$annotation->collectClass(FooCron::class);
$this->assertEquals([FooCron::class, 'execute'], $annotation->callback);
}
public function testEnable()
{
$annotation = new Crontab();
$annotation->callback = 'execute';
$annotation->collectClass(FooCron::class);
$this->assertTrue($annotation->enable);
$annotation = new Crontab();
$annotation->callback = 'execute';
$annotation->enable = 'true';
$annotation->collectClass(FooCron::class);
$this->assertTrue($annotation->enable);
$annotation = new Crontab();
$annotation->callback = 'execute';
$annotation->enable = 'isEnable';
$annotation->collectClass(FooCron::class);
$this->assertEquals([FooCron::class, 'isEnable'], $annotation->enable);
}
public function testCollectMethod()
{
$annotation = new Crontab();
$annotation->collectMethod(FooCron::class, 'cron');
$this->assertSame(FooCron::class . '::cron', $annotation->name);
$this->assertSame([FooCron::class, 'cron'], $annotation->callback);
}
}

View File

@ -0,0 +1,122 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace HyperfTest\Crontab;
use Hyperf\Config\Config;
use Hyperf\Contract\ConfigInterface;
use Hyperf\Contract\StdoutLoggerInterface;
use Hyperf\Crontab\Annotation\Crontab;
use Hyperf\Crontab\Annotation\Crontab as CrontabAnnotation;
use Hyperf\Crontab\CrontabManager;
use Hyperf\Crontab\Listener\CrontabRegisterListener;
use Hyperf\Crontab\Parser;
use Hyperf\Di\Annotation\AnnotationCollector;
use Hyperf\Di\Container;
use Hyperf\Utils\ApplicationContext;
use Hyperf\Utils\Reflection\ClassInvoker;
use HyperfTest\Crontab\Stub\FooCron;
use HyperfTest\Crontab\Stub\FooCron2;
use Mockery;
use PHPUnit\Framework\TestCase;
use Psr\Container\ContainerInterface;
/**
* @internal
* @coversNothing
*/
class CrontabRegisterListenerTest extends TestCase
{
protected $prevContainer;
protected function setUp(): void
{
if (ApplicationContext::hasContainer()) {
$this->prevContainer = ApplicationContext::getContainer();
}
}
protected function tearDown(): void
{
if ($this->prevContainer instanceof ContainerInterface) {
ApplicationContext::setContainer($this->prevContainer);
}
}
public function testBuildCrontabWithoutConstructorByAnnotationEnableMethod()
{
$container = Mockery::mock(Container::class);
$container->shouldReceive('has')->with(FooCron2::class)->andReturnTrue();
$container->shouldReceive('get')->with(FooCron2::class)->andReturn(new FooCron2());
ApplicationContext::setContainer($container);
$crontabAnnotation = new Crontab();
$crontabAnnotation->callback = 'execute';
$crontabAnnotation->enable = 'isEnable';
$crontabAnnotation->collectClass(FooCron2::class);
$annotationCrontabs = AnnotationCollector::getClassesByAnnotation(CrontabAnnotation::class);
$manager = new CrontabManager(new Parser());
$class = new ClassInvoker(new CrontabRegisterListener($manager, Mockery::mock(StdoutLoggerInterface::class), Mockery::mock(ConfigInterface::class)));
$crontab = $class->buildCrontabByAnnotation($annotationCrontabs[FooCron2::class]);
$this->assertTrue($crontab->isEnable());
}
public function testBuildCrontabWithConstructorByAnnotationEnableMethod()
{
$container = Mockery::mock(Container::class);
$container->shouldReceive('has')->with(FooCron::class)->andReturnTrue();
$container->shouldReceive('get')->with(FooCron::class)->andReturn(new FooCron(new Config([
'enable' => true,
])));
ApplicationContext::setContainer($container);
$crontabAnnotation = new Crontab();
$crontabAnnotation->callback = 'execute';
$crontabAnnotation->enable = 'isEnable';
$crontabAnnotation->collectClass(FooCron::class);
$annotationCrontabs = AnnotationCollector::getClassesByAnnotation(CrontabAnnotation::class);
$manager = new CrontabManager(new Parser());
$class = new ClassInvoker(new CrontabRegisterListener($manager, Mockery::mock(StdoutLoggerInterface::class), Mockery::mock(ConfigInterface::class)));
$crontab = $class->buildCrontabByAnnotation($annotationCrontabs[FooCron::class]);
$this->assertTrue($crontab->isEnable());
}
public function testBuildCrontabWithStaticMethodByAnnotationEnableMethod()
{
$container = Mockery::mock(Container::class);
$container->shouldReceive('has')->with(FooCron::class)->andReturnTrue();
$container->shouldReceive('get')->with(FooCron::class)->andReturn(new FooCron(new Config([
'enable' => true,
])));
ApplicationContext::setContainer($container);
$crontabAnnotation = new Crontab();
$crontabAnnotation->callback = 'execute';
$crontabAnnotation->enable = 'isEnableCrontab';
$crontabAnnotation->collectClass(FooCron::class);
$annotationCrontabs = AnnotationCollector::getClassesByAnnotation(CrontabAnnotation::class);
$manager = new CrontabManager(new Parser());
$class = new ClassInvoker(new CrontabRegisterListener($manager, Mockery::mock(StdoutLoggerInterface::class), Mockery::mock(ConfigInterface::class)));
$crontab = $class->buildCrontabByAnnotation($annotationCrontabs[FooCron::class]);
$this->assertTrue($crontab->isEnable());
}
}

Binary file not shown.

View File

@ -26,6 +26,11 @@ class ParserCronNumberTest extends TestCase
ini_set('date.timezone', 'Asia/Shanghai');
}
protected function tearDown(): void
{
ini_set('date.timezone', '');
}
public function testParse()
{
$parser = new Parser();

View File

@ -26,6 +26,11 @@ class ParserTest extends TestCase
ini_set('date.timezone', 'Asia/Shanghai');
}
protected function tearDown(): void
{
ini_set('date.timezone', '');
}
public function testParseSecondLevel()
{
$crontabString = '*/11 * * * * *';

View File

@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace HyperfTest\Crontab\Stub;
use Hyperf\Contract\ConfigInterface;
class FooCron
{
protected $config;
public function __construct(ConfigInterface $config)
{
$this->config = $config->get('enable', false);
}
public function execute()
{
}
public function isEnable()
{
return (bool) $this->config;
}
public static function isEnableCrontab(): bool
{
return true;
}
protected function bar()
{
}
}

View File

@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace HyperfTest\Crontab\Stub;
class FooCron2
{
public function execute()
{
}
public function isEnable()
{
return true;
}
}

View File

@ -28,7 +28,6 @@ use Psr\EventDispatcher\EventDispatcherInterface;
use Psr\EventDispatcher\StoppableEventInterface;
/**
* @mixin Builder
* @mixin ModelIDE
*/
abstract class Model implements ArrayAccess, Arrayable, Jsonable, JsonSerializable, CompressInterface

View File

@ -324,7 +324,7 @@ class Builder
{
[$query, $bindings] = $this->createSub($query);
return $this->fromRaw('(' . $query . ') as ' . $this->grammar->wrap($as), $bindings);
return $this->fromRaw('(' . $query . ') as ' . $this->grammar->wrapTable($as), $bindings);
}
/**
@ -455,7 +455,7 @@ class Builder
{
[$query, $bindings] = $this->createSub($query);
$expression = '(' . $query . ') as ' . $this->grammar->wrap($as);
$expression = '(' . $query . ') as ' . $this->grammar->wrapTable($as);
$this->addBinding($bindings, 'join');

View File

@ -659,6 +659,25 @@ class DatabaseQueryBuilderTest extends TestCase
$this->assertEquals([], $builder->getBindings());
}
public function testJoinSubWithPrefix()
{
$builder = $this->getBuilder();
$builder->getGrammar()->setTablePrefix('prefix_');
$builder->from('users')->joinSub('select * from "contacts"', 'sub', 'users.id', '=', 'sub.id');
$this->assertEquals('select * from "prefix_users" inner join (select * from "contacts") as "prefix_sub" on "prefix_users"."id" = "prefix_sub"."id"', $builder->toSql());
}
public function testFromSubWithPrefix()
{
$builder = $this->getBuilder();
$builder->getGrammar()->setTablePrefix('prefix_');
$builder->fromSub(function ($query) {
$query->select(new Raw('max(last_seen_at) as last_seen_at'))->from('user_sessions')->where('foo', '=', '1');
}, 'sessions')->where('bar', '<', '10');
$this->assertEquals('select * from (select max(last_seen_at) as last_seen_at from "prefix_user_sessions" where "foo" = ?) as "prefix_sessions" where "bar" < ?', $builder->toSql());
$this->assertEquals(['1', '10'], $builder->getBindings());
}
protected function getBuilder()
{
$grammar = new Grammar();

View File

@ -78,7 +78,9 @@ trait Cacheable
{
$res = parent::increment($column, $amount, $extra);
if ($res > 0) {
if (empty($extra)) {
if ($this->getConnection()->transactionLevel() && $this instanceof CacheableInterface) {
InvalidCacheManager::instance()->push($this);
} elseif (empty($extra)) {
// Only increment a column's value.
/** @var Manager $manager */
$manager = $this->getContainer()->get(Manager::class);
@ -101,7 +103,9 @@ trait Cacheable
{
$res = parent::decrement($column, $amount, $extra);
if ($res > 0) {
if (empty($extra)) {
if ($this->getConnection()->transactionLevel() && $this instanceof CacheableInterface) {
InvalidCacheManager::instance()->push($this);
} elseif (empty($extra)) {
// Only decrement a column's value.
/** @var Manager $manager */
$manager = $this->getContainer()->get(Manager::class);

View File

@ -12,10 +12,13 @@ declare(strict_types=1);
namespace HyperfTest\ModelCache;
use Hyperf\Database\Model\Relations\Relation;
use Hyperf\DbConnection\Db;
use Hyperf\DbConnection\Listener\InitTableCollectorListener;
use Hyperf\ModelCache\EagerLoad\EagerLoader;
use Hyperf\ModelCache\InvalidCacheManager;
use Hyperf\ModelCache\Listener\EagerLoadListener;
use Hyperf\Redis\RedisProxy;
use Hyperf\Utils\Reflection\ClassInvoker;
use HyperfTest\ModelCache\Stub\BookModel;
use HyperfTest\ModelCache\Stub\ContainerStub;
use HyperfTest\ModelCache\Stub\ImageModel;
@ -381,4 +384,104 @@ class ModelCacheTest extends TestCase
BookModel::findFromCache(1);
$this->assertSame(100, $redis->ttl('{mc:default:m:book}:id:1'));
}
public function testModelSaveInTransaction()
{
$container = ContainerStub::mockContainer();
$id = 209;
UserModel::query()->firstOrCreate(['id' => $id], [
'name' => uniqid(),
'gender' => 1,
]);
$redis = $container->make(RedisProxy::class, ['pool' => 'default']);
wait(function () use ($redis, $id) {
Db::beginTransaction();
try {
$model = UserModel::findFromCache($id);
/* @var \Redis $redis */
$this->assertEquals(1, $redis->exists('{mc:default:m:user}:id:' . $id));
$model->gender = 2;
$model->save();
$this->assertEquals(1, $redis->hGet('{mc:default:m:user}:id:' . $id, 'gender'));
$invoker = new ClassInvoker(InvalidCacheManager::instance());
$this->assertSame(1, count($invoker->models));
Db::commit();
} catch (\Throwable $exception) {
Db::rollBack();
}
});
$this->assertSame(0, $redis->exists('{mc:default:m:user}:id:' . $id));
UserModel::query(true)->where('id', $id)->delete();
}
public function testModelIncrInTransaction()
{
$container = ContainerStub::mockContainer();
$id = 209;
UserModel::query()->firstOrCreate(['id' => $id], [
'name' => uniqid(),
'gender' => 1,
]);
$redis = $container->make(RedisProxy::class, ['pool' => 'default']);
wait(function () use ($redis, $id) {
Db::beginTransaction();
try {
$model = UserModel::findFromCache($id);
/* @var \Redis $redis */
$this->assertEquals(1, $redis->exists('{mc:default:m:user}:id:' . $id));
$model->increment('gender');
$this->assertEquals(1, $redis->hGet('{mc:default:m:user}:id:' . $id, 'gender'));
$invoker = new ClassInvoker(InvalidCacheManager::instance());
$this->assertSame(1, count($invoker->models));
Db::commit();
} catch (\Throwable $exception) {
Db::rollBack();
}
});
$this->assertSame(0, $redis->exists('{mc:default:m:user}:id:' . $id));
UserModel::query(true)->where('id', $id)->delete();
}
public function testModelDecrInTransaction()
{
$container = ContainerStub::mockContainer();
$id = 209;
UserModel::query()->firstOrCreate(['id' => $id], [
'name' => uniqid(),
'gender' => 1,
]);
$redis = $container->make(RedisProxy::class, ['pool' => 'default']);
wait(function () use ($redis, $id) {
Db::beginTransaction();
try {
$model = UserModel::findFromCache($id);
/* @var \Redis $redis */
$this->assertEquals(1, $redis->exists('{mc:default:m:user}:id:' . $id));
$model->decrement('gender');
$this->assertEquals(1, $redis->hGet('{mc:default:m:user}:id:' . $id, 'gender'));
$invoker = new ClassInvoker(InvalidCacheManager::instance());
$this->assertSame(1, count($invoker->models));
Db::commit();
} catch (\Throwable $exception) {
Db::rollBack();
}
});
$this->assertSame(0, $redis->exists('{mc:default:m:user}:id:' . $id));
UserModel::query(true)->where('id', $id)->delete();
}
}

View File

@ -22,6 +22,7 @@ use Hyperf\Database\Model\Events\Deleted;
use Hyperf\Database\Model\Events\Saved;
use Hyperf\DbConnection\Collector\TableCollector;
use Hyperf\DbConnection\ConnectionResolver;
use Hyperf\DbConnection\Db;
use Hyperf\DbConnection\Frequency;
use Hyperf\DbConnection\Pool\DbPool;
use Hyperf\DbConnection\Pool\PoolFactory;
@ -42,6 +43,7 @@ use Hyperf\Redis\Pool\RedisPool;
use Hyperf\Redis\RedisProxy;
use Hyperf\Utils\ApplicationContext;
use Hyperf\Utils\Packer\PhpSerializerPacker;
use Hyperf\Utils\Waiter;
use Mockery;
use Psr\EventDispatcher\EventDispatcherInterface;
use Psr\Log\LogLevel;
@ -168,6 +170,8 @@ class ContainerStub
$container->shouldReceive('get')->with(Manager::class)->andReturn(new Manager($container));
$container->shouldReceive('get')->with(PhpSerializerPacker::class)->andReturn(new PhpSerializerPacker());
$container->shouldReceive('get')->with(EagerLoader::class)->andReturn(new EagerLoader());
$container->shouldReceive('get')->with(Waiter::class)->andReturn(new Waiter());
$container->shouldReceive('get')->with(Db::class)->andReturn(new Db($container));
return $container;
}
}

View File

@ -127,7 +127,11 @@ class SwowServer implements ServerInterface
$handler->initCoreMiddleware($name);
}
if ($server instanceof HttpServer) {
$server->handle([$handler, $method]);
$server->handle(static function ($request, $session) use ($handler, $method) {
wait(static function () use ($request, $session, $handler, $method) {
$handler->{$method}($request, $session);
});
});
}
}
return;

View File

@ -302,7 +302,7 @@ class Server implements MiddlewareInitializerInterface, OnHandShakeInterface, On
};
if ($server instanceof SwooleResponse) {
$onOpen();
wait($onOpen);
} else {
defer($onOpen);
}

View File

@ -0,0 +1,61 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace HyperfTest\WebSocketServer;
use Hyperf\Contract\StdoutLoggerInterface;
use Hyperf\Dispatcher\HttpDispatcher;
use Hyperf\ExceptionHandler\ExceptionHandlerDispatcher;
use Hyperf\HttpServer\ResponseEmitter;
use Hyperf\Utils\ApplicationContext;
use Hyperf\Utils\Coroutine;
use Hyperf\Utils\Reflection\ClassInvoker;
use Hyperf\Utils\Waiter;
use Hyperf\WebSocketServer\Server;
use HyperfTest\WebSocketServer\Stub\WebSocketStub;
use Mockery;
use PHPUnit\Framework\TestCase;
use Psr\Container\ContainerInterface;
use Swoole\Http\Request as SwooleRequest;
use Swoole\Http\Response as SwooleResponse;
/**
* @internal
* @coversNothing
*/
class ServerTest extends TestCase
{
protected function tearDown(): void
{
Mockery::close();
}
public function testDeferOnOpenInCoroutineStyleServer()
{
$container = Mockery::mock(ContainerInterface::class);
ApplicationContext::setContainer($container);
$container->shouldReceive('get')->with(WebSocketStub::class)->andReturn(new WebSocketStub());
$container->shouldReceive('get')->with(Waiter::class)->andReturn(new Waiter());
$server = new Server(
$container,
Mockery::mock(HttpDispatcher::class),
Mockery::mock(ExceptionHandlerDispatcher::class),
Mockery::mock(ResponseEmitter::class),
Mockery::mock(StdoutLoggerInterface::class),
);
$server = new ClassInvoker($server);
$server->deferOnOpen(new SwooleRequest(), WebSocketStub::class, new SwooleResponse());
$this->assertNotEquals(Coroutine::id(), WebSocketStub::$coroutineId);
$this->assertFalse(\Swoole\Coroutine::exists(WebSocketStub::$coroutineId));
}
}

View File

@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace HyperfTest\WebSocketServer\Stub;
use Hyperf\Contract\OnOpenInterface;
use Hyperf\Utils\Coroutine;
use Swoole\Http\Request;
class WebSocketStub implements OnOpenInterface
{
public static $coroutineId = 0;
public function onOpen($server, Request $request): void
{
static::$coroutineId = Coroutine::id();
}
}