Merge branch 'master' into pr/717

This commit is contained in:
李铭昕 2019-10-17 10:11:07 +08:00
commit b92c35f8c2
22 changed files with 275 additions and 68 deletions

View File

@ -51,9 +51,6 @@ return PhpCsFixer\Config::create()
'comment_types' => [
],
],
'list_syntax' => [
'syntax' => 'short',
],
'yoda_style' => [
'always_move_variable' => false,
'equal' => false,

View File

@ -5,11 +5,11 @@ sudo: required
matrix:
include:
- php: 7.2
env: SW_VERSION="4.4.5"
env: SW_VERSION="4.4.7"
- php: 7.3
env: SW_VERSION="4.4.5"
env: SW_VERSION="4.4.7"
- php: master
env: SW_VERSION="4.4.5"
env: SW_VERSION="4.4.7"
allow_failures:
- php: master

View File

@ -1,15 +1,23 @@
# v1.1.2 - TBD
## Added
- [#722](https://github.com/hyperf-cloud/hyperf/pull/722) Added config `concurrent.limit` for AMQP consumer.
## Changed
- [#678](https://github.com/hyperf-cloud/hyperf/pull/678) Added ignore-tables for `gen:model`, and ignore `migrations` table, and `migrations` table will not generate when execute the `gen:model` command.
## Fixed
- [#678](https://github.com/hyperf-cloud/hyperf/pull/678) Added ignore-tables for `gen:model`, and ignore `migrations` table.
- [#694](https://github.com/hyperf-cloud/hyperf/pull/694) Fixed `validationData` method of `Hyperf\Validation\Request\FormRequest` does not contains the uploaded files.
- [#700](https://github.com/hyperf-cloud/hyperf/pull/700) Fixed the `download` method of `Hyperf\HttpServer\Contract\ResponseInterface` does not works as expected.
- [#701](https://github.com/hyperf-cloud/hyperf/pull/701) Fixed the custom process will not restart automatically when throw an uncaptured exception.
- [#704](https://github.com/hyperf-cloud/hyperf/pull/704) Fixed bug that `Call to a member function getName() on null` in `Hyperf\Validation\Middleware\ValidationMiddleware` when the argument of action method does not define the argument type.
- [#713](https://github.com/hyperf-cloud/hyperf/pull/713) Fixed `ignoreAnnotations` does not works when cache is used.
- [#717](https://github.com/hyperf-cloud/hyperf/pull/717) Fixed the validator will be created repeatedly in `getValidatorInstance`.
- [#724](https://github.com/hyperf-cloud/hyperf/pull/724) Fixed `db:seed` command without database selected.
# v1.1.1 - 2019-10-08

View File

@ -9,6 +9,18 @@ composer require hyperf/amqp
```
## 默认配置
| 配置 | 类型 | 默认值 | 备注 |
|:----------------:|:------:|:---------:|:--------------:|
| host | string | localhost | Host |
| port | int | 5672 | 端口号 |
| user | string | guest | 用户名 |
| password | string | guest | 密码 |
| vhost | string | / | vhost |
| concurrent.limit | int | 0 | 同时消费的数量 |
| pool | object | | 连接池配置 |
| params | object | | 基本配置 |
```php
<?php
@ -19,6 +31,9 @@ return [
'user' => 'guest',
'password' => 'guest',
'vhost' => '/',
'concurrent' => [
'limit' => 1,
],
'pool' => [
'min_connections' => 1,
'max_connections' => 10,
@ -150,9 +165,9 @@ class DemoConsumer extends ConsumerMessage
框架会根据 `Consumer` 内的 `consume` 方法所返回的结果来决定该消息的响应行为,共有 4 中响应结果,分别为 `\Hyperf\Amqp\Result::ACK`、`\Hyperf\Amqp\Result::NACK`、`\Hyperf\Amqp\Result::REQUEUE`、`\Hyperf\Amqp\Result::DROP`,每个返回值分别代表如下行为:
| 返回值 | 行为 |
|-------------------------------|-----|
| \Hyperf\Amqp\Result::ACK | 确认消息正确被消费掉了 |
| \Hyperf\Amqp\Result::NACK | 消息没有被正确消费掉,以 `basic_nack` 方法来响应 |
| \Hyperf\Amqp\Result::REQUEUE | 消息没有被正确消费掉,以 `basic_reject` 方法来响应,并使消息重新入列 |
| \Hyperf\Amqp\Result::DROP | 消息没有被正确消费掉,以 `basic_reject` 方法来响应 |
| 返回值 | 行为 |
|------------------------------|----------------------------------------------------------------------|
| \Hyperf\Amqp\Result::ACK | 确认消息正确被消费掉了 |
| \Hyperf\Amqp\Result::NACK | 消息没有被正确消费掉,以 `basic_nack` 方法来响应 |
| \Hyperf\Amqp\Result::REQUEUE | 消息没有被正确消费掉,以 `basic_reject` 方法来响应,并使消息重新入列 |
| \Hyperf\Amqp\Result::DROP | 消息没有被正确消费掉,以 `basic_reject` 方法来响应 |

View File

@ -26,11 +26,11 @@ $db->connect($config, function ($db, $r) {
// 从 users 表中查询一条数据
$sql = 'select * from users where id = 1';
$db->query($sql, function(swoole_mysql $db, $r) {
if ($r === true) {
$rows = $db->affected_rows;
if ($r !== false) {
// 查询成功后修改一条数据
$updateSql = 'update users set name='new name' where id = 1';
$updateSql = 'update users set name="new name" where id = 1';
$db->query($updateSql, function (swoole_mysql $db, $r) {
$rows = $db->affected_rows;
if ($r === true) {
return $this->response->end('更新成功');
}
@ -40,6 +40,9 @@ $db->connect($config, function ($db, $r) {
});
});
```
> 注意 `MySQL` 等异步模块已在[4.3.0](https://wiki.swoole.com/wiki/page/p-4.3.0.html)中移除,并转移到了[swoolw_async](https://github.com/swoole/ext-async)。
从上面的代码片段可以看出,每一个操作几乎就需要一个回调函数,在复杂的业务场景中回调的层次感和代码结构绝对会让你崩溃,其实不难看出这样的写法有点类似 `JavaScript` 上的异步方法的写法,而 `JavaScript` 也为此提供了不少的解决方案(当然方案是源于其它编程语言),如 `Promise``yield + generator`, `async/await``Promise` 则是对回调的一种封装方式,而 `yield + generator``async/await` 则需要在代码上显性的增加一些代码语法标记,这些相对比回调函数来说,不妨都是一些非常不错的解决方案,但是你需要另花时间来理解它的实现机制和语法。
Swoole 协程也是对异步回调的一种解决方案,在 `PHP` 语言下,`Swoole` 协程与 `yield + generator` 都属于协程的解决方案,协程的解决方案可以使代码以近乎于同步代码的书写方式来书写异步代码,显性的区别则是 `yield + generator` 的协程机制下,每一处 `I/O` 操作的调用代码都需要在前面加上 `yield` 语法实现协程切换,每一层调用都需要加上,否则会出现意料之外的错误,而 `Swoole` 协程的解决方案对比于此就高明多了,在遇到 `I/O` 时底层自动的进行隐式协程切换,无需添加任何的额外语法,无需在代码前加上 `yield`,协程切换的过程无声无息,极大的减轻了维护异步系统的心智负担。
@ -266,4 +269,4 @@ use Hyperf\Utils\Context;
$request = Context::override(ServerRequestInterface::class, function (ServerRequestInterface $request) {
return $request->withAddedHeader('foo', 'bar');
});
```
```

View File

@ -14,7 +14,7 @@ $ php bin/hyperf.php gen:model table_name
1.0.* 版本:
```
$ php bin/hyperf.php gen:model table_name
$ php bin/hyperf.php db:model table_name
```
创建的模型如下

View File

@ -3,7 +3,7 @@
## 简介
Hyperf 默认采用 [hyperf/di](https://github.com/hyperf-cloud/di) 作为框架的依赖注入管理容器,尽管从设计上我们允许您更换其它的依赖注入管理容器,但我们强烈不建议您更换该组件。
[hyperf/di](https://github.com/hyperf-cloud/di) 是一个强大的用于管理类的依赖关并完成自动注入的组件,与传统依赖注入容器的区别在于更符合长生命周期的应用使用、提供了 [注解及注解注入](zh/annotation.md) 的支持、提供了无比强大的 [AOP 面向切面编程](zh/aop.md) 能力,这些能力及易用性作为 Hyperf 的核心输出,我们自信的认为该组件是最优秀的。
[hyperf/di](https://github.com/hyperf-cloud/di) 是一个强大的用于管理类的依赖关并完成自动注入的组件,与传统依赖注入容器的区别在于更符合长生命周期的应用使用、提供了 [注解及注解注入](zh/annotation.md) 的支持、提供了无比强大的 [AOP 面向切面编程](zh/aop.md) 能力,这些能力及易用性作为 Hyperf 的核心输出,我们自信的认为该组件是最优秀的。
## 安装

View File

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

View File

@ -73,6 +73,7 @@ Router::addGroup(
```php
<?php
namespace App\Controller;
use App\Middleware\FooMiddleware;
use Hyperf\HttpServer\Annotation\AutoController;
@ -95,6 +96,7 @@ class IndexController
```php
<?php
namespace App\Controller;
use App\Middleware\BarMiddleware;
use App\Middleware\FooMiddleware;
@ -124,6 +126,7 @@ class IndexController
```php
<?php
namespace App\Controller;
use App\Middleware\BarMiddleware;
use App\Middleware\FooMiddleware;

View File

@ -83,7 +83,7 @@ registry.cn-hangzhou.aliyuncs.com/log-service/logtail
| Label白名单 | app.name | hyperf-demo-api |
| 模式 | 完整正则模式 | |
| 单行模式 | false | |
| 日志样例 | [2019-03-07 11:58:57] hyperf.WARNING: xxx | |
| 日志样例 | `[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+):(.*) | |

View File

@ -36,13 +36,11 @@ return [
```php
<?php
use Hyperf\Validation\ValidationExceptionHandler;
return [
'handler' => [
// 这里对应您当前的 Server 名称
'http' => [
ValidationExceptionHandler::class,
\Hyperf\Validation\ValidationExceptionHandler::class,
],
],
];

View File

@ -17,6 +17,9 @@ return [
'user' => env('AMQP_USER', 'guest'),
'password' => env('AMQP_PASSWORD', 'guest'),
'vhost' => env('AMQP_VHOST', '/'),
'concurrent' => [
'limit' => 1,
],
'pool' => [
'min_connections' => 1,
'max_connections' => 10,

View File

@ -16,7 +16,9 @@ use Hyperf\Amqp\Exception\MessageException;
use Hyperf\Amqp\Message\ConsumerMessageInterface;
use Hyperf\Amqp\Message\MessageInterface;
use Hyperf\Amqp\Pool\PoolFactory;
use Hyperf\Contract\ConfigInterface;
use Hyperf\ExceptionHandler\Formatter\FormatterInterface;
use Hyperf\Utils\Coroutine\Concurrent;
use PhpAmqpLib\Channel\AMQPChannel;
use PhpAmqpLib\Message\AMQPMessage;
use Psr\Container\ContainerInterface;
@ -52,6 +54,7 @@ class Consumer extends Builder
$channel = $connection->getConfirmChannel();
$this->declare($consumerMessage, $channel);
$concurrent = $this->getConcurrent();
$channel->basic_consume(
$consumerMessage->getQueue(),
@ -60,41 +63,13 @@ class Consumer extends Builder
false,
false,
false,
function (AMQPMessage $message) use ($consumerMessage) {
$data = $consumerMessage->unserialize($message->getBody());
/** @var AMQPChannel $channel */
$channel = $message->delivery_info['channel'];
$deliveryTag = $message->delivery_info['delivery_tag'];
[$result] = parallel([function () use ($consumerMessage, $data) {
try {
return $consumerMessage->consume($data);
} catch (Throwable $exception) {
if ($this->container->has(FormatterInterface::class)) {
$formatter = $this->container->get(FormatterInterface::class);
$this->logger->error($formatter->format($exception));
} else {
$this->logger->error($exception->getMessage());
}
return Result::DROP;
}
}]);
if ($result === Result::ACK) {
$this->logger->debug($deliveryTag . ' acked.');
return $channel->basic_ack($deliveryTag);
}
if ($result === Result::NACK) {
$this->logger->debug($deliveryTag . ' uacked.');
return $channel->basic_nack($deliveryTag);
}
if ($consumerMessage->isRequeue() && $result === Result::REQUEUE) {
$this->logger->debug($deliveryTag . ' requeued.');
return $channel->basic_reject($deliveryTag, true);
function (AMQPMessage $message) use ($consumerMessage, $concurrent) {
$callback = $this->getCallback($consumerMessage, $message);
if (! $concurrent instanceof Concurrent) {
return parallel([$callback]);
}
$this->logger->debug($deliveryTag . ' rejected.');
$channel->basic_reject($deliveryTag, false);
return $concurrent->create($callback);
}
);
@ -129,4 +104,54 @@ class Consumer extends Builder
$channel->queue_bind($message->getQueue(), $message->getExchange(), $routingKey);
}
}
protected function getConcurrent(): ?Concurrent
{
$config = $this->container->get(ConfigInterface::class);
$concurrent = (int) $config->get('amqp.' . $this->name . '.concurrent.limit', 0);
if ($concurrent > 1) {
return new Concurrent($concurrent);
}
return null;
}
protected function getCallback(ConsumerMessageInterface $consumerMessage, AMQPMessage $message)
{
return function () use ($consumerMessage, $message) {
$data = $consumerMessage->unserialize($message->getBody());
/** @var AMQPChannel $channel */
$channel = $message->delivery_info['channel'];
$deliveryTag = $message->delivery_info['delivery_tag'];
try {
$result = $consumerMessage->consume($data);
} catch (Throwable $exception) {
if ($this->container->has(FormatterInterface::class)) {
$formatter = $this->container->get(FormatterInterface::class);
$this->logger->error($formatter->format($exception));
} else {
$this->logger->error($exception->getMessage());
}
$result = Result::DROP;
}
if ($result === Result::ACK) {
$this->logger->debug($deliveryTag . ' acked.');
return $channel->basic_ack($deliveryTag);
}
if ($result === Result::NACK) {
$this->logger->debug($deliveryTag . ' uacked.');
return $channel->basic_nack($deliveryTag);
}
if ($consumerMessage->isRequeue() && $result === Result::REQUEUE) {
$this->logger->debug($deliveryTag . ' requeued.');
return $channel->basic_reject($deliveryTag, true);
}
$this->logger->debug($deliveryTag . ' rejected.');
return $channel->basic_reject($deliveryTag, false);
};
}
}

View File

@ -48,7 +48,7 @@ class InfoCommand extends HyperfCommand
protected function configure()
{
$this->setDescription('Delete all message from failed queue.');
$this->setDescription('Get all messages from the queue.');
$this->addArgument('name', InputArgument::OPTIONAL, 'The name of queue.', 'default');
}
}

View File

@ -64,7 +64,7 @@ class SeedCommand extends BaseCommand
$this->seed->setOutput($this->output);
if ($this->input->hasOption('database')) {
if ($this->input->hasOption('database') && $this->input->getOption('database')) {
$this->seed->setConnection($this->input->getOption('database'));
}

View File

@ -25,18 +25,16 @@ class Scanner
*/
private $parser;
/**
* @var array
*/
private $ignoreAnnotations = [];
public function __construct(array $ignoreAnnotations = ['mixin'])
{
$this->parser = new Ast();
$this->ignoreAnnotations = $ignoreAnnotations;
// TODO: this method is deprecated and will be removed in doctrine/annotations 2.0
AnnotationRegistry::registerLoader('class_exists');
foreach ($ignoreAnnotations as $annotation) {
AnnotationReader::addGlobalIgnoredName($annotation);
}
}
public function scan(array $paths): array
@ -49,9 +47,6 @@ class Scanner
$finder = new Finder();
$finder->files()->in($paths)->name('*.php');
array_walk($this->ignoreAnnotations, function ($value) {
AnnotationReader::addGlobalIgnoredName($value);
});
$meta = [];
foreach ($finder as $file) {
try {

View File

@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://doc.hyperf.io
* @contact group@hyperf.io
* @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
*/
namespace HyperfTest\Di;
use Hyperf\Di\Annotation\Scanner;
use HyperfTest\Di\Stub\AnnotationCollector;
use HyperfTest\Di\Stub\Ignore;
use HyperfTest\Di\Stub\IgnoreDemoAnnotation;
use PHPUnit\Framework\TestCase;
/**
* @internal
* @coversNothing
*/
class AnnotationTest extends TestCase
{
public function testIgnoreAnnotations()
{
$scaner = new Scanner([]);
$scaner->collect([Ignore::class]);
$annotations = AnnotationCollector::get(Ignore::class . '._c');
$this->assertArrayHasKey(IgnoreDemoAnnotation::class, $annotations);
AnnotationCollector::clear();
$scaner = new Scanner(['IgnoreDemoAnnotation']);
$scaner->collect([Ignore::class]);
$annotations = AnnotationCollector::get(Ignore::class . '._c');
$this->assertNull($annotations);
}
}

View File

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

View File

@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://doc.hyperf.io
* @contact group@hyperf.io
* @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
*/
namespace HyperfTest\Di\Stub;
use Hyperf\Di\Annotation\AbstractAnnotation;
/**
* @Annotation
* @Target({"CLASS"})
*/
class IgnoreDemoAnnotation extends AbstractAnnotation
{
}

View File

@ -140,7 +140,7 @@ class FormRequest extends Request implements ValidatesWhenResolved
*/
protected function validationData(): array
{
return $this->all();
return array_merge_recursive($this->all(), $this->getUploadedFiles());
}
/**

View File

@ -0,0 +1,71 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://doc.hyperf.io
* @contact group@hyperf.io
* @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
*/
namespace HyperfTest\Validation\Cases;
use Hyperf\HttpMessage\Upload\UploadedFile;
use Hyperf\Utils\Context;
use HyperfTest\Validation\Cases\Stub\DemoRequest;
use Mockery;
use PHPUnit\Framework\TestCase;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ServerRequestInterface;
/**
* @internal
* @coversNothing
*/
class FormRequestTest extends TestCase
{
protected function tearDown()
{
Mockery::close();
Context::set(ServerRequestInterface::class, null);
Context::set('http.request.parsedData', null);
}
public function testRequestValidationData()
{
$psrRequest = Mockery::mock(ServerRequestInterface::class);
$file = new UploadedFile('/tmp/tmp_name', 32, 0);
$psrRequest->shouldReceive('getUploadedFiles')->andReturn([
'file' => $file,
]);
$psrRequest->shouldReceive('getParsedBody')->andReturn([
'id' => 1,
]);
$psrRequest->shouldReceive('getQueryParams')->andReturn([]);
Context::set(ServerRequestInterface::class, $psrRequest);
$request = new DemoRequest(Mockery::mock(ContainerInterface::class));
$this->assertEquals(['id' => 1, 'file' => $file], $request->getValidationData());
}
public function testRequestValidationDataWithSameKey()
{
$psrRequest = Mockery::mock(ServerRequestInterface::class);
$file = new UploadedFile('/tmp/tmp_name', 32, 0);
$psrRequest->shouldReceive('getUploadedFiles')->andReturn([
'file' => [$file],
]);
$psrRequest->shouldReceive('getParsedBody')->andReturn([
'file' => ['Invalid File.'],
]);
$psrRequest->shouldReceive('getQueryParams')->andReturn([]);
Context::set(ServerRequestInterface::class, $psrRequest);
$request = new DemoRequest(Mockery::mock(ContainerInterface::class));
$this->assertEquals(['file' => ['Invalid File.', $file]], $request->getValidationData());
}
}

View File

@ -40,6 +40,11 @@ class DemoRequest extends FormRequest
];
}
public function getValidationData()
{
return parent::validationData();
}
protected function withValidator($request)
{
Context::override('test.validation.DemoRequest.number', function ($id) {