Merge branch 'feat/validation' of https://github.com/chunhei2008/hyperf into feat/validation

This commit is contained in:
wangyi 2019-09-06 16:28:04 +08:00
commit a2ec86b54c
115 changed files with 2508 additions and 2075 deletions

View File

@ -1,4 +1,34 @@
# v1.0.13 - TBD
# v1.0.14 - TBD
## Changed
- [#482](https://github.com/hyperf-cloud/hyperf/pull/482) Re-generate the `fillable` argument of Model when use `refresh-fillable` option, at the same time, the command will keep the `fillable` argument as default behaviours.
- [#501](https://github.com/hyperf-cloud/hyperf/pull/501) When the path argument of Mapping annotation is an empty string, then the path is equal to prefix of Controller annotation.
- [#513](https://github.com/hyperf-cloud/hyperf/pull/513) Rewrite process name with `app_name`.
## Fixed
- [#479](https://github.com/hyperf-cloud/hyperf/pull/479) Fixed typehint error when host of Elasticsearch client does not reached.
- [#508](https://github.com/hyperf-cloud/hyperf/pull/508) Fixed return type error of `Hyperf\Utils\Coroutine::parentId()` when running in non-coroutine environment.
- [#514](https://github.com/hyperf-cloud/hyperf/pull/514) Fixed redis auth failed when the password is an empty string.
# v1.0.13 - 2019-08-28
## Added
- [#428](https://github.com/hyperf-cloud/hyperf/pull/428) Added an independent component [hyperf/translation](https://github.com/hyperf-cloud/translation), forked by illuminate/translation.
- [#449](https://github.com/hyperf-cloud/hyperf/pull/449) Added standard error code for grpc-server.
- [#450](https://github.com/hyperf-cloud/hyperf/pull/450) Added comments of static methods for `Hyperf\Database\Schema\Schema`.
## Changed
- [#451](https://github.com/hyperf-cloud/hyperf/pull/451) Removed routes of magic methods from `AuthController`.
- [#468](https://github.com/hyperf-cloud/hyperf/pull/468) Default exception handlers catch all exceptions.
## Fixed
- [#466](https://github.com/hyperf-cloud/hyperf/pull/466) Fixed error when the number of data is not enough to paginate.
- [#466](https://github.com/hyperf-cloud/hyperf/pull/470) Optimized `vendor:publish` command, if the destination folder exists, then will not repeatedly create the folder.
# v1.0.12 - 2019-08-21

View File

@ -38,8 +38,8 @@
"squizlabs/php_codesniffer": "^3.4",
"symfony/console": "^4.2",
"symfony/finder": "^4.1",
"symfony/http-foundation": "^4.3",
"vlucas/phpdotenv": "^3.1"
"vlucas/phpdotenv": "^3.1",
"egulias/email-validator": "^2.1"
},
"require-dev": {
"doctrine/common": "@stable",
@ -107,6 +107,7 @@
"files": [
"src/config/src/Functions.php",
"src/di/src/Functions.php",
"src/translation/src/Functions.php",
"src/utils/src/Functions.php"
],
"psr-4": {
@ -191,6 +192,7 @@
"HyperfTest\\Etcd\\": "src/etcd/tests/",
"HyperfTest\\Event\\": "src/event/tests/",
"HyperfTest\\GrpcClient\\": "src/grpc-client/tests/",
"HyperfTest\\GrpcServer\\": "src/grpc-server/tests/",
"HyperfTest\\Guzzle\\": "src/guzzle/tests/",
"HyperfTest\\HttpMessage\\": "src/http-message/tests/",
"HyperfTest\\HttpServer\\": "src/http-server/tests/",

103
doc/zh/snowflake.md Normal file
View File

@ -0,0 +1,103 @@
# Snowflake
## 安装
```
composer require hyperf/snowflake
```
## 使用
框架提供了 `MetaGeneratorInterface``IdGeneratorInterface``MetaGeneratorInterface` 会生成 `ID``Meta` 文件,`IdGeneratorInterface` 则会根据对应的 `Meta` 文件生成 `分布式ID`
框架默认使用的 `MetaGeneratorInterface` 是基于 `Redis` 实现的 `毫秒级别生成器`。配置如下:
```php
<?php
declare(strict_types=1);
use Hyperf\Snowflake\MetaGenerator\RedisMilliSecondMetaGenerator;
use Hyperf\Snowflake\MetaGeneratorInterface;
return [
'begin_second' => MetaGeneratorInterface::DEFAULT_BEGIN_SECOND,
RedisMilliSecondMetaGenerator::class => [
'pool' => 'default',
],
];
```
框架中使用 `Snowfalke` 十分简单,只需要从 `DI` 中取出 `IdGeneratorInterface` 对象即可。
```php
<?php
use Hyperf\Snowflake\IdGeneratorInterface;
use Hyperf\Utils\ApplicationContext;
$container = ApplicationContext::getContainer();
$generator = $container->get(IdGeneratorInterface::class);
$id = $generator->generate();
```
当知道 `ID` 需要反推对应的 `Meta` 时,只需要调用 `degenerate` 即可。
```php
<?php
use Hyperf\Snowflake\IdGeneratorInterface;
use Hyperf\Utils\ApplicationContext;
$container = ApplicationContext::getContainer();
$generator = $container->get(IdGeneratorInterface::class);
$meta = $generator->degenerate($id);
```
## 重写 `Meta` 生成器
`分布式ID` 的实现方式多种多样,虽然都是 `Snowflake` 算法,但也不尽相同。比如有人可能会根据 `UserId` 生成 `Meta`,而非 `WorkerId`。接下来,让我们实现一个简单的 `MetaGenerator`
简单的来讲,`UserId` 绝对会超过 `10 bit`,所以默认的 `DataCenterId``WorkerId` 肯定是装不过来的,所以就需要对 `UserId` 取模。
```php
<?php
declare(strict_types=1);
use Hyperf\Snowflake\IdGenerator;
class UserDefinedIdGenerator
{
/**
* @var IdGenerator\SnowflakeIdGenerator
*/
protected $idGenerator;
public function __construct(IdGenerator\SnowflakeIdGenerator $idGenerator)
{
$this->idGenerator = $idGenerator;
}
public function generate(int $userId)
{
$meta = $this->idGenerator->getMetaGenerator()->generate();
return $this->idGenerator->generate($meta->setWorkerId($userId % 31));
}
public function degenerate(int $id)
{
return $this->idGenerator->degenerate($id);
}
}
use Hyperf\Utils\ApplicationContext;
$container = ApplicationContext::getContainer();
$generator = $container->get(UserDefinedIdGenerator::class);
$userId = 20190620;
$id = $generator->generate($userId);
```

138
doc/zh/translation.md Normal file
View File

@ -0,0 +1,138 @@
# 国际化
Hyperf 对国际化的支持是非常友好的,允许让您的项目支持多种语言。
# 安装
```bash
composer require hyperf/translation
```
> 该组件为一个独立组件,无框架相关依赖,可独立复用于其它项目或框架。
# 语言文件
Hyperf 的语言文件默认都放在 `storage/languages` 下面,您也可以在 `config/autoload/translation.php` 内更改语言文件的文件夹,每种语言对应其中的一个子文件夹,例如 `en` 指英文语言文件,`zh-CN` 指中文简体的语言文件,你可以按照实际需要创建新的语言文件夹和里面的语言文件。示例如下:
```
/storage
/languages
/en
messages.php
/zh-CN
messages.php
```
所有的语言文件都是返回一个数组,数组的键是字符串类型的:
```php
<?php
// storage/languages/en/messages.php
return [
'welcome' => 'Welcome to our application',
];
```
## 配置语言环境
关于国际化组件的相关配置都是在 `config/autoload/translation.php` 配置文件里设定的,你可以按照实际需要修改它。
```php
<?php
// config/autoload/translation.php
return [
// 默认语言
'locale' => 'zh_CN',
// 回退语言,当默认语言的语言文本没有提供时,就会使用回退语言的对应语言文本
'fallback_locale' => 'en',
// 语言文件存放的文件夹
'path' => BASE_PATH . '/storage/languages',
];
```
# 翻译字符串
## 通过 TranslatorInterface 翻译
可直接通过注入 `Hyperf\Contact\TranslatorInterface` 并调用实例的 `trans` 方法实现对字符串的翻译:
```php
<?php
use Hyperf\Di\Annotation\Inject;
use Hyperf\Contract\TranslatorInterface;
class FooController
{
/**
* @Inject
* @var TranslatorInterface
*/
private $translator;
public function index()
{
return $this->translator->trans('messages.welcome', [], 'zh-CN');
}
}
```
## 通过全局函数翻译
您也可以通过全局函数 `__()``trans()` 来对字符串进行翻译。
函数的第一个参数使用 `键`(指使用翻译字符串作为键的键) 或者是 `文件.键` 的形式。
```php
echo __('messages.welcome');
echo trans('messages.welcome');
```
# 翻译字符串中定义占位符
您也可以在语言字符串中定义占位符,所有的占位符使用 `:` 作为前缀。例如,把用户名作为占位符:
```php
<?php
// storage/languages/en/messages.php
return [
'welcome' => 'Welcome :name',
];
```
替换占位符使用函数的第二个参数:
```php
echo __('messages.welcome', ['name' => 'Hyperf']);
```
如果占位符全部是大写字母,或者是首字母大写。那么翻译过来的字符串也会是相应的大写形式:
```php
'welcome' => 'Welcome, :NAME', // Welcome, HYPERF
'goodbye' => 'Goodbye, :Name', // Goodbye, HYPERF
```
# 处理复数
不同语言的复数规则是不同的,在中文中可能不太关注这一点,但在翻译其它语言时我们需要处理复数形式的用词。我们可以使用 `「管道」` 字符,可以用来区分字符串的单数和复数形式:
```php
'apples' => 'There is one apple|There are many apples',
```
也可以指定数字范围,创建更加复杂的复数规则:
```php
'apples' => '{0} There are none|[1,19] There are some|[20,*] There are many',
```
使用 `「管道」` 字符,定义好复数规则后,就可以使用全局函数 `trans_choice` 来获得给定 `「数量」` 的字符串文本。在下面的例子中,因为数量大于 `1`,所以就会返回翻译字符串的复数形式:
```php
echo trans_choice('messages.apples', 10);
```
当然除了全局函数 `trans_choice()`,您也可以使用 `Hyperf\Contract\TranslatorInterface``transChoice` 方法。

View File

@ -23,6 +23,7 @@
<directory suffix="Test.php">./src/elasticsearch/tests</directory>
<directory suffix="Test.php">./src/event/tests</directory>
<directory suffix="Test.php">./src/grpc-client/tests</directory>
<directory suffix="Test.php">./src/grpc-server/tests</directory>
<directory suffix="Test.php">./src/guzzle/tests</directory>
<directory suffix="Test.php">./src/http-message/tests</directory>
<directory suffix="Test.php">./src/http-server/tests</directory>

View File

@ -10,42 +10,29 @@ declare(strict_types=1);
* @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Translation\Contracts;
namespace Hyperf\Contract;
interface Translator
interface TranslatorInterface
{
/**
* Get the translation for a given key.
*
* @param string $key
* @param array $replace
* @param null|string $locale
* @return mixed
*/
public function trans(string $key, array $replace = [], $locale = null);
public function trans(string $key, array $replace = [], ?string $locale = null);
/**
* Get a translation according to an integer value.
*
* @param string $key
* @param array|\Countable|int $number
* @param array $replace
* @param null|string $locale
* @return string
*/
public function transChoice(string $key, $number, array $replace = [], $locale = null): string;
public function transChoice(string $key, $number, array $replace = [], ?string $locale = null): string;
/**
* Get the default locale being used.
*
* @return string
*/
public function getLocale(): string;
/**
* Set the default locale.
*
* @param string $locale
*/
public function setLocale(string $locale);
}

View File

@ -10,39 +10,27 @@ declare(strict_types=1);
* @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Translation\Contracts;
namespace Hyperf\Contract;
interface Loader
interface TranslatorLoaderInterface
{
/**
* Load the messages for the given locale.
*
* @param string $locale
* @param string $group
* @param null|string $namespace
* @return array
*/
public function load(string $locale, string $group, $namespace = null): array;
public function load(string $locale, string $group, ?string $namespace = null): array;
/**
* Add a new namespace to the loader.
*
* @param string $namespace
* @param string $hint
*/
public function addNamespace(string $namespace, string $hint);
/**
* Add a new JSON path to the loader.
*
* @param string $path
*/
public function addJsonPath(string $path);
/**
* Get an array of all the registered namespaces.
*
* @return array
*/
public function namespaces(): array;
}

View File

@ -1,47 +1,30 @@
<?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 Hyperf\Contract;
namespace Hyperf\Validation\Contracts\Validation;
use Hyperf\Utils\Contracts\MessageProvider;
use Hyperf\Utils\Contracts\MessageBag;
use Hyperf\Validation\Contracts\Support\MessageProvider;
use Hyperf\Validation\Support\MessageBag;
interface Validator extends MessageProvider
interface ValidatorInterface extends MessageProvider
{
/**
* Run the validator's rules against its data.
*
* @return array
*/
public function validate(): array;
/**
* Get the attributes and values that were validated.
*
* @return array
*/
public function validated(): array;
/**
* Determine if the data fails the validation rules.
*
* @return bool
*/
public function fails(): bool;
/**
* Get the failed validation rules.
*
* @return array
*/
public function failed(): array;
@ -50,7 +33,6 @@ interface Validator extends MessageProvider
*
* @param array|string $attribute
* @param array|string $rules
* @param callable $callback
* @return $this
*/
public function sometimes($attribute, $rules, callable $callback);
@ -65,8 +47,7 @@ interface Validator extends MessageProvider
/**
* Get all of the validation error messages.
*
* @return MessageBag
*/
public function errors();
}
public function errors(): MessageBag;
}

View File

@ -12,24 +12,34 @@ declare(strict_types=1);
namespace Hyperf\Database\Commands\Ast;
use Hyperf\Database\Commands\ModelOption;
use PhpParser\Comment\Doc;
use PhpParser\Node;
use PhpParser\NodeVisitorAbstract;
class ModelUpdateVisitor extends NodeVisitorAbstract
{
/**
* @var array
*/
protected $columns = [];
public function __construct($columns = [])
/**
* @var ModelOption
*/
protected $option;
public function __construct($columns = [], ModelOption $option)
{
$this->columns = $columns;
$this->option = $option;
}
public function leaveNode(Node $node)
{
switch ($node) {
case $node instanceof Node\Stmt\PropertyProperty:
if ($node->name == 'fillable') {
if ($node->name == 'fillable' && $this->option->isRefreshFillable()) {
$node = $this->rewriteFillable($node);
} elseif ($node->name == 'casts') {
$node = $this->rewriteCasts($node);

View File

@ -93,7 +93,8 @@ class ModelCommand extends Command
->setPrefix($this->getOption('prefix', 'prefix', $pool, ''))
->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));
->setForceCasts($this->getOption('force-casts', 'commands.db:model.force_casts', $pool, false))
->setRefreshFillable($this->getOption('refresh-fillable', 'commands.db:model.refresh_fillable', $pool, false));
if ($table) {
$this->createModel($table, $option);
@ -112,6 +113,7 @@ class ModelCommand extends Command
$this->addOption('prefix', 'P', InputOption::VALUE_OPTIONAL, 'What prefix that you want the Model set.');
$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.');
}
protected function getSchemaBuilder(string $poolName): MySqlBuilder
@ -158,7 +160,10 @@ class ModelCommand extends Command
$stms = $this->astParser->parse(file_get_contents($path));
$traverser = new NodeTraverser();
$visitor = make(ModelUpdateVisitor::class, ['columns' => $columns]);
$visitor = make(ModelUpdateVisitor::class, [
'columns' => $columns,
'option' => $option,
]);
$traverser->addVisitor($visitor);
$stms = $traverser->traverse($stms);
$code = $this->printer->prettyPrintFile($stms);
@ -203,7 +208,7 @@ class ModelCommand extends Command
protected function getOption(string $name, string $key, string $pool = 'default', $default = null)
{
$result = $this->input->getOption($name);
$nonInput = $name === 'force-casts' ? false : null;
$nonInput = in_array($name, ['force-casts', 'refresh-fillable']) ? false : null;
if ($result === $nonInput) {
$result = $this->config->get("databases.{$pool}.{$key}", $default);
}

View File

@ -44,6 +44,11 @@ class ModelOption
*/
protected $uses;
/**
* @var bool
*/
protected $refreshFillable;
public function getPool(): string
{
return $this->pool;
@ -99,21 +104,25 @@ class ModelOption
return $this;
}
/**
* @return string
*/
public function getUses(): string
{
return $this->uses;
}
/**
* @param string $uses
* @return ModelOption
*/
public function setUses(string $uses): ModelOption
{
$this->uses = $uses;
return $this;
}
public function isRefreshFillable(): bool
{
return $this->refreshFillable;
}
public function setRefreshFillable(bool $refreshFillable): ModelOption
{
$this->refreshFillable = $refreshFillable;
return $this;
}
}

View File

@ -16,6 +16,28 @@ use Hyperf\Database\ConnectionInterface;
use Hyperf\Database\ConnectionResolverInterface;
use Hyperf\Utils\ApplicationContext;
/**
* @method static bool hasTable(string $table)
* @method static array getColumnListing(string $table)
* @method static array getColumnTypeListing(string $table)
* @method static void dropAllTables()
* @method static void dropAllViews()
* @method static array getAllTables()
* @method static array getAllViews()
* @method static bool hasColumn(string $table, string $column)
* @method static bool hasColumns(string $table, array $columns)
* @method static string getColumnType(string $table, string $column)
* @method static void table(string $table, \Closure $callback)
* @method static void create(string $table, \Closure $callback))
* @method static void drop(string $table)
* @method static void dropIfExists(string $table)
* @method static void rename(string $from, string $to)
* @method static bool enableForeignKeyConstraints()
* @method static bool disableForeignKeyConstraints()
* @method static \Hyperf\Database\Connection getConnection()
* @method static Builder setConnection(\Hyperf\Database\Connection $connection)
* @method static void blueprintResolver(\Closure $resolver)
*/
class Schema
{
public static function __callStatic($name, $arguments)

View File

@ -33,12 +33,12 @@ use Psr\Container\ContainerInterface;
* @method static int affectingStatement(string $query, array $bindings = [])
* @method static bool unprepared(string $query)
* @method static array prepareBindings(array $bindings)
* @method static transaction(Closure $callback, int $attempts = 1)
* @method static transaction(\Closure $callback, int $attempts = 1)
* @method static beginTransaction()
* @method static rollBack()
* @method static commit()
* @method static int transactionLevel()
* @method static array pretend(Closure $callback)
* @method static array pretend(\Closure $callback)
* @method static ConnectionInterface connection(string $pool)
*/
class Db

View File

@ -38,8 +38,11 @@ return [
'middleware' => [
'namespace' => 'App\\Middleware',
],
'Process' => [
'process' => [
'namespace' => 'App\\Process',
],
'request' => [
'namespace' => 'App\\Request',
],
],
];

View File

@ -10,13 +10,11 @@ declare(strict_types=1);
* @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Validation\Request;
namespace Hyperf\Devtool\Generator;
use Hyperf\Command\Annotation\Command;
use Hyperf\Devtool\Generator\GeneratorCommand;
/**
* Class RequestCommand.
* @Command
*/
class RequestCommand extends GeneratorCommand
@ -29,11 +27,11 @@ class RequestCommand extends GeneratorCommand
protected function getStub(): string
{
return $this->getConfig()['stub'] ?? __DIR__ . '/stubs/request.stub';
return $this->getConfig()['stub'] ?? __DIR__ . '/stubs/validation-request.stub';
}
protected function getDefaultNamespace(): string
{
return $this->getConfig()['namespace'] ?? 'App\\Requests';
return $this->getConfig()['namespace'] ?? 'App\\Request';
}
}

View File

@ -113,7 +113,9 @@ class VendorPublishCommand extends SymfonyCommand
continue;
}
mkdir(dirname($destination), 0755, true);
if (! file_exists(dirname($destination))) {
mkdir(dirname($destination), 0755, true);
}
copy($source, $destination);
$this->output->writeln(sprintf('<fg=green>[%s] publish [%s] success.</>', $package, $id));

View File

@ -13,6 +13,7 @@ declare(strict_types=1);
namespace HyperfTest\Elasticsearch;
use Elasticsearch\ClientBuilder;
use Elasticsearch\Common\Exceptions\NoNodesAvailableException;
use Hyperf\Elasticsearch\ClientBuilderFactory;
use PHPUnit\Framework\TestCase;
@ -30,4 +31,15 @@ class ClientFactoryTest extends TestCase
$this->assertInstanceOf(ClientBuilder::class, $client);
}
public function testHostNotReached()
{
$this->expectException(NoNodesAvailableException::class);
$clientFactory = new ClientBuilderFactory();
$client = $clientFactory->create()->setHosts(['http://127.0.0.1:9201'])->build();
$client->info();
}
}

View File

@ -56,6 +56,9 @@ class AnnotationReader
*/
private $mode;
/**
* @var array
*/
private $methodAnnotationCache = [];
/**
@ -101,25 +104,21 @@ class AnnotationReader
public function getRequestAnnotation(ReflectionMethod $refMethod, string $annotationName): ?AbstractRequest
{
/* @var null|AbstractRequest $queryAnnotation */
return $this->getMethodAnnotation($refMethod, $annotationName);
}
public function getLoggedAnnotation(ReflectionMethod $refMethod): ?Logged
{
/* @var null|Logged $loggedAnnotation */
return $this->getMethodAnnotation($refMethod, Logged::class);
}
public function getRightAnnotation(ReflectionMethod $refMethod): ?Right
{
/* @var null|Right $rightAnnotation */
return $this->getMethodAnnotation($refMethod, Right::class);
}
public function getFailWithAnnotation(ReflectionMethod $refMethod): ?FailWith
{
/* @var null|FailWith $failWithAnnotation */
return $this->getMethodAnnotation($refMethod, FailWith::class);
}
@ -128,13 +127,11 @@ class AnnotationReader
*/
public function getSourceFields(ReflectionClass $refClass): array
{
/* @var SourceField[] $sourceFields */
return $this->getClassAnnotations($refClass, SourceField::class);
}
public function getFactoryAnnotation(ReflectionMethod $refMethod): ?Factory
{
/* @var null|Factory $factoryAnnotation */
return $this->getMethodAnnotation($refMethod, Factory::class);
}

View File

@ -34,6 +34,7 @@
},
"autoload-dev": {
"psr-4": {
"HyperfTest\\GrpcServer\\": "tests/"
}
},
"config": {

View File

@ -10,13 +10,10 @@ declare(strict_types=1);
* @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
*/
namespace HyperfTest\Translation\Cases;
namespace Hyperf\GrpcServer\Exception;
use PHPUnit\Framework\TestCase;
use Hyperf\Server\Exception\ServerException;
/**
* Class AbstractTestCase.
*/
abstract class AbstractTestCase extends TestCase
class GrpcException extends ServerException
{
}

View File

@ -15,6 +15,8 @@ namespace Hyperf\GrpcServer\Exception\Handler;
use Hyperf\Contract\StdoutLoggerInterface;
use Hyperf\ExceptionHandler\ExceptionHandler;
use Hyperf\ExceptionHandler\Formatter\FormatterInterface;
use Hyperf\Grpc\StatusCode;
use Hyperf\GrpcServer\Exception\GrpcException;
use Hyperf\Server\Exception\ServerException;
use Psr\Http\Message\ResponseInterface;
use Throwable;
@ -39,27 +41,28 @@ class GrpcExceptionHandler extends ExceptionHandler
public function handle(Throwable $throwable, ResponseInterface $response)
{
$this->logger->warning($this->formatter->format($throwable));
if ($throwable instanceof GrpcException) {
$this->logger->debug($this->formatter->format($throwable));
} else {
$this->logger->warning($this->formatter->format($throwable));
}
return $this->transferToResponse($throwable->getCode(), $throwable->getMessage(), $response);
}
public function isValid(Throwable $throwable): bool
{
return $throwable instanceof ServerException;
return true;
}
/**
* Transfer the non-standard response content to a standard response object.
*
* @param int|string $code
* @param string $message
* @param ResponseInterface $response
*/
protected function transferToResponse($code, $message, ResponseInterface $response): ResponseInterface
protected function transferToResponse(int $code, string $message, ResponseInterface $response): ResponseInterface
{
$response = $response->withAddedHeader('Content-Type', 'application/grpc')
->withAddedHeader('trailer', 'grpc-status, grpc-message');
->withAddedHeader('trailer', 'grpc-status, grpc-message')
->withStatus(StatusCode::HTTP_CODE_MAPPING[$code] ?? 500);
$response->getSwooleResponse()->trailer('grpc-status', (string) $code);
$response->getSwooleResponse()->trailer('grpc-message', (string) $message);

View File

@ -0,0 +1,98 @@
<?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\GrpcServer;
use Hyperf\Contract\StdoutLoggerInterface;
use Hyperf\ExceptionHandler\Formatter\FormatterInterface;
use Hyperf\Grpc\StatusCode;
use Hyperf\HttpMessage\Server\Response;
use HyperfTest\GrpcServer\Stub\GrpcExceptionHandlerStub;
use Mockery;
use PHPUnit\Framework\TestCase;
use Psr\Container\ContainerInterface;
/**
* @internal
* @coversNothing
*/
class GrpcExceptionHandlerTest extends TestCase
{
public function testTransferToResponse200()
{
$container = $this->getContainer();
$logger = $container->get(StdoutLoggerInterface::class);
$formatter = $container->get(FormatterInterface::class);
$swooleResponse = Mockery::mock(\Swoole\Http\Response::class);
$data = [];
$swooleResponse->shouldReceive('trailer')->andReturnUsing(function (...$args) use (&$data) {
$data[] = $args;
});
$response = new Response($swooleResponse);
$handler = new GrpcExceptionHandlerStub($logger, $formatter);
$response = $handler->transferToResponse(StatusCode::OK, 'OK', $response);
$this->assertSame([['grpc-status', '0'], ['grpc-message', 'OK']], $data);
$this->assertSame(200, $response->getStatusCode());
}
public function testTransferToResponse499()
{
$container = $this->getContainer();
$logger = $container->get(StdoutLoggerInterface::class);
$formatter = $container->get(FormatterInterface::class);
$swooleResponse = Mockery::mock(\Swoole\Http\Response::class);
$data = [];
$swooleResponse->shouldReceive('trailer')->andReturnUsing(function (...$args) use (&$data) {
$data[] = $args;
});
$response = new Response($swooleResponse);
$handler = new GrpcExceptionHandlerStub($logger, $formatter);
$response = $handler->transferToResponse(StatusCode::CANCELLED, 'The operation was cancelled', $response);
$this->assertSame([['grpc-status', '1'], ['grpc-message', 'The operation was cancelled']], $data);
$this->assertSame(499, $response->getStatusCode());
}
public function testTransferToResponseUnKnown()
{
$container = $this->getContainer();
$logger = $container->get(StdoutLoggerInterface::class);
$formatter = $container->get(FormatterInterface::class);
$swooleResponse = Mockery::mock(\Swoole\Http\Response::class);
$data = [];
$swooleResponse->shouldReceive('trailer')->andReturnUsing(function (...$args) use (&$data) {
$data[] = $args;
});
$response = new Response($swooleResponse);
$handler = new GrpcExceptionHandlerStub($logger, $formatter);
$response = $handler->transferToResponse(123, 'UNKNOWN', $response);
$this->assertSame([['grpc-status', '123'], ['grpc-message', 'UNKNOWN']], $data);
$this->assertSame(500, $response->getStatusCode());
}
protected function getContainer()
{
$container = Mockery::mock(ContainerInterface::class);
$logger = Mockery::mock(StdoutLoggerInterface::class);
$logger->shouldReceive(Mockery::any())->with(Mockery::any())->andReturn(null);
$container->shouldReceive('get')->with(StdoutLoggerInterface::class)->andReturn($logger);
$formatter = Mockery::mock(FormatterInterface::class);
$formatter->shouldReceive(Mockery::any())->with(Mockery::any())->andReturn('');
$container->shouldReceive('get')->with(FormatterInterface::class)->andReturn($formatter);
return $container;
}
}

View File

@ -0,0 +1,24 @@
<?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\GrpcServer\Stub;
use Hyperf\GrpcServer\Exception\Handler\GrpcExceptionHandler;
use Psr\Http\Message\ResponseInterface;
class GrpcExceptionHandlerStub extends GrpcExceptionHandler
{
public function transferToResponse($code, $message, ResponseInterface $response): ResponseInterface
{
return parent::transferToResponse($code, $message, $response);
}
}

186
src/grpc/src/StatusCode.php Normal file
View File

@ -0,0 +1,186 @@
<?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 Hyperf\Grpc;
/**
* the const inside this class is copied from this address.
* @see https://github.com/grpc/grpc-java/blob/b363f80764bcc8452ef54511b6c6d6b596f5f177/api/src/main/java/io/grpc/Status.java
* @see https://grpc.github.io/grpc/core/md_doc_statuscodes.html
*/
class StatusCode
{
/**
* The operation completed successfully.
*/
const OK = 0;
/**
* The operation was cancelled (typically by the caller).
*/
const CANCELLED = 1;
/**
* Unknown error. An example of where this error may be returned is
* if a Status value received from another address space belongs to
* an error-space that is not known in this address space. Also
* errors raised by APIs that do not return enough error information
* may be converted to this error.
*/
const UNKNOWN = 2;
/**
* Client specified an invalid argument. Note that this differs
* from FAILED_PRECONDITION. INVALID_ARGUMENT indicates arguments
* that are problematic regardless of the state of the system
* (e.g., a malformed file name).
*/
const INVALID_ARGUMENT = 3;
/**
* Deadline expired before operation could complete. For operations
* that change the state of the system, this error may be returned
* even if the operation has completed successfully. For example, a
* successful response from a server could have been delayed long
* enough for the deadline to expire.
*/
const DEADLINE_EXCEEDED = 4;
/**
* Some requested entity (e.g., file or directory) was not found.
*/
const NOT_FOUND = 5;
/**
* Some entity that we attempted to create (e.g., file or directory) already exists.
*/
const ALREADY_EXISTS = 6;
/**
* The caller does not have permission to execute the specified
* operation. PERMISSION_DENIED must not be used for rejections
* caused by exhausting some resource (use RESOURCE_EXHAUSTED
* instead for those errors). PERMISSION_DENIED must not be
* used if the caller cannot be identified (use UNAUTHENTICATED
* instead for those errors).
*/
const PERMISSION_DENIED = 7;
/**
* Some resource has been exhausted, perhaps a per-user quota, or
* perhaps the entire file system is out of space.
*/
const RESOURCE_EXHAUSTED = 8;
/**
* Operation was rejected because the system is not in a state
* required for the operation's execution. For example, directory
* to be deleted may be non-empty, an rmdir operation is applied to
* a non-directory, etc.
*
* <p>A litmus test that may help a service implementor in deciding
* between FAILED_PRECONDITION, ABORTED, and UNAVAILABLE:
* (a) Use UNAVAILABLE if the client can retry just the failing call.
* (b) Use ABORTED if the client should retry at a higher-level
* (e.g., restarting a read-modify-write sequence).
* (c) Use FAILED_PRECONDITION if the client should not retry until
* the system state has been explicitly fixed. E.g., if an "rmdir"
* fails because the directory is non-empty, FAILED_PRECONDITION
* should be returned since the client should not retry unless
* they have first fixed up the directory by deleting files from it.
*/
const FAILED_PRECONDITION = 9;
/**
* The operation was aborted, typically due to a concurrency issue
* like sequencer check failures, transaction aborts, etc.
*
* <p>See litmus test above for deciding between FAILED_PRECONDITION,
* ABORTED, and UNAVAILABLE.
*/
const ABORTED = 10;
/**
* Operation was attempted past the valid range. E.g., seeking or
* reading past end of file.
*
* <p>Unlike INVALID_ARGUMENT, this error indicates a problem that may
* be fixed if the system state changes. For example, a 32-bit file
* system will generate INVALID_ARGUMENT if asked to read at an
* offset that is not in the range [0,2^32-1], but it will generate
* OUT_OF_RANGE if asked to read from an offset past the current
* file size.
*
* <p>There is a fair bit of overlap between FAILED_PRECONDITION and OUT_OF_RANGE.
* We recommend using OUT_OF_RANGE (the more specific error) when it applies
* so that callers who are iterating through
* a space can easily look for an OUT_OF_RANGE error to detect when they are done.
*/
const OUT_OF_RANGE = 11;
/**
* Operation is not implemented or not supported/enabled in this service.
*/
const UNIMPLEMENTED = 12;
/**
* Internal errors. Means some invariants expected by underlying
* system has been broken. If you see one of these errors,
* something is very broken.
*/
const INTERNAL = 13;
/**
* The service is currently unavailable. This is a most likely a
* transient condition and may be corrected by retrying with
* a backoff. Note that it is not always safe to retry
* non-idempotent operations.
*
* <p>See litmus test above for deciding between FAILED_PRECONDITION,
* ABORTED, and UNAVAILABLE.
*/
const UNAVAILABLE = 14;
/**
* Unrecoverable data loss or corruption.
*/
const DATA_LOSS = 15;
/**
* The request does not have valid authentication credentials for the
* operation.
*/
const UNAUTHENTICATED = 16;
/**
* @see https://grpc.github.io/grpc/core/md_doc_statuscodes.html
*/
const HTTP_CODE_MAPPING = [
self::OK => 200,
self::CANCELLED => 499,
self::UNKNOWN => 500,
self::INVALID_ARGUMENT => 400,
self::DEADLINE_EXCEEDED => 504,
self::NOT_FOUND => 404,
self::ALREADY_EXISTS => 409,
self::PERMISSION_DENIED => 403,
self::RESOURCE_EXHAUSTED => 429,
self::FAILED_PRECONDITION => 400,
self::ABORTED => 409,
self::OUT_OF_RANGE => 400,
self::UNIMPLEMENTED => 501,
self::INTERNAL => 500,
self::UNAVAILABLE => 503,
self::DATA_LOSS => 500,
self::UNAUTHENTICATED => 401,
];
}

View File

@ -67,12 +67,7 @@ class CoroutineHandler
$ex = $this->checkStatusCode($client, $request);
if ($ex !== true) {
return [
'status' => null,
'reason' => null,
'headers' => [],
'error' => $ex,
];
return $this->getErrorResponse($ex, $btime, $effectiveUrl);
}
return $this->getResponse($client, $btime, $effectiveUrl);
@ -116,6 +111,24 @@ class CoroutineHandler
$client->setHeaders($headers);
}
protected function getErrorResponse(\Throwable $throwable, $btime, $effectiveUrl)
{
return new CompletedFutureArray([
'curl' => [
'errno' => 0,
],
'transfer_stats' => [
'total_time' => microtime(true) - $btime,
],
'effective_url' => $effectiveUrl,
'body' => '',
'status' => null,
'reason' => null,
'headers' => [],
'error' => $throwable,
]);
}
protected function getResponse(Client $client, $btime, $effectiveUrl)
{
return new CompletedFutureArray([

View File

@ -74,12 +74,7 @@ class PoolHandler extends CoroutineHandler
if ($ex !== true) {
$connection->close();
$connection->release();
return [
'status' => null,
'reason' => null,
'headers' => [],
'error' => $ex,
];
return $this->getErrorResponse($ex, $btime, $effectiveUrl);
}
$response = $this->getResponse($client, $btime, $effectiveUrl);

View File

@ -61,6 +61,11 @@ class UploadedFile extends \SplFileInfo implements UploadedFileInterface
*/
private $size;
/**
* @var string
*/
private $mimeType;
/**
* @param string $tmpFile
* @param null|int $size
@ -98,6 +103,14 @@ class UploadedFile extends \SplFileInfo implements UploadedFileInterface
return end($segments) ?? null;
}
public function getMimeType(): string
{
if (is_string($this->mimeType)) {
return $this->mimeType;
}
return $this->mimeType = mime_content_type($this->tmpFile);
}
/**
* Returns whether the file was uploaded successfully.
*

View File

@ -51,6 +51,6 @@ class HttpExceptionHandler extends ExceptionHandler
*/
public function isValid(Throwable $throwable): bool
{
return $throwable instanceof ServerException;
return true;
}
}

View File

@ -118,6 +118,9 @@ class DispatcherFactory
foreach ($methods as $method) {
$path = $this->parsePath($prefix, $method);
$methodName = $method->getName();
if (substr($methodName, 0, 2) === '__') {
continue;
}
$router->addRoute($autoMethods, $path, [$className, $methodName, $annotation->server]);
$methodMiddlewares = $middlewares;
@ -177,7 +180,9 @@ class DispatcherFactory
}
$path = $mapping->path;
if ($path[0] !== '/') {
if ($path === '') {
$path = $prefix;
} elseif ($path[0] !== '/') {
$path = $prefix . '/' . $path;
}
$router->addRoute($mapping->methods, $path, [

View File

@ -12,6 +12,8 @@ declare(strict_types=1);
namespace HyperfTest\HttpServer\Router;
use Hyperf\HttpServer\Annotation\AutoController;
use HyperfTest\HttpServer\Stub\DemoController;
use HyperfTest\HttpServer\Stub\DispatcherFactory;
use PHPUnit\Framework\TestCase;
@ -31,4 +33,21 @@ class DispatcherFactoryTest extends TestCase
$res = $factory->getPrefix('App\\Controller\\Admin\\UserAuthController', '');
$this->assertSame('/admin/user_auth', $res);
}
public function testRemoveMagicMethods()
{
$factory = new DispatcherFactory();
$annotation = new AutoController(['prefix' => 'test']);
$factory->handleAutoController(DemoController::class, $annotation);
$router = $factory->getRouter('http');
[$routers] = $router->getData();
$this->assertSame(['GET', 'POST', 'HEAD'], array_keys($routers));
foreach ($routers as $method => $items) {
$this->assertFalse(in_array('/test/__construct', array_keys($items)));
$this->assertFalse(in_array('/test/__return', array_keys($items)));
}
}
}

View File

@ -14,8 +14,17 @@ namespace HyperfTest\HttpServer\Stub;
class DemoController
{
public function __construct()
{
}
public function index(int $id, string $name = 'Hyperf', array $params = [])
{
return [$id, $name, $params];
return $this->__return($id, $name, $params);
}
public function __return(...$args)
{
return $args;
}
}

View File

@ -12,10 +12,23 @@ declare(strict_types=1);
namespace HyperfTest\HttpServer\Stub;
use Hyperf\HttpServer\Annotation\AutoController;
use Hyperf\HttpServer\Router\RouteCollector;
class DispatcherFactory extends \Hyperf\HttpServer\Router\DispatcherFactory
{
public function getPrefix(string $className, string $prefix): string
{
return parent::getPrefix($className, $prefix); // TODO: Change the autogenerated stub
return parent::getPrefix($className, $prefix);
}
public function handleAutoController(string $className, AutoController $annotation, array $middlewares = [], array $methodMetadata = []): void
{
parent::handleAutoController($className, $annotation, $middlewares, $methodMetadata);
}
public function getRouter(string $serverName): RouteCollector
{
return parent::getRouter($serverName);
}
}

View File

@ -61,6 +61,7 @@ class Paginator extends AbstractPaginator implements Arrayable, ArrayAccess, Cou
if ($this->hasMorePages()) {
return $this->url($this->currentPage() + 1);
}
return null;
}
/**

View File

@ -81,7 +81,7 @@ class RedisConnection extends BaseConnection implements ConnectionInterface
throw new ConnectionException('Connection reconnect failed.');
}
if (isset($auth)) {
if (isset($auth) && $auth !== '') {
$redis->auth($auth);
}

1
src/server/.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
/tests export-ignore

View File

@ -12,14 +12,35 @@ declare(strict_types=1);
namespace Hyperf\Server\Listener;
use Hyperf\Contract\ConfigInterface;
use Hyperf\Event\Contract\ListenerInterface;
use Hyperf\Framework\Event\AfterWorkerStart;
use Hyperf\Framework\Event\OnManagerStart;
use Hyperf\Framework\Event\OnStart;
use Hyperf\Process\Event\BeforeProcessHandle;
use Psr\Container\ContainerInterface;
class InitProcessTitleListener implements ListenerInterface
{
/**
* @var string
*/
protected $name = '';
/**
* @var string
*/
protected $dot = '.';
public function __construct(ContainerInterface $container)
{
if ($container->has(ConfigInterface::class)) {
if ($name = $container->get(ConfigInterface::class)->get('app_name')) {
$this->name = $name;
}
}
}
public function listen(): array
{
return [
@ -32,18 +53,35 @@ class InitProcessTitleListener implements ListenerInterface
public function process(object $event)
{
$array = [];
if ($this->name !== '') {
$array[] = $this->name;
}
if ($event instanceof OnStart) {
@cli_set_process_title('Master');
$array[] = 'Master';
} elseif ($event instanceof OnManagerStart) {
@cli_set_process_title('Manager');
$array[] = 'Manager';
} elseif ($event instanceof AfterWorkerStart) {
if ($event->server->taskworker) {
@cli_set_process_title('TaskWorker.' . $event->workerId);
$array[] = 'TaskWorker';
$array[] = $event->workerId;
} else {
@cli_set_process_title('Worker.' . $event->workerId);
$array[] = 'Worker';
$array[] = $event->workerId;
}
} elseif ($event instanceof BeforeProcessHandle) {
@cli_set_process_title($event->process->name . '.' . $event->index);
$array[] = $event->process->name;
$array[] = $event->index;
}
if ($title = implode($this->dot, $array)) {
$this->setTitle($title);
}
}
protected function setTitle(string $title)
{
@cli_set_process_title($title);
}
}

View File

@ -12,12 +12,21 @@ declare(strict_types=1);
namespace HyperfTest\Server\Listener;
use Hyperf\Config\Config;
use Hyperf\Contract\ConfigInterface;
use Hyperf\Framework\Event\AfterWorkerStart;
use Hyperf\Framework\Event\OnManagerStart;
use Hyperf\Framework\Event\OnStart;
use Hyperf\Process\Event\BeforeProcessHandle;
use Hyperf\Server\Listener\InitProcessTitleListener;
use Hyperf\Utils\Context;
use HyperfTest\Server\Stub\DemoProcess;
use HyperfTest\Server\Stub\InitProcessTitleListenerStub;
use HyperfTest\Server\Stub\InitProcessTitleListenerStub2;
use Mockery;
use PHPUnit\Framework\TestCase;
use Psr\Container\ContainerInterface;
use Psr\EventDispatcher\EventDispatcherInterface;
/**
* @internal
@ -25,9 +34,17 @@ use PHPUnit\Framework\TestCase;
*/
class InitProcessTitleListenerTest extends TestCase
{
protected function tearDown()
{
Mockery::close();
}
public function testInitProcessTitleListenerListen()
{
$listener = new InitProcessTitleListener();
$container = Mockery::mock(ContainerInterface::class);
$container->shouldReceive('has')->with(Mockery::any())->andReturn(false);
$listener = new InitProcessTitleListener($container);
$this->assertSame([
OnStart::class,
@ -36,4 +53,53 @@ class InitProcessTitleListenerTest extends TestCase
BeforeProcessHandle::class,
], $listener->listen());
}
public function testProcessDefaultName()
{
$container = Mockery::mock(ContainerInterface::class);
$container->shouldReceive('has')->with(Mockery::any())->andReturn(false);
$listener = new InitProcessTitleListenerStub($container);
$process = new DemoProcess($container);
$listener->process(new BeforeProcessHandle($process, 1));
$this->assertSame('test.demo.1', Context::get('test.server.process.title'));
}
public function testProcessName()
{
$name = 'hyperf-skeleton.' . uniqid();
$container = Mockery::mock(ContainerInterface::class);
$container->shouldReceive('has')->with(ConfigInterface::class)->andReturn(true);
$container->shouldReceive('has')->with(EventDispatcherInterface::class)->andReturn(false);
$container->shouldReceive('get')->with(ConfigInterface::class)->andReturn(new Config([
'app_name' => $name,
]));
$listener = new InitProcessTitleListenerStub($container);
$process = new DemoProcess($container);
$listener->process(new BeforeProcessHandle($process, 0));
$this->assertSame($name . '.test.demo.0', Context::get('test.server.process.title'));
}
public function testUserDefinedDot()
{
$name = 'hyperf-skeleton.' . uniqid();
$container = Mockery::mock(ContainerInterface::class);
$container->shouldReceive('has')->with(ConfigInterface::class)->andReturn(true);
$container->shouldReceive('has')->with(EventDispatcherInterface::class)->andReturn(false);
$container->shouldReceive('get')->with(ConfigInterface::class)->andReturn(new Config([
'app_name' => $name,
]));
$listener = new InitProcessTitleListenerStub2($container);
$process = new DemoProcess($container);
$listener->process(new BeforeProcessHandle($process, 0));
$this->assertSame($name . '#test.demo#0', Context::get('test.server.process.title'));
}
}

View File

@ -10,14 +10,15 @@ declare(strict_types=1);
* @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Translation\Contracts;
namespace HyperfTest\Server\Stub;
interface HasLocalePreference
use Hyperf\Process\AbstractProcess;
class DemoProcess extends AbstractProcess
{
/**
* Get the preferred locale of the entity.
*
* @return null|string
*/
public function preferredLocale();
public $name = 'test.demo';
public function handle(): void
{
}
}

View File

@ -0,0 +1,24 @@
<?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\Server\Stub;
use Hyperf\Server\Listener\InitProcessTitleListener;
use Hyperf\Utils\Context;
class InitProcessTitleListenerStub extends InitProcessTitleListener
{
public function setTitle(string $title)
{
Context::set('test.server.process.title', $title);
}
}

View File

@ -0,0 +1,26 @@
<?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\Server\Stub;
use Hyperf\Server\Listener\InitProcessTitleListener;
use Hyperf\Utils\Context;
class InitProcessTitleListenerStub2 extends InitProcessTitleListener
{
protected $dot = '#';
public function setTitle(string $title)
{
Context::set('test.server.process.title', $title);
}
}

View File

@ -18,8 +18,9 @@
"phpunit/phpunit": "^7.0"
},
"suggest": {
"psr/container": "Required to use SnowflakeFactory.",
"hyperf/config": "Required to read snowflake config."
"psr/container": "Required to use MetaGeneratorFactory.",
"hyperf/config": "Required to read snowflake config.",
"hyperf/redis": "Required to use RedisMilliSecondMetaGenerator or RedisSecondMetaGenerator."
},
"autoload": {
"psr-4": {

View File

@ -10,9 +10,16 @@ declare(strict_types=1);
* @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
*/
use Hyperf\Snowflake\IdGeneratorInterface;
use Hyperf\Snowflake\MetaGenerator\RedisMilliSecondMetaGenerator;
use Hyperf\Snowflake\MetaGenerator\RedisSecondMetaGenerator;
use Hyperf\Snowflake\MetaGeneratorInterface;
return [
'level' => IdGeneratorInterface::LEVEL_MILLISECOND,
'begin_second' => IdGeneratorInterface::DEFAULT_SECOND,
'begin_second' => MetaGeneratorInterface::DEFAULT_BEGIN_SECOND,
RedisMilliSecondMetaGenerator::class => [
'pool' => 'default',
],
RedisSecondMetaGenerator::class => [
'pool' => 'default',
],
];

View File

@ -0,0 +1,74 @@
<?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 Hyperf\Snowflake;
class Config implements ConfigInterface
{
const MILLISECOND_BITS = 41;
const DATA_CENTER_ID_BITS = 5;
const WORKER_ID_BITS = 5;
const SEQUENCE_BITS = 12;
public function maxWorkerId(): int
{
return -1 ^ (-1 << self::WORKER_ID_BITS);
}
public function maxDataCenterId(): int
{
return -1 ^ (-1 << self::DATA_CENTER_ID_BITS);
}
public function maxSequence(): int
{
return -1 ^ (-1 << self::SEQUENCE_BITS);
}
public function getTimeStampShift(): int
{
return self::SEQUENCE_BITS + self::WORKER_ID_BITS + self::DATA_CENTER_ID_BITS;
}
public function getDataCenterShift(): int
{
return self::SEQUENCE_BITS + self::WORKER_ID_BITS;
}
public function getWorkerIdShift(): int
{
return self::SEQUENCE_BITS;
}
public function getTimeStampBits(): int
{
return self::MILLISECOND_BITS;
}
public function getDataCenterBits(): int
{
return self::DATA_CENTER_ID_BITS;
}
public function getWorkerBits(): int
{
return self::WORKER_ID_BITS;
}
public function getSequenceBits(): int
{
return self::SEQUENCE_BITS;
}
}

View File

@ -0,0 +1,36 @@
<?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 Hyperf\Snowflake;
interface ConfigInterface
{
public function maxWorkerId(): int;
public function maxDataCenterId(): int;
public function maxSequence(): int;
public function getTimeStampShift(): int;
public function getDataCenterShift(): int;
public function getWorkerIdShift(): int;
public function getTimeStampBits(): int;
public function getDataCenterBits(): int;
public function getWorkerBits(): int;
public function getSequenceBits(): int;
}

View File

@ -12,14 +12,17 @@ declare(strict_types=1);
namespace Hyperf\Snowflake;
use Hyperf\Snowflake\IdGenerator\SnowflakeIdGenerator;
class ConfigProvider
{
public function __invoke(): array
{
return [
'dependencies' => [
IdGeneratorInterface::class => SnowflakeFactory::class,
MetaGeneratorInterface::class => RandomMetaGenerator::class,
IdGeneratorInterface::class => SnowflakeIdGenerator::class,
MetaGeneratorInterface::class => MetaGeneratorFactory::class,
ConfigInterface::class => Config::class,
],
'commands' => [
],

View File

@ -0,0 +1,75 @@
<?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 Hyperf\Snowflake;
abstract class IdGenerator implements IdGeneratorInterface
{
/**
* @var MetaGeneratorInterface
*/
protected $metaGenerator;
/**
* @var ConfigInterface
*/
protected $config;
public function __construct(MetaGeneratorInterface $metaGenerator)
{
$this->metaGenerator = $metaGenerator;
$this->config = $metaGenerator->getConfig();
}
public function generate(?Meta $meta = null): int
{
$meta = $this->meta($meta);
$interval = $meta->getTimeInterval() << $this->config->getTimeStampShift();
$dataCenterId = $meta->getDataCenterId() << $this->config->getDataCenterShift();
$workerId = $meta->getWorkerId() << $this->config->getWorkerIdShift();
return $interval | $dataCenterId | $workerId | $meta->getSequence();
}
public function degenerate(int $id): Meta
{
$interval = $id >> $this->config->getTimeStampShift();
$dataCenterId = $id >> $this->config->getDataCenterShift();
$workerId = $id >> $this->config->getWorkerIdShift();
return new Meta(
$interval << $this->config->getDataCenterBits() ^ $dataCenterId,
$dataCenterId << $this->config->getWorkerBits() ^ $workerId,
$workerId << $this->config->getSequenceBits() ^ $id,
$interval + $this->metaGenerator->getBeginTimeStamp(),
$this->metaGenerator->getBeginTimeStamp()
);
}
/**
* @return MetaGeneratorInterface
*/
public function getMetaGenerator(): MetaGeneratorInterface
{
return $this->metaGenerator;
}
protected function meta(?Meta $meta = null): Meta
{
if (is_null($meta)) {
return $this->metaGenerator->generate();
}
return $meta;
}
}

View File

@ -10,4 +10,10 @@ declare(strict_types=1);
* @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
*/
require_once dirname(dirname(__FILE__)) . '/vendor/autoload.php';
namespace Hyperf\Snowflake\IdGenerator;
use Hyperf\Snowflake\IdGenerator;
class SnowflakeIdGenerator extends IdGenerator
{
}

View File

@ -14,12 +14,6 @@ namespace Hyperf\Snowflake;
interface IdGeneratorInterface extends \Hyperf\Contract\IdGeneratorInterface
{
const LEVEL_SECOND = 1;
const LEVEL_MILLISECOND = 2;
const DEFAULT_SECOND = 1565712000;
public function generate(?Meta $meta = null): int;
public function degenerate(int $id): Meta;

View File

@ -12,89 +12,122 @@ declare(strict_types=1);
namespace Hyperf\Snowflake;
use Hyperf\Snowflake\Exception\SnowflakeException;
class Meta
{
const MILLISECOND_BITS = 41;
const DATA_CENTER_ID_BITS = 5;
const MACHINE_ID_BITS = 5;
const SEQUENCE_BITS = 12;
const MILLISECOND_BITS = 39;
const BUSINESS_ID_BITS = 4;
const DATA_CENTER_ID_BITS = 2;
const MACHINE_ID_BITS = 7;
/**
* @var int [0, 31]
*/
protected $dataCenterId;
/**
* @var int [0, 15]
* @var int [0, 31]
*/
public $businessId;
/**
* @var int [0, 3]
*/
public $dataCenterId;
/**
* @var int [0, 127]
*/
public $machineId;
protected $workerId;
/**
* @var int [0, 4095]
*/
public $sequence;
protected $sequence;
/**
* @var int seconds or millisecond
* @var int seconds or milliseconds
*/
public $timestamp = 0;
protected $timestamp = 0;
public function __construct(int $businessId, int $dataCenterId, int $machineId, int $sequence)
/**
* @var int seconds or milliseconds
*/
protected $beginTimeStamp = 0;
public function __construct(int $dataCenterId, int $workerId, int $sequence, int $timestamp, int $beginTimeStamp = 1560960000)
{
if ($businessId < 0 || $businessId > $this->maxBusinessId()) {
throw new SnowflakeException('Business Id can\'t be greater than 15 or less than 0');
}
if ($dataCenterId < 0 || $dataCenterId > $this->maxDataCenterId()) {
throw new SnowflakeException('DataCenter Id can\'t be greater than 4 or less than 0');
}
if ($machineId < 0 || $machineId > $this->maxMachineId()) {
throw new SnowflakeException('Machine Id can\'t be greater than 128 or less than 0');
}
if ($sequence < 0 || $sequence > $this->maxSequence()) {
throw new SnowflakeException('Sequence can\'t be greater than 4096 or less than 0');
}
$this->businessId = $businessId;
$this->dataCenterId = $dataCenterId;
$this->machineId = $machineId;
$this->workerId = $workerId;
$this->sequence = $sequence;
$this->timestamp = $timestamp;
$this->beginTimeStamp = $beginTimeStamp;
}
public function setTimestamp(int $timestamp): self
public function getTimeInterval(): int
{
$this->timestamp = $timestamp;
return $this->timestamp - $this->beginTimeStamp;
}
/**
* @return int
*/
public function getDataCenterId(): int
{
return $this->dataCenterId;
}
/**
* @return int
*/
public function getWorkerId(): int
{
return $this->workerId;
}
/**
* @return int
*/
public function getSequence(): int
{
return $this->sequence;
}
/**
* @param int $dataCenterId
* @return Meta
*/
public function setDataCenterId(int $dataCenterId): self
{
$this->dataCenterId = $dataCenterId;
return $this;
}
protected function maxMachineId(): int
/**
* @param int $workerId
* @return Meta
*/
public function setWorkerId(int $workerId): self
{
return -1 ^ (-1 << self::MACHINE_ID_BITS);
$this->workerId = $workerId;
return $this;
}
protected function maxDataCenterId(): int
/**
* @param int $sequence
* @return Meta
*/
public function setSequence(int $sequence): self
{
return -1 ^ (-1 << self::DATA_CENTER_ID_BITS);
$this->sequence = $sequence;
return $this;
}
protected function maxBusinessId(): int
/**
* @return int
*/
public function getTimestamp(): int
{
return -1 ^ (-1 << self::BUSINESS_ID_BITS);
return $this->timestamp;
}
protected function maxSequence(): int
/**
* @return int
*/
public function getBeginTimeStamp(): int
{
return -1 ^ (-1 << self::SEQUENCE_BITS);
return $this->beginTimeStamp;
}
}

View File

@ -0,0 +1,85 @@
<?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 Hyperf\Snowflake;
use Hyperf\Snowflake\Exception\SnowflakeException;
abstract class MetaGenerator implements MetaGeneratorInterface
{
/**
* @var ConfigInterface
*/
protected $config;
protected $sequence = 0;
protected $lastTimeStamp = 0;
protected $beginTimeStamp = 0;
public function __construct(ConfigInterface $config, int $beginTimeStamp)
{
$this->config = $config;
$this->lastTimeStamp = $this->getTimeStamp();
$this->beginTimeStamp = $beginTimeStamp;
}
public function generate(): Meta
{
$timestamp = $this->getTimeStamp();
if ($timestamp < $this->lastTimeStamp) {
$this->clockMovedBackwards($timestamp, $this->lastTimeStamp);
}
if ($timestamp == $this->lastTimeStamp) {
$this->sequence = ($this->sequence + 1) % $this->config->maxSequence();
if ($this->sequence == 0) {
$timestamp = $this->getNextTimeStamp();
}
} else {
$this->sequence = 0;
}
if ($timestamp < $this->beginTimeStamp) {
throw new SnowflakeException(sprintf('The beginTimeStamp %d is invalid, because it smaller than timestamp %d.', $this->beginTimeStamp, $timestamp));
}
$this->lastTimeStamp = $timestamp;
return new Meta($this->getDataCenterId(), $this->getWorkerId(), $this->sequence, $timestamp, $this->beginTimeStamp);
}
public function getBeginTimeStamp(): int
{
return $this->beginTimeStamp;
}
public function getConfig(): ConfigInterface
{
return $this->config;
}
abstract public function getDataCenterId(): int;
abstract public function getWorkerId(): int;
abstract public function getTimeStamp(): int;
abstract public function getNextTimeStamp(): int;
protected function clockMovedBackwards($timestamp, $lastTimeStamp)
{
throw new SnowflakeException(sprintf('Clock moved backwards. Refusing to generate id for %d milliseconds.', $lastTimeStamp - $timestamp));
}
}

View File

@ -0,0 +1,49 @@
<?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 Hyperf\Snowflake\MetaGenerator;
use Hyperf\Snowflake\ConfigInterface;
use Hyperf\Snowflake\MetaGenerator;
class RandomMilliSecondMetaGenerator extends MetaGenerator
{
public function __construct(ConfigInterface $config, int $beginTimeStamp)
{
parent::__construct($config, $beginTimeStamp * 1000);
}
public function getDataCenterId(): int
{
return rand(0, 31);
}
public function getWorkerId(): int
{
return rand(0, 31);
}
public function getTimeStamp(): int
{
return intval(microtime(true) * 1000);
}
public function getNextTimeStamp(): int
{
$timestamp = $this->getTimeStamp();
while ($timestamp <= $this->lastTimeStamp) {
$timestamp = $this->getTimeStamp();
}
return $timestamp;
}
}

View File

@ -0,0 +1,69 @@
<?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 Hyperf\Snowflake\MetaGenerator;
use Hyperf\Contract\ConfigInterface as HyperfConfig;
use Hyperf\Redis\RedisProxy;
use Hyperf\Snowflake\ConfigInterface;
use Hyperf\Snowflake\MetaGenerator;
class RedisMilliSecondMetaGenerator extends MetaGenerator
{
const REDIS_KEY = 'hyperf:snowflake:worker';
protected $workerId;
protected $dataCenterId;
public function __construct(HyperfConfig $hConfig, ConfigInterface $config, int $beginTimeStamp = self::DEFAULT_BEGIN_SECOND)
{
parent::__construct($config, $beginTimeStamp * 1000);
$pool = $hConfig->get('snowflake.' . static::class . '.pool', 'default');
/** @var \Redis $redis */
$redis = make(RedisProxy::class, [
'pool' => $pool,
]);
$id = $redis->incr(static::REDIS_KEY);
$this->workerId = $id % $config->maxWorkerId();
$this->dataCenterId = intval($id / $config->maxWorkerId()) % $config->maxDataCenterId();
}
public function getDataCenterId(): int
{
return $this->dataCenterId;
}
public function getWorkerId(): int
{
return $this->workerId;
}
public function getTimeStamp(): int
{
return intval(microtime(true) * 1000);
}
public function getNextTimeStamp(): int
{
$timestamp = $this->getTimeStamp();
while ($timestamp <= $this->lastTimeStamp) {
$timestamp = $this->getTimeStamp();
}
return $timestamp;
}
}

View File

@ -0,0 +1,68 @@
<?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 Hyperf\Snowflake\MetaGenerator;
use Hyperf\Contract\ConfigInterface as HyperfConfig;
use Hyperf\Redis\RedisProxy;
use Hyperf\Snowflake\ConfigInterface;
use Hyperf\Snowflake\MetaGenerator;
class RedisSecondMetaGenerator extends MetaGenerator
{
const REDIS_KEY = 'hyperf:snowflake:worker';
protected $workerId;
protected $dataCenterId;
public function __construct(HyperfConfig $hConfig, ConfigInterface $config, int $beginTimeStamp = self::DEFAULT_BEGIN_SECOND)
{
parent::__construct($config, $beginTimeStamp);
$pool = $hConfig->get('snowflake.' . static::class . '.pool', 'default');
/** @var \Redis $redis */
$redis = make(RedisProxy::class, [
'pool' => $pool,
]);
$id = $redis->incr(static::REDIS_KEY);
$this->workerId = $id % $config->maxWorkerId();
$this->dataCenterId = intval($id / $config->maxWorkerId()) % $config->maxDataCenterId();
}
public function getDataCenterId(): int
{
return $this->dataCenterId;
}
public function getWorkerId(): int
{
return $this->workerId;
}
public function getTimeStamp(): int
{
return time();
}
public function getNextTimeStamp(): int
{
return $this->lastTimeStamp + 1;
}
protected function clockMovedBackwards($timestamp, $lastTimeStamp)
{
}
}

View File

@ -13,19 +13,21 @@ declare(strict_types=1);
namespace Hyperf\Snowflake;
use Hyperf\Contract\ConfigInterface;
use Hyperf\Snowflake\ConfigInterface as SnowflakeConfigInterface;
use Hyperf\Snowflake\MetaGenerator\RedisMilliSecondMetaGenerator;
use Psr\Container\ContainerInterface;
class SnowflakeFactory
class MetaGeneratorFactory
{
public function __invoke(ContainerInterface $container)
{
$config = $container->get(ConfigInterface::class);
$level = $config->get('snowflake.level', IdGeneratorInterface::LEVEL_MILLISECOND);
$beginSecond = $config->get('snowflake.begin_second', IdGeneratorInterface::DEFAULT_SECOND);
$beginSecond = $config->get('snowflake.begin_second', MetaGeneratorInterface::DEFAULT_BEGIN_SECOND);
return make(Snowflake::class, [
'level' => $level,
'beginSecond' => $beginSecond,
return make(RedisMilliSecondMetaGenerator::class, [
$config,
$container->get(SnowflakeConfigInterface::class),
$beginSecond,
]);
}
}

View File

@ -14,5 +14,11 @@ namespace Hyperf\Snowflake;
interface MetaGeneratorInterface
{
const DEFAULT_BEGIN_SECOND = 1560960000;
public function generate(): Meta;
public function getBeginTimeStamp(): int;
public function getConfig(): ConfigInterface;
}

View File

@ -1,28 +0,0 @@
<?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 Hyperf\Snowflake;
class RandomMetaGenerator implements MetaGeneratorInterface
{
protected $sequence = 0;
public function generate(): Meta
{
$businessId = rand(0, 15);
$dataCenterId = rand(0, 3);
$machineId = rand(0, 127);
$sequence = ($this->sequence++) % 4096;
return new Meta($businessId, $dataCenterId, $machineId, $sequence);
}
}

View File

@ -1,104 +0,0 @@
<?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 Hyperf\Snowflake;
class Snowflake implements IdGeneratorInterface
{
/**
* @var MetaGeneratorInterface
*/
protected $metaGenerator;
/**
* @var int
*/
protected $level;
/**
* @var int
*/
protected $beginSecond;
public function __construct(MetaGeneratorInterface $metaGenerator, int $level = self::LEVEL_MILLISECOND, int $beginSecond = self::DEFAULT_SECOND)
{
$this->metaGenerator = $metaGenerator;
$this->level = $level;
$this->beginSecond = $level == self::LEVEL_SECOND ? $beginSecond : $beginSecond * 1000;
}
public function generate(?Meta $meta = null): int
{
$meta = $this->meta($meta);
$timestamp = $this->getTimestamp();
$timestamp = ($timestamp - $this->beginSecond) << $this->getTimestampShift();
$businessId = $meta->businessId << $this->getBusinessIdShift();
$dataCenterId = $meta->dataCenterId << $this->getDataCenterShift();
$machineId = $meta->machineId << $this->getMachineIdShift();
return $timestamp | $businessId | $dataCenterId | $machineId | $meta->sequence;
}
public function degenerate(int $id): Meta
{
$timestamp = $id >> $this->getTimestampShift();
$businessId = $id >> $this->getBusinessIdShift();
$dataCenterId = $id >> $this->getDataCenterShift();
$machineId = $id >> $this->getMachineIdShift();
return (new Meta(
$timestamp << Meta::BUSINESS_ID_BITS ^ $businessId,
$businessId << Meta::DATA_CENTER_ID_BITS ^ $dataCenterId,
$dataCenterId << Meta::MACHINE_ID_BITS ^ $machineId,
$machineId << Meta::SEQUENCE_BITS ^ $id
))->setTimestamp($timestamp + $this->beginSecond);
}
protected function getTimestampShift(): int
{
return Meta::SEQUENCE_BITS + Meta::MACHINE_ID_BITS + Meta::DATA_CENTER_ID_BITS + Meta::BUSINESS_ID_BITS;
}
protected function getBusinessIdShift(): int
{
return Meta::SEQUENCE_BITS + Meta::MACHINE_ID_BITS + Meta::DATA_CENTER_ID_BITS;
}
protected function getDataCenterShift(): int
{
return Meta::SEQUENCE_BITS + Meta::MACHINE_ID_BITS;
}
protected function getMachineIdShift(): int
{
return Meta::SEQUENCE_BITS;
}
protected function getTimestamp(): int
{
if ($this->level == self::LEVEL_SECOND) {
return time();
}
return intval(microtime(true) * 1000);
}
protected function meta(?Meta $meta = null): Meta
{
if (is_null($meta)) {
return $this->metaGenerator->generate();
}
return $meta;
}
}

View File

@ -1,66 +0,0 @@
<?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\Snowflake;
use Hyperf\Snowflake\Meta;
use Hyperf\Snowflake\RandomMetaGenerator;
use Hyperf\Snowflake\Snowflake;
use PHPUnit\Framework\TestCase;
/**
* @internal
* @coversNothing
*/
class GeneratorTest extends TestCase
{
public function testGenerateReturnInt()
{
$generator = new Snowflake(new RandomMetaGenerator());
$this->assertTrue(is_int($generator->generate()));
}
public function testDegenerateInstanceofMeta()
{
$generator = new Snowflake(new RandomMetaGenerator());
$id = $generator->generate();
$this->assertInstanceOf(Meta::class, $generator->degenerate($id));
}
public function testGenerateAndDegenerate()
{
$metaGenerator = new RandomMetaGenerator();
$generator = new Snowflake($metaGenerator);
$meta = $metaGenerator->generate();
$id = $generator->generate($meta);
$this->assertEquals($meta, $generator->degenerate($id)->setTimestamp(0));
$id = $generator->generate();
$this->assertEquals($meta->sequence + 1, $generator->degenerate($id)->sequence);
}
public function testDegenerateMaxId()
{
$generator = new Snowflake(new RandomMetaGenerator(), Snowflake::LEVEL_SECOND, 0);
$meta = $generator->degenerate(PHP_INT_MAX);
$days = intval(($meta->timestamp) / (3600 * 24 * 1000));
$this->assertSame(3181, $days);
$generator = new Snowflake(new RandomMetaGenerator(), Snowflake::LEVEL_SECOND, 0);
$meta = $generator->degenerate(PHP_INT_MAX);
$years = intval($meta->timestamp / (3600 * 24 * 365));
$this->assertSame(8716, $years);
}
}

View File

@ -0,0 +1,192 @@
<?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\Snowflake;
use Hyperf\Config\Config;
use Hyperf\Contract\ConfigInterface;
use Hyperf\Di\Container;
use Hyperf\Pool\Channel;
use Hyperf\Pool\PoolOption;
use Hyperf\Redis\Pool\PoolFactory;
use Hyperf\Redis\Pool\RedisPool;
use Hyperf\Redis\RedisProxy;
use Hyperf\Snowflake\Config as SnowflakeConfig;
use Hyperf\Snowflake\IdGenerator\SnowflakeIdGenerator;
use Hyperf\Snowflake\Meta;
use Hyperf\Snowflake\MetaGenerator\RedisMilliSecondMetaGenerator;
use Hyperf\Snowflake\MetaGenerator\RedisSecondMetaGenerator;
use Hyperf\Snowflake\MetaGeneratorInterface;
use Hyperf\Utils\ApplicationContext;
use HyperfTest\Snowflake\Stub\UserDefinedIdGenerator;
use Mockery;
use PHPUnit\Framework\TestCase;
/**
* @internal
* @coversNothing
*/
class RedisMetaGeneratorTest extends TestCase
{
protected function tearDown()
{
$container = $this->getContainer();
$redis = $container->make(RedisProxy::class, ['pool' => 'snowflake']);
$redis->del(RedisMilliSecondMetaGenerator::REDIS_KEY);
}
public function testGenerateMeta()
{
$container = $this->getContainer();
$config = $container->get(ConfigInterface::class);
$metaGenerator = new RedisMilliSecondMetaGenerator($config, new SnowflakeConfig());
$meta = $metaGenerator->generate();
$this->assertInstanceOf(Meta::class, $meta);
$this->assertSame(0, $meta->getDataCenterId());
$this->assertSame(1, $meta->getWorkerId());
}
public function testGenerateId()
{
$container = $this->getContainer();
$hConfig = $container->get(ConfigInterface::class);
$config = new SnowflakeConfig();
$metaGenerator = new RedisMilliSecondMetaGenerator($hConfig, $config);
$generator = new SnowflakeIdGenerator($metaGenerator);
$id = $generator->generate();
$this->assertTrue(is_int($id));
$meta = $generator->degenerate($id);
$this->assertInstanceOf(Meta::class, $meta);
$this->assertSame(0, $meta->getDataCenterId());
$this->assertSame(1, $meta->getWorkerId());
}
public function testGenerateMetaSeconds()
{
$container = $this->getContainer();
$hConfig = $container->get(ConfigInterface::class);
$config = new SnowflakeConfig();
$metaGenerator = new RedisSecondMetaGenerator($hConfig, $config);
$meta = $metaGenerator->generate();
$this->assertInstanceOf(Meta::class, $meta);
$this->assertSame(0, $meta->getDataCenterId());
$this->assertSame(1, $meta->getWorkerId());
}
public function testGenerateAndDeGenerateSeconds()
{
$container = $this->getContainer();
$hConfig = $container->get(ConfigInterface::class);
$config = new SnowflakeConfig();
$metaGenerator = new RedisSecondMetaGenerator($hConfig, $config);
$generator = new SnowflakeIdGenerator($metaGenerator);
$id = $generator->generate();
$this->assertTrue(is_int($id));
$meta = $generator->degenerate($id);
$this->assertInstanceOf(Meta::class, $meta);
$this->assertSame(0, $meta->getDataCenterId());
$this->assertSame(1, $meta->getWorkerId());
}
public function testDeGenerateMaxId()
{
$container = $this->getContainer();
$hConfig = $container->get(ConfigInterface::class);
$config = new SnowflakeConfig();
$metaGenerator = new RedisSecondMetaGenerator($hConfig, $config);
$generator = new SnowflakeIdGenerator($metaGenerator);
$meta = $generator->degenerate(PHP_INT_MAX);
$interval = $meta->getTimeInterval();
$this->assertSame(31, $meta->getDataCenterId());
$this->assertSame(31, $meta->getWorkerId());
$this->assertSame(4095, $meta->getSequence());
$this->assertSame(69730, intval($interval / (3600 * 24 * 365))); // 7W years
}
public function testUserDefinedIdGenerator()
{
$container = $this->getContainer();
$hConfig = $container->get(ConfigInterface::class);
$config = new SnowflakeConfig();
$metaGenerator = new RedisSecondMetaGenerator($hConfig, $config);
$generator = new SnowflakeIdGenerator($metaGenerator);
$generator = new UserDefinedIdGenerator($generator);
$userId = 20190620;
$id = $generator->generate($userId);
$meta = $generator->degenerate($id);
$this->assertSame($meta->getWorkerId(), $userId % 31);
}
protected function getContainer()
{
$config = new Config([
'redis' => [
'snowflake' => [
'host' => 'localhost',
'auth' => '910123',
'port' => 6379,
'db' => 0,
'timeout' => 0.0,
'reserved' => null,
'retry_interval' => 0,
'pool' => [
'min_connections' => 1,
'max_connections' => 10,
'connect_timeout' => 10.0,
'wait_timeout' => 3.0,
'heartbeat' => -1,
'max_idle_time' => 60,
],
],
],
'snowflake' => [
'begin_second' => MetaGeneratorInterface::DEFAULT_BEGIN_SECOND,
RedisMilliSecondMetaGenerator::class => [
'pool' => 'snowflake',
],
RedisSecondMetaGenerator::class => [
'pool' => 'snowflake',
],
],
]);
$container = Mockery::mock(Container::class);
$container->shouldReceive('get')->with(ConfigInterface::class)->andReturn($config);
$container->shouldReceive('make')->with(Channel::class, Mockery::any())->andReturnUsing(function ($class, $args) {
return new Channel($args['size']);
});
$container->shouldReceive('make')->with(PoolOption::class, Mockery::any())->andReturnUsing(function ($class, $args) {
return new PoolOption(...array_values($args));
});
$container->shouldReceive('make')->with(RedisPool::class, Mockery::any())->andReturnUsing(function ($class, $args) use ($container) {
return new RedisPool($container, $args['name']);
});
$factory = new PoolFactory($container);
$container->shouldReceive('make')->with(RedisProxy::class, Mockery::any())->andReturnUsing(function ($class, $args) use ($factory) {
return new RedisProxy($factory, $args['pool']);
});
ApplicationContext::setContainer($container);
return $container;
}
}

View File

@ -0,0 +1,66 @@
<?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\Snowflake;
use Hyperf\Snowflake\Config;
use Hyperf\Snowflake\IdGenerator\SnowflakeIdGenerator;
use Hyperf\Snowflake\Meta;
use Hyperf\Snowflake\MetaGenerator\RandomMilliSecondMetaGenerator;
use Hyperf\Snowflake\MetaGeneratorInterface;
use PHPUnit\Framework\TestCase;
/**
* @internal
* @coversNothing
*/
class SnowflakeGeneratorTest extends TestCase
{
public function testGenerateReturnInt()
{
$config = new Config();
$generator = new SnowflakeIdGenerator(new RandomMilliSecondMetaGenerator($config, MetaGeneratorInterface::DEFAULT_BEGIN_SECOND));
$this->assertTrue(is_int($generator->generate()));
}
public function testDegenerateInstanceofMeta()
{
$config = new Config();
$generator = new SnowflakeIdGenerator(new RandomMilliSecondMetaGenerator($config, MetaGeneratorInterface::DEFAULT_BEGIN_SECOND));
$id = $generator->generate();
$this->assertInstanceOf(Meta::class, $generator->degenerate($id));
}
public function testGenerateAndDegenerate()
{
$config = new Config();
$metaGenerator = new RandomMilliSecondMetaGenerator($config, MetaGeneratorInterface::DEFAULT_BEGIN_SECOND);
$generator = new SnowflakeIdGenerator($metaGenerator);
$meta = $metaGenerator->generate();
$id = $generator->generate($meta);
$this->assertEquals($meta, $generator->degenerate($id));
}
public function testDegenerateMaxId()
{
$config = new Config();
$metaGenerator = new RandomMilliSecondMetaGenerator($config, MetaGeneratorInterface::DEFAULT_BEGIN_SECOND);
$generator = new SnowflakeIdGenerator($metaGenerator);
$meta = $generator->degenerate(PHP_INT_MAX);
$days = intval(($meta->getTimeInterval()) / (3600 * 24 * 1000));
$this->assertSame(25451, $days); // 70 years.
}
}

View File

@ -0,0 +1,40 @@
<?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\Snowflake\Stub;
use Hyperf\Snowflake\IdGenerator;
class UserDefinedIdGenerator
{
/**
* @var IdGenerator\SnowflakeIdGenerator
*/
protected $idGenerator;
public function __construct(IdGenerator\SnowflakeIdGenerator $idGenerator)
{
$this->idGenerator = $idGenerator;
}
public function generate(int $userId)
{
$meta = $this->idGenerator->getMetaGenerator()->generate();
return $this->idGenerator->generate($meta->setWorkerId($userId % 31));
}
public function degenerate(int $id)
{
return $this->idGenerator->degenerate($id);
}
}

View File

@ -1,64 +1,43 @@
# Hyperf Translation
## About
[hyperf/translation](https://github.com/hyperf-cloud/translation) 组件衍生于 `Laravel Translation` 组件的,我们对它进行了一些改造,大部分功能保持了相同。在这里感谢一下 Laravel 开发组,实现了如此强大好用的 Translation 组件。
hyperf/translation 是对Laravel Translation的移植不包含门面和快捷函数部分具体使用方法可以参考Laravel Lang的使用。
## Installation
## Install
```
```bash
composer require hyperf/translation
```
## Config
## Configuration
### Publish config
### publish config
```
php bin/hyperf.php vendor:publish hyperf/translation
```bash
php bin/hyperf.php vendor:publish hyperf/translation
```
### config path
Config files:
```
your/config/path/autoload/translation.php
+ ./config/autoload/translation.php
```
### config content
### Configuration
```
```php
<?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
*/
return [
'locale' => 'en',
'locale' => 'en',
'fallback_locale' => '',
'lang' => BASE_PATH . '/resources/lang',
'path' => BASE_PATH . '/storage/languages',
];
```
## Usage
```
$translator = $this->container->get(\Hyperf\Translation\Contracts\Translator::class);
$translator->trans('validation.accepted'),
```php
$container = ApplicationContext::getContainer();
$translator = $container->get(\Hyperf\Contract\TranslatorInterface::class);
$translator->trans('validation.accepted');
```

View File

@ -1,13 +1,16 @@
{
"name": "hyperf/translation",
"type": "library",
"description": "An independent translation component, forked by illuminate/translation.",
"license": "MIT",
"keywords": [
"translation",
"hyperf"
],
"description": "",
"autoload": {
"files": [
"src/Functions.php"
],
"psr-4": {
"Hyperf\\Translation\\": "src/"
}
@ -19,18 +22,15 @@
},
"require": {
"php": ">=7.2",
"ext-swoole": ">=4.3",
"hyperf/contract": "1.0.*",
"hyperf/utils": "1.0.*",
"hyperf/framework": "1.0.*",
"hyperf/di": "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",
"swoft/swoole-ide-helper": "^4.4.0"
"phpstan/phpstan": "^0.10.5"
},
"config": {
"sort-packages": true

View File

@ -13,5 +13,5 @@ declare(strict_types=1);
return [
'locale' => 'zh_CN',
'fallback_locale' => 'en',
'lang' => BASE_PATH . '/resources/lang',
'path' => BASE_PATH . '/storage/languages',
];

View File

@ -12,9 +12,9 @@ declare(strict_types=1);
namespace Hyperf\Translation;
use Hyperf\Translation\Contracts\Loader;
use Hyperf\Contract\TranslatorLoaderInterface;
class ArrayLoader implements Loader
class ArrayLoader implements TranslatorLoaderInterface
{
/**
* All of the translation messages.
@ -25,13 +25,8 @@ class ArrayLoader implements Loader
/**
* Load the messages for the given locale.
*
* @param string $locale
* @param string $group
* @param null|string $namespace
* @return array
*/
public function load(string $locale, string $group, $namespace = null): array
public function load(string $locale, string $group, ?string $namespace = null): array
{
$namespace = $namespace ?: '*';
@ -40,9 +35,6 @@ class ArrayLoader implements Loader
/**
* Add a new namespace to the loader.
*
* @param string $namespace
* @param string $hint
*/
public function addNamespace(string $namespace, string $hint)
{
@ -50,8 +42,6 @@ class ArrayLoader implements Loader
/**
* Add a new JSON path to the loader.
*
* @param string $path
*/
public function addJsonPath(string $path)
{
@ -59,14 +49,8 @@ class ArrayLoader implements Loader
/**
* Add messages to the loader.
*
* @param string $locale
* @param string $group
* @param array $messages
* @param null|string $namespace
* @return $this
*/
public function addMessages(string $locale, string $group, array $messages, $namespace = null)
public function addMessages(string $locale, string $group, array $messages, ?string $namespace = null): self
{
$namespace = $namespace ?: '*';
@ -77,8 +61,6 @@ class ArrayLoader implements Loader
/**
* Get an array of all the registered namespaces.
*
* @return array
*/
public function namespaces(): array
{

View File

@ -12,8 +12,8 @@ declare(strict_types=1);
namespace Hyperf\Translation;
use Hyperf\Translation\Contracts\Loader;
use Hyperf\Translation\Contracts\Translator;
use Hyperf\Contract\TranslatorInterface;
use Hyperf\Contract\TranslatorLoaderInterface;
class ConfigProvider
{
@ -21,8 +21,8 @@ class ConfigProvider
{
return [
'dependencies' => [
Loader::class => FileLoaderFactory::class,
Translator::class => TranslatorFactory::class,
TranslatorLoaderInterface::class => FileLoaderFactory::class,
TranslatorInterface::class => TranslatorFactory::class,
],
'scan' => [
'paths' => [

View File

@ -12,11 +12,11 @@ declare(strict_types=1);
namespace Hyperf\Translation;
use Hyperf\Translation\Contracts\Loader;
use Hyperf\Contract\TranslatorLoaderInterface;
use Hyperf\Utils\Filesystem\Filesystem;
use RuntimeException;
class FileLoader implements Loader
class FileLoader implements TranslatorLoaderInterface
{
/**
* The filesystem instance.
@ -60,13 +60,8 @@ class FileLoader implements Loader
/**
* Load the messages for the given locale.
*
* @param string $locale
* @param string $group
* @param null|string $namespace
* @return array
*/
public function load(string $locale, string $group, $namespace = null): array
public function load(string $locale, string $group, ?string $namespace = null): array
{
if ($group === '*' && $namespace === '*') {
return $this->loadJsonPaths($locale);
@ -81,9 +76,6 @@ class FileLoader implements Loader
/**
* Add a new namespace to the loader.
*
* @param string $namespace
* @param string $hint
*/
public function addNamespace(string $namespace, string $hint)
{
@ -92,8 +84,6 @@ class FileLoader implements Loader
/**
* Add a new JSON path to the loader.
*
* @param string $path
*/
public function addJsonPath(string $path)
{
@ -102,8 +92,6 @@ class FileLoader implements Loader
/**
* Get an array of all the registered namespaces.
*
* @return array
*/
public function namespaces(): array
{
@ -112,11 +100,6 @@ class FileLoader implements Loader
/**
* Load a namespaced translation group.
*
* @param string $locale
* @param string $group
* @param string $namespace
* @return array
*/
protected function loadNamespaced(string $locale, string $group, string $namespace): array
{
@ -131,12 +114,6 @@ class FileLoader implements Loader
/**
* Load a local namespaced translation group for overrides.
*
* @param array $lines
* @param string $locale
* @param string $group
* @param string $namespace
* @return array
*/
protected function loadNamespaceOverrides(array $lines, string $locale, string $group, string $namespace): array
{
@ -151,11 +128,6 @@ class FileLoader implements Loader
/**
* Load a locale from a given path.
*
* @param string $path
* @param string $locale
* @param string $group
* @return array
*/
protected function loadPath(string $path, string $locale, string $group): array
{
@ -169,11 +141,9 @@ class FileLoader implements Loader
/**
* Load a locale from the given JSON file path.
*
* @param string $locale
* @throws \RuntimeException
* @return array
*/
protected function loadJsonPaths(string $locale)
protected function loadJsonPaths(string $locale): iterable
{
return collect(array_merge($this->jsonPaths, [$this->path]))
->reduce(function ($output, $path) use ($locale) {

View File

@ -22,7 +22,7 @@ class FileLoaderFactory
{
$config = $container->get(ConfigInterface::class);
$files = $container->get(Filesystem::class);
$path = $config->get('translation.lang');
$path = $config->get('translation.path');
return make(FileLoader::class, compact('files', 'path'));
}

View File

@ -0,0 +1,37 @@
<?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
*/
use Hyperf\Contract\TranslatorInterface;
use Hyperf\Utils\ApplicationContext;
if (! function_exists('__')) {
function __(string $key, array $replace = [], ?string $locale = null)
{
$translator = ApplicationContext::getContainer()->get(TranslatorInterface::class);
return $translator->trans($key, $replace, $locale);
}
}
if (! function_exists('trans')) {
function trans(string $key, array $replace = [], ?string $locale = null)
{
return __($key, $replace, $locale);
}
}
if (! function_exists('trans_choice')) {
function trans_choice(string $key, $number, array $replace = [], ?string $locale = null): string
{
$translator = ApplicationContext::getContainer()->get(TranslatorInterface::class);
return $translator->transChoice($key, $number, $replace, $locale);
}
}

View File

@ -1,112 +0,0 @@
<?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 Hyperf\Translation\Support;
class NamespacedItemResolver
{
/**
* A cache of the parsed items.
*
* @var array
*/
protected $parsed = [];
/**
* Parse a key into namespace, group, and item.
*
* @param string $key
* @return array
*/
public function parseKey(string $key): array
{
// If we've already parsed the given key, we'll return the cached version we
// already have, as this will save us some processing. We cache off every
// key we parse so we can quickly return it on all subsequent requests.
if (isset($this->parsed[$key])) {
return $this->parsed[$key];
}
// If the key does not contain a double colon, it means the key is not in a
// namespace, and is just a regular configuration item. Namespaces are a
// tool for organizing configuration items for things such as modules.
if (strpos($key, '::') === false) {
$segments = explode('.', $key);
$parsed = $this->parseBasicSegments($segments);
} else {
$parsed = $this->parseNamespacedSegments($key);
}
// Once we have the parsed array of this key's elements, such as its groups
// and namespace, we will cache each array inside a simple list that has
// the key and the parsed array for quick look-ups for later requests.
return $this->parsed[$key] = $parsed;
}
/**
* Set the parsed value of a key.
*
* @param string $key
* @param array $parsed
*/
public function setParsedKey(string $key, array $parsed)
{
$this->parsed[$key] = $parsed;
}
/**
* Parse an array of basic segments.
*
* @param array $segments
* @return array
*/
protected function parseBasicSegments(array $segments)
{
// The first segment in a basic array will always be the group, so we can go
// ahead and grab that segment. If there is only one total segment we are
// just pulling an entire group out of the array and not a single item.
$group = $segments[0];
// If there is more than one segment in this group, it means we are pulling
// a specific item out of a group and will need to return this item name
// as well as the group so we know which item to pull from the arrays.
$item = count($segments) === 1
? null
: implode('.', array_slice($segments, 1));
return [null, $group, $item];
}
/**
* Parse an array of namespaced segments.
*
* @param string $key
* @return array
*/
protected function parseNamespacedSegments(string $key): array
{
[$namespace, $item] = explode('::', $key);
// First we'll just explode the first segment to get the namespace and group
// since the item should be in the remaining segments. Once we have these
// two pieces of data we can proceed with parsing out the item's value.
$itemSegments = explode('.', $item);
$groupAndItem = array_slice(
$this->parseBasicSegments($itemSegments),
1
);
return array_merge([$namespace], $groupAndItem);
}
}

View File

@ -13,22 +13,21 @@ declare(strict_types=1);
namespace Hyperf\Translation;
use Countable;
use Hyperf\Translation\Contracts\Loader;
use Hyperf\Translation\Contracts\Translator as TranslatorContract;
use Hyperf\Translation\Support\NamespacedItemResolver;
use Hyperf\Contract\TranslatorInterface;
use Hyperf\Contract\TranslatorLoaderInterface;
use Hyperf\Utils\Arr;
use Hyperf\Utils\Collection;
use Hyperf\Utils\Str;
use Hyperf\Utils\Traits\Macroable;
class Translator extends NamespacedItemResolver implements TranslatorContract
class Translator implements TranslatorInterface
{
use Macroable;
/**
* The loader implementation.
*
* @var \Hyperf\Translation\Contracts\Loader
* @var TranslatorLoaderInterface
*/
protected $loader;
@ -61,12 +60,13 @@ class Translator extends NamespacedItemResolver implements TranslatorContract
protected $selector;
/**
* Create a new translator instance.
* A cache of the parsed items.
*
* @param \Hyperf\Translation\Contracts\Loader $loader
* @param string $locale
* @var array
*/
public function __construct(Loader $loader, string $locale)
protected $parsed = [];
public function __construct(TranslatorLoaderInterface $loader, string $locale)
{
$this->loader = $loader;
$this->locale = $locale;
@ -74,25 +74,16 @@ class Translator extends NamespacedItemResolver implements TranslatorContract
/**
* Determine if a translation exists for a given locale.
*
* @param string $key
* @param null|string $locale
* @return bool
*/
public function hasForLocale(string $key, $locale = null): bool
public function hasForLocale(string $key, ?string $locale = null): bool
{
return $this->has($key, $locale, false);
}
/**
* Determine if a translation exists.
*
* @param string $key
* @param null|string $locale
* @param bool $fallback
* @return bool
*/
public function has(string $key, $locale = null, bool $fallback = true): bool
public function has(string $key, ?string $locale = null, bool $fallback = true): bool
{
return $this->get($key, [], $locale, $fallback) !== $key;
}
@ -100,12 +91,9 @@ class Translator extends NamespacedItemResolver implements TranslatorContract
/**
* Get the translation for a given key.
*
* @param string $key
* @param array $replace
* @param null|string $locale
* @return array|string
*/
public function trans(string $key, array $replace = [], $locale = null)
public function trans(string $key, array $replace = [], ?string $locale = null)
{
return $this->get($key, $replace, $locale);
}
@ -113,13 +101,9 @@ class Translator extends NamespacedItemResolver implements TranslatorContract
/**
* Get the translation for the given key.
*
* @param string $key
* @param array $replace
* @param null|string $locale
* @param bool $fallback
* @return array|string
*/
public function get(string $key, array $replace = [], $locale = null, $fallback = true)
public function get(string $key, array $replace = [], ?string $locale = null, bool $fallback = true)
{
[$namespace, $group, $item] = $this->parseKey($key);
@ -150,12 +134,9 @@ class Translator extends NamespacedItemResolver implements TranslatorContract
/**
* Get the translation for a given key from the JSON translation files.
*
* @param string $key
* @param array $replace
* @param null|string $locale
* @return array|string
*/
public function getFromJson(string $key, array $replace = [], $locale = null)
public function getFromJson(string $key, array $replace = [], ?string $locale = null)
{
$locale = $locale ?: $this->locale;
@ -183,13 +164,9 @@ class Translator extends NamespacedItemResolver implements TranslatorContract
/**
* Get a translation according to an integer value.
*
* @param string $key
* @param array|\Countable|int $number
* @param array $replace
* @param null|string $locale
* @return string
*/
public function transChoice(string $key, $number, array $replace = [], $locale = null): string
public function transChoice(string $key, $number, array $replace = [], ?string $locale = null): string
{
return $this->choice($key, $number, $replace, $locale);
}
@ -197,13 +174,9 @@ class Translator extends NamespacedItemResolver implements TranslatorContract
/**
* Get a translation according to an integer value.
*
* @param string $key
* @param array|\Countable|int $number
* @param array $replace
* @param null|string $locale
* @return string
*/
public function choice(string $key, $number, array $replace = [], $locale = null): string
public function choice(string $key, $number, array $replace = [], ?string $locale = null): string
{
$line = $this->get(
$key,
@ -228,10 +201,6 @@ class Translator extends NamespacedItemResolver implements TranslatorContract
/**
* Add translation lines to the given locale.
*
* @param array $lines
* @param string $locale
* @param string $namespace
*/
public function addLines(array $lines, string $locale, string $namespace = '*')
{
@ -244,10 +213,6 @@ class Translator extends NamespacedItemResolver implements TranslatorContract
/**
* Load the specified language group.
*
* @param string $namespace
* @param string $group
* @param string $locale
*/
public function load(string $namespace, string $group, string $locale)
{
@ -265,9 +230,6 @@ class Translator extends NamespacedItemResolver implements TranslatorContract
/**
* Add a new namespace to the loader.
*
* @param string $namespace
* @param string $hint
*/
public function addNamespace(string $namespace, string $hint)
{
@ -276,8 +238,6 @@ class Translator extends NamespacedItemResolver implements TranslatorContract
/**
* Add a new JSON path to the loader.
*
* @param string $path
*/
public function addJsonPath(string $path)
{
@ -286,27 +246,41 @@ class Translator extends NamespacedItemResolver implements TranslatorContract
/**
* Parse a key into namespace, group, and item.
*
* @param string $key
* @return array
*/
public function parseKey(string $key): array
{
$segments = parent::parseKey($key);
if (is_null($segments[0])) {
$segments[0] = '*';
// If we've already parsed the given key, we'll return the cached version we
// already have, as this will save us some processing. We cache off every
// key we parse so we can quickly return it on all subsequent requests.
if (isset($this->parsed[$key])) {
return $this->parsed[$key];
}
return $segments;
// If the key does not contain a double colon, it means the key is not in a
// namespace, and is just a regular configuration item. Namespaces are a
// tool for organizing configuration items for things such as modules.
if (strpos($key, '::') === false) {
$segments = explode('.', $key);
$parsed = $this->parseBasicSegments($segments);
} else {
$parsed = $this->parseNamespacedSegments($key);
}
if (is_null($parsed[0])) {
$parsed[0] = '*';
}
// Once we have the parsed array of this key's elements, such as its groups
// and namespace, we will cache each array inside a simple list that has
// the key and the parsed array for quick look-ups for later requests.
return $this->parsed[$key] = $parsed;
}
/**
* Get the message selector instance.
*
* @return \Hyperf\Translation\MessageSelector
*/
public function getSelector()
public function getSelector(): MessageSelector
{
if (! isset($this->selector)) {
$this->selector = new MessageSelector();
@ -317,8 +291,6 @@ class Translator extends NamespacedItemResolver implements TranslatorContract
/**
* Set the message selector instance.
*
* @param \Hyperf\Translation\MessageSelector $selector
*/
public function setSelector(MessageSelector $selector)
{
@ -328,7 +300,7 @@ class Translator extends NamespacedItemResolver implements TranslatorContract
/**
* Get the language line loader implementation.
*
* @return \Hyperf\Translation\Contracts\Loader
* @return TranslatorLoaderInterface
*/
public function getLoader()
{
@ -337,8 +309,6 @@ class Translator extends NamespacedItemResolver implements TranslatorContract
/**
* Get the default locale being used.
*
* @return string
*/
public function locale(): string
{
@ -347,8 +317,6 @@ class Translator extends NamespacedItemResolver implements TranslatorContract
/**
* Get the default locale being used.
*
* @return string
*/
public function getLocale(): string
{
@ -357,8 +325,6 @@ class Translator extends NamespacedItemResolver implements TranslatorContract
/**
* Set the default locale.
*
* @param string $locale
*/
public function setLocale(string $locale)
{
@ -367,8 +333,6 @@ class Translator extends NamespacedItemResolver implements TranslatorContract
/**
* Get the fallback locale being used.
*
* @return string
*/
public function getFallback(): string
{
@ -377,8 +341,6 @@ class Translator extends NamespacedItemResolver implements TranslatorContract
/**
* Set the fallback locale being used.
*
* @param string $fallback
*/
public function setFallback(string $fallback)
{
@ -387,8 +349,6 @@ class Translator extends NamespacedItemResolver implements TranslatorContract
/**
* Set the loaded translation groups.
*
* @param array $loaded
*/
public function setLoaded(array $loaded)
{
@ -396,12 +356,20 @@ class Translator extends NamespacedItemResolver implements TranslatorContract
}
/**
* Get the proper locale for a choice operation.
* Set the parsed value of a key.
*
* @param null|string $locale
* @return string
* @param string $key
* @param array $parsed
*/
protected function localeForChoice($locale): string
public function setParsedKey(string $key, array $parsed)
{
$this->parsed[$key] = $parsed;
}
/**
* Get the proper locale for a choice operation.
*/
protected function localeForChoice(?string $locale): string
{
return $locale ?: $this->locale ?: $this->fallback;
}
@ -409,11 +377,7 @@ class Translator extends NamespacedItemResolver implements TranslatorContract
/**
* Retrieve a language line out the loaded array.
*
* @param string $namespace
* @param string $group
* @param string $locale
* @param mixed $item
* @param array $replace
* @return null|array|string
*/
protected function getLine(string $namespace, string $group, string $locale, $item, array $replace)
@ -429,6 +393,7 @@ class Translator extends NamespacedItemResolver implements TranslatorContract
if (is_string($line)) {
return $this->makeReplacements($line, $replace);
}
if (is_array($line) && count($line) > 0) {
foreach ($line as $key => $value) {
$line[$key] = $this->makeReplacements($value, $replace);
@ -441,9 +406,8 @@ class Translator extends NamespacedItemResolver implements TranslatorContract
/**
* Make the place-holder replacements on a line.
*
* @param string $line
* @param array $replace
* @return string
* @param array|string $line
* @return array|string
*/
protected function makeReplacements($line, array $replace)
{
@ -468,9 +432,6 @@ class Translator extends NamespacedItemResolver implements TranslatorContract
/**
* Sort the replacements array.
*
* @param array $replace
* @return array
*/
protected function sortReplacements(array $replace): array
{
@ -481,25 +442,63 @@ class Translator extends NamespacedItemResolver implements TranslatorContract
/**
* Determine if the given group has been loaded.
*
* @param string $namespace
* @param string $group
* @param string $locale
* @return bool
*/
protected function isLoaded(string $namespace, string $group, string $locale)
protected function isLoaded(string $namespace, string $group, string $locale): bool
{
return isset($this->loaded[$namespace][$group][$locale]);
}
/**
* Get the array of locales to be checked.
*
* @param null|string $locale
* @return array
*/
protected function localeArray($locale): array
protected function localeArray(?string $locale): array
{
return array_filter([$locale ?: $this->locale, $this->fallback]);
}
/**
* Parse an array of basic segments.
*
* @param array $segments
* @return array
*/
protected function parseBasicSegments(array $segments)
{
// The first segment in a basic array will always be the group, so we can go
// ahead and grab that segment. If there is only one total segment we are
// just pulling an entire group out of the array and not a single item.
$group = $segments[0];
// If there is more than one segment in this group, it means we are pulling
// a specific item out of a group and will need to return this item name
// as well as the group so we know which item to pull from the arrays.
$item = count($segments) === 1
? null
: implode('.', array_slice($segments, 1));
return [null, $group, $item];
}
/**
* Parse an array of namespaced segments.
*
* @param string $key
* @return array
*/
protected function parseNamespacedSegments(string $key): array
{
[$namespace, $item] = explode('::', $key);
// First we'll just explode the first segment to get the namespace and group
// since the item should be in the remaining segments. Once we have these
// two pieces of data we can proceed with parsing out the item's value.
$itemSegments = explode('.', $item);
$groupAndItem = array_slice(
$this->parseBasicSegments($itemSegments),
1
);
return array_merge([$namespace], $groupAndItem);
}
}

View File

@ -13,7 +13,7 @@ declare(strict_types=1);
namespace Hyperf\Translation;
use Hyperf\Contract\ConfigInterface;
use Hyperf\Translation\Contracts\Loader;
use Hyperf\Contract\TranslatorLoaderInterface;
use Psr\Container\ContainerInterface;
class TranslatorFactory
@ -28,11 +28,11 @@ class TranslatorFactory
$locale = $config->get('translation.locale');
$fallbackLocale = $config->get('translation.fallback_locale');
$loader = $container->get(Loader::class);
$loader = $container->get(TranslatorLoaderInterface::class);
$trans = make(Translator::class, compact('loader', 'locale'));
$trans->setFallback((string) $fallbackLocale);
$translator = make(Translator::class, compact('loader', 'locale'));
$translator->setFallback((string) $fallbackLocale);
return $trans;
return $translator;
}
}

View File

@ -10,26 +10,26 @@ declare(strict_types=1);
* @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
*/
namespace HyperfTest\Translation\Cases;
namespace HyperfTest\Translation;
use Hyperf\Translation\FileLoader;
use Hyperf\Utils\Filesystem\Filesystem;
use Mockery as m;
use Mockery;
use PHPUnit\Framework\TestCase;
/**
* @internal
* @coversNothing
*/
class TranslationFileLoaderTest extends AbstractTestCase
class FileLoaderTest extends TestCase
{
protected function tearDown(): void
{
m::close();
Mockery::close();
}
public function testLoadMethodWithoutNamespacesProperlyCallsLoader()
{
$loader = new FileLoader($files = m::mock(Filesystem::class), __DIR__);
$loader = new FileLoader($files = Mockery::mock(Filesystem::class), __DIR__);
$files->shouldReceive('exists')->once()->with(__DIR__ . '/en/foo.php')->andReturn(true);
$files->shouldReceive('getRequire')->once()->with(__DIR__ . '/en/foo.php')->andReturn(['messages']);
@ -38,7 +38,7 @@ class TranslationFileLoaderTest extends AbstractTestCase
public function testLoadMethodWithNamespacesProperlyCallsLoader()
{
$loader = new FileLoader($files = m::mock(Filesystem::class), __DIR__);
$loader = new FileLoader($files = Mockery::mock(Filesystem::class), __DIR__);
$files->shouldReceive('exists')->once()->with('bar/en/foo.php')->andReturn(true);
$files->shouldReceive('exists')->once()->with(__DIR__ . '/vendor/namespace/en/foo.php')->andReturn(false);
$files->shouldReceive('getRequire')->once()->with('bar/en/foo.php')->andReturn(['foo' => 'bar']);
@ -49,7 +49,7 @@ class TranslationFileLoaderTest extends AbstractTestCase
public function testLoadMethodWithNamespacesProperlyCallsLoaderAndLoadsLocalOverrides()
{
$loader = new FileLoader($files = m::mock(Filesystem::class), __DIR__);
$loader = new FileLoader($files = Mockery::mock(Filesystem::class), __DIR__);
$files->shouldReceive('exists')->once()->with('bar/en/foo.php')->andReturn(true);
$files->shouldReceive('exists')->once()->with(__DIR__ . '/vendor/namespace/en/foo.php')->andReturn(true);
$files->shouldReceive('getRequire')->once()->with('bar/en/foo.php')->andReturn(['foo' => 'bar']);
@ -61,7 +61,7 @@ class TranslationFileLoaderTest extends AbstractTestCase
public function testEmptyArraysReturnedWhenFilesDontExist()
{
$loader = new FileLoader($files = m::mock(Filesystem::class), __DIR__);
$loader = new FileLoader($files = Mockery::mock(Filesystem::class), __DIR__);
$files->shouldReceive('exists')->once()->with(__DIR__ . '/en/foo.php')->andReturn(false);
$files->shouldReceive('getRequire')->never();
@ -70,7 +70,7 @@ class TranslationFileLoaderTest extends AbstractTestCase
public function testEmptyArraysReturnedWhenFilesDontExistForNamespacedItems()
{
$loader = new FileLoader($files = m::mock(Filesystem::class), __DIR__);
$loader = new FileLoader($files = Mockery::mock(Filesystem::class), __DIR__);
$files->shouldReceive('getRequire')->never();
$this->assertEquals([], $loader->load('en', 'foo', 'bar'));
@ -78,7 +78,7 @@ class TranslationFileLoaderTest extends AbstractTestCase
public function testLoadMethodForJSONProperlyCallsLoader()
{
$loader = new FileLoader($files = m::mock(Filesystem::class), __DIR__);
$loader = new FileLoader($files = Mockery::mock(Filesystem::class), __DIR__);
$files->shouldReceive('exists')->once()->with(__DIR__ . '/en.json')->andReturn(true);
$files->shouldReceive('get')->once()->with(__DIR__ . '/en.json')->andReturn('{"foo":"bar"}');
@ -87,7 +87,7 @@ class TranslationFileLoaderTest extends AbstractTestCase
public function testLoadMethodForJSONProperlyCallsLoaderForMultiplePaths()
{
$loader = new FileLoader($files = m::mock(Filesystem::class), __DIR__);
$loader = new FileLoader($files = Mockery::mock(Filesystem::class), __DIR__);
$loader->addJsonPath(__DIR__ . '/another');
$files->shouldReceive('exists')->once()->with(__DIR__ . '/en.json')->andReturn(true);

View File

@ -10,15 +10,15 @@ declare(strict_types=1);
* @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
*/
namespace HyperfTest\Translation\Cases;
namespace HyperfTest\Translation;
use Hyperf\Translation\MessageSelector;
use PHPUnit\Framework\TestCase;
/**
* @internal
* @coversNothing
*/
class TranslationMessageSelectorTest extends AbstractTestCase
class MessageSelectorTest extends TestCase
{
/**
* @dataProvider chooseTestData

View File

@ -10,58 +10,105 @@ declare(strict_types=1);
* @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
*/
namespace HyperfTest\Translation\Cases;
namespace HyperfTest\Translation;
use Hyperf\Translation\Contracts\Loader;
use Hyperf\Contract\TranslatorLoaderInterface;
use Hyperf\Translation\MessageSelector;
use Hyperf\Translation\Translator;
use Hyperf\Utils\Collection;
use Mockery as m;
use Mockery;
use PHPUnit\Framework\TestCase;
/**
* @internal
* @coversNothing
*/
class TranslationTranslatorTest extends AbstractTestCase
class TranslatorTest extends TestCase
{
protected function tearDown(): void
{
m::close();
Mockery::close();
}
public function testHasMethodReturnsFalseWhenReturnedTranslationIsNull()
{
$t = $this->getMockBuilder(Translator::class)->setMethods(['get'])->setConstructorArgs([$this->getLoader(), 'en'])->getMock();
$t->expects($this->once())->method('get')->with($this->equalTo('foo'), $this->equalTo([]), $this->equalTo('bar'))->will($this->returnValue('foo'));
$t = $this->getMockBuilder(Translator::class)->setMethods(['get'])->setConstructorArgs([
$this->getLoader(),
'en',
])->getMock();
$t->expects($this->once())
->method('get')
->with($this->equalTo('foo'), $this->equalTo([]), $this->equalTo('bar'))
->will($this->returnValue('foo'));
$this->assertFalse($t->has('foo', 'bar'));
$t = $this->getMockBuilder(Translator::class)->setMethods(['get'])->setConstructorArgs([$this->getLoader(), 'en', 'sp'])->getMock();
$t->expects($this->once())->method('get')->with($this->equalTo('foo'), $this->equalTo([]), $this->equalTo('bar'))->will($this->returnValue('bar'));
$t = $this->getMockBuilder(Translator::class)->setMethods(['get'])->setConstructorArgs([
$this->getLoader(),
'en',
'sp',
])->getMock();
$t->expects($this->once())
->method('get')
->with($this->equalTo('foo'), $this->equalTo([]), $this->equalTo('bar'))
->will($this->returnValue('bar'));
$this->assertTrue($t->has('foo', 'bar'));
$t = $this->getMockBuilder(Translator::class)->setMethods(['get'])->setConstructorArgs([$this->getLoader(), 'en'])->getMock();
$t->expects($this->once())->method('get')->with($this->equalTo('foo'), $this->equalTo([]), $this->equalTo('bar'), false)->will($this->returnValue('bar'));
$t = $this->getMockBuilder(Translator::class)->setMethods(['get'])->setConstructorArgs([
$this->getLoader(),
'en',
])->getMock();
$t->expects($this->once())
->method('get')
->with($this->equalTo('foo'), $this->equalTo([]), $this->equalTo('bar'), false)
->will($this->returnValue('bar'));
$this->assertTrue($t->hasForLocale('foo', 'bar'));
$t = $this->getMockBuilder(Translator::class)->setMethods(['get'])->setConstructorArgs([$this->getLoader(), 'en'])->getMock();
$t->expects($this->once())->method('get')->with($this->equalTo('foo'), $this->equalTo([]), $this->equalTo('bar'), false)->will($this->returnValue('foo'));
$t = $this->getMockBuilder(Translator::class)->setMethods(['get'])->setConstructorArgs([
$this->getLoader(),
'en',
])->getMock();
$t->expects($this->once())
->method('get')
->with($this->equalTo('foo'), $this->equalTo([]), $this->equalTo('bar'), false)
->will($this->returnValue('foo'));
$this->assertFalse($t->hasForLocale('foo', 'bar'));
$t = $this->getMockBuilder(Translator::class)->setMethods(['load', 'getLine'])->setConstructorArgs([$this->getLoader(), 'en'])->getMock();
$t->expects($this->any())->method('load')->with($this->equalTo('*'), $this->equalTo('foo'), $this->equalTo('en'))->will($this->returnValue(null));
$t->expects($this->once())->method('getLine')->with($this->equalTo('*'), $this->equalTo('foo'), $this->equalTo('en'), null, $this->equalTo([]))->will($this->returnValue('bar'));
$t = $this->getMockBuilder(Translator::class)
->setMethods(['load', 'getLine'])
->setConstructorArgs([$this->getLoader(), 'en'])
->getMock();
$t->expects($this->any())
->method('load')
->with($this->equalTo('*'), $this->equalTo('foo'), $this->equalTo('en'))
->will($this->returnValue(null));
$t->expects($this->once())
->method('getLine')
->with($this->equalTo('*'), $this->equalTo('foo'), $this->equalTo('en'), null, $this->equalTo([]))
->will($this->returnValue('bar'));
$this->assertTrue($t->hasForLocale('foo'));
$t = $this->getMockBuilder(Translator::class)->setMethods(['load', 'getLine'])->setConstructorArgs([$this->getLoader(), 'en'])->getMock();
$t->expects($this->any())->method('load')->with($this->equalTo('*'), $this->equalTo('foo'), $this->equalTo('en'))->will($this->returnValue(null));
$t->expects($this->once())->method('getLine')->with($this->equalTo('*'), $this->equalTo('foo'), $this->equalTo('en'), null, $this->equalTo([]))->will($this->returnValue('foo'));
$t = $this->getMockBuilder(Translator::class)
->setMethods(['load', 'getLine'])
->setConstructorArgs([$this->getLoader(), 'en'])
->getMock();
$t->expects($this->any())
->method('load')
->with($this->equalTo('*'), $this->equalTo('foo'), $this->equalTo('en'))
->will($this->returnValue(null));
$t->expects($this->once())
->method('getLine')
->with($this->equalTo('*'), $this->equalTo('foo'), $this->equalTo('en'), null, $this->equalTo([]))
->will($this->returnValue('foo'));
$this->assertFalse($t->hasForLocale('foo'));
}
public function testGetMethodProperlyLoadsAndRetrievesItem()
{
$t = new Translator($this->getLoader(), 'en');
$t->getLoader()->shouldReceive('load')->once()->with('en', 'bar', 'foo')->andReturn(['foo' => 'foo', 'baz' => 'breeze :foo', 'qux' => ['tree :foo', 'breeze :foo']]);
$t->getLoader()->shouldReceive('load')->once()->with('en', 'bar', 'foo')->andReturn([
'foo' => 'foo',
'baz' => 'breeze :foo',
'qux' => ['tree :foo', 'breeze :foo'],
]);
$this->assertEquals(['tree bar', 'breeze bar'], $t->get('foo::bar.qux', ['foo' => 'bar'], 'en'));
$this->assertEquals('breeze bar', $t->get('foo::bar.baz', ['foo' => 'bar'], 'en'));
$this->assertEquals('foo', $t->get('foo::bar.foo'));
@ -70,14 +117,24 @@ class TranslationTranslatorTest extends AbstractTestCase
public function testTransMethodProperlyLoadsAndRetrievesItemWithHTMLInTheMessage()
{
$t = new Translator($this->getLoader(), 'en');
$t->getLoader()->shouldReceive('load')->once()->with('en', 'foo', '*')->andReturn(['bar' => 'breeze <p>test</p>']);
$t->getLoader()
->shouldReceive('load')
->once()
->with('en', 'foo', '*')
->andReturn(['bar' => 'breeze <p>test</p>']);
$this->assertSame('breeze <p>test</p>', $t->trans('foo.bar', [], 'en'));
}
public function testGetMethodProperlyLoadsAndRetrievesItemWithCapitalization()
{
$t = $this->getMockBuilder(Translator::class)->setMethods(null)->setConstructorArgs([$this->getLoader(), 'en'])->getMock();
$t->getLoader()->shouldReceive('load')->once()->with('en', 'bar', 'foo')->andReturn(['foo' => 'foo', 'baz' => 'breeze :Foo :BAR']);
$t = $this->getMockBuilder(Translator::class)
->setMethods(null)
->setConstructorArgs([$this->getLoader(), 'en'])
->getMock();
$t->getLoader()->shouldReceive('load')->once()->with('en', 'bar', 'foo')->andReturn([
'foo' => 'foo',
'baz' => 'breeze :Foo :BAR',
]);
$this->assertEquals('breeze Bar FOO', $t->get('foo::bar.baz', ['foo' => 'bar', 'bar' => 'foo'], 'en'));
$this->assertEquals('foo', $t->get('foo::bar.foo'));
}
@ -85,9 +142,15 @@ class TranslationTranslatorTest extends AbstractTestCase
public function testGetMethodProperlyLoadsAndRetrievesItemWithLongestReplacementsFirst()
{
$t = new Translator($this->getLoader(), 'en');
$t->getLoader()->shouldReceive('load')->once()->with('en', 'bar', 'foo')->andReturn(['foo' => 'foo', 'baz' => 'breeze :foo :foobar']);
$t->getLoader()->shouldReceive('load')->once()->with('en', 'bar', 'foo')->andReturn([
'foo' => 'foo',
'baz' => 'breeze :foo :foobar',
]);
$this->assertEquals('breeze bar taylor', $t->get('foo::bar.baz', ['foo' => 'bar', 'foobar' => 'taylor'], 'en'));
$this->assertEquals('breeze foo bar baz taylor', $t->get('foo::bar.baz', ['foo' => 'foo bar baz', 'foobar' => 'taylor'], 'en'));
$this->assertEquals('breeze foo bar baz taylor', $t->get('foo::bar.baz', [
'foo' => 'foo bar baz',
'foobar' => 'taylor',
], 'en'));
$this->assertEquals('foo', $t->get('foo::bar.foo'));
}
@ -96,7 +159,10 @@ class TranslationTranslatorTest extends AbstractTestCase
$t = new Translator($this->getLoader(), 'en');
$t->setFallback('lv');
$t->getLoader()->shouldReceive('load')->once()->with('en', 'bar', 'foo')->andReturn([]);
$t->getLoader()->shouldReceive('load')->once()->with('lv', 'bar', 'foo')->andReturn(['foo' => 'foo', 'baz' => 'breeze :foo']);
$t->getLoader()->shouldReceive('load')->once()->with('lv', 'bar', 'foo')->andReturn([
'foo' => 'foo',
'baz' => 'breeze :foo',
]);
$this->assertEquals('breeze bar', $t->get('foo::bar.baz', ['foo' => 'bar'], 'en'));
$this->assertEquals('foo', $t->get('foo::bar.foo'));
}
@ -110,9 +176,15 @@ class TranslationTranslatorTest extends AbstractTestCase
public function testChoiceMethodProperlyLoadsAndRetrievesItem()
{
$t = $this->getMockBuilder(Translator::class)->setMethods(['get'])->setConstructorArgs([$this->getLoader(), 'en'])->getMock();
$t->expects($this->once())->method('get')->with($this->equalTo('foo'), $this->equalTo(['replace']), $this->equalTo('en'))->will($this->returnValue('line'));
$t->setSelector($selector = m::mock(MessageSelector::class));
$t = $this->getMockBuilder(Translator::class)->setMethods(['get'])->setConstructorArgs([
$this->getLoader(),
'en',
])->getMock();
$t->expects($this->once())
->method('get')
->with($this->equalTo('foo'), $this->equalTo(['replace']), $this->equalTo('en'))
->will($this->returnValue('line'));
$t->setSelector($selector = Mockery::mock(MessageSelector::class));
$selector->shouldReceive('choose')->once()->with('line', 10, 'en')->andReturn('choiced');
$t->choice('foo', 10, ['replace']);
@ -120,9 +192,15 @@ class TranslationTranslatorTest extends AbstractTestCase
public function testChoiceMethodProperlyCountsCollectionsAndLoadsAndRetrievesItem()
{
$t = $this->getMockBuilder(Translator::class)->setMethods(['get'])->setConstructorArgs([$this->getLoader(), 'en'])->getMock();
$t->expects($this->exactly(2))->method('get')->with($this->equalTo('foo'), $this->equalTo(['replace']), $this->equalTo('en'))->will($this->returnValue('line'));
$t->setSelector($selector = m::mock(MessageSelector::class));
$t = $this->getMockBuilder(Translator::class)->setMethods(['get'])->setConstructorArgs([
$this->getLoader(),
'en',
])->getMock();
$t->expects($this->exactly(2))
->method('get')
->with($this->equalTo('foo'), $this->equalTo(['replace']), $this->equalTo('en'))
->will($this->returnValue('line'));
$t->setSelector($selector = Mockery::mock(MessageSelector::class));
$selector->shouldReceive('choose')->twice()->with('line', 3, 'en')->andReturn('choiced');
$values = ['foo', 'bar', 'baz'];
@ -142,8 +220,16 @@ class TranslationTranslatorTest extends AbstractTestCase
public function testGetJsonReplaces()
{
$t = new Translator($this->getLoader(), 'en');
$t->getLoader()->shouldReceive('load')->once()->with('en', '*', '*')->andReturn(['foo :i:c :u' => 'bar :i:c :u']);
$this->assertEquals('bar onetwo three', $t->getFromJson('foo :i:c :u', ['i' => 'one', 'c' => 'two', 'u' => 'three']));
$t->getLoader()
->shouldReceive('load')
->once()
->with('en', '*', '*')
->andReturn(['foo :i:c :u' => 'bar :i:c :u']);
$this->assertEquals('bar onetwo three', $t->getFromJson('foo :i:c :u', [
'i' => 'one',
'c' => 'two',
'u' => 'three',
]));
}
public function testGetJsonReplacesForAssociativeInput()
@ -156,8 +242,15 @@ class TranslationTranslatorTest extends AbstractTestCase
public function testGetJsonPreservesOrder()
{
$t = new Translator($this->getLoader(), 'en');
$t->getLoader()->shouldReceive('load')->once()->with('en', '*', '*')->andReturn(['to :name I give :greeting' => ':greeting :name']);
$this->assertEquals('Greetings David', $t->getFromJson('to :name I give :greeting', ['name' => 'David', 'greeting' => 'Greetings']));
$t->getLoader()
->shouldReceive('load')
->once()
->with('en', '*', '*')
->andReturn(['to :name I give :greeting' => ':greeting :name']);
$this->assertEquals('Greetings David', $t->getFromJson('to :name I give :greeting', [
'name' => 'David',
'greeting' => 'Greetings',
]));
}
public function testGetJsonForNonExistingJsonKeyLooksForRegularKeys()
@ -194,6 +287,6 @@ class TranslationTranslatorTest extends AbstractTestCase
protected function getLoader()
{
return m::mock(Loader::class);
return Mockery::mock(TranslatorLoaderInterface::class);
}
}

View File

@ -1,8 +0,0 @@
[opcache]
opcache.enable_cli=1
[redis]
extension = "redis.so"
[swoole]
extension = "swoole.so"

View File

@ -1,10 +0,0 @@
#!/usr/bin/env bash
wget https://github.com/swoole/swoole-src/archive/v${SW_VERSION}.tar.gz -O swoole.tar.gz
mkdir -p swoole
tar -xf swoole.tar.gz -C swoole --strip-components=1
rm swoole.tar.gz
cd swoole
phpize
./configure --enable-openssl --enable-mysqlnd
make -j$(nproc)
make install

View File

@ -10,27 +10,21 @@ declare(strict_types=1);
* @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Validation\Contracts\Support;
namespace Hyperf\Utils\Contracts;
use Hyperf\Utils\Contracts\Arrayable;
use Hyperf\Validation\Contracts\Support\MessageProvider;
interface MessageBag extends Arrayable
interface MessageBag
{
/**
* Get the keys present in the message bag.
*
* @return array
*/
public function keys(): array;
/**
* Add a message to the bag.
*
* @param string $key
* @param string $message
* @return $this
*/
public function add(string $key, string $message);
public function add(string $key, string $message): MessageBag;
/**
* Merge a new array of messages into the bag.
@ -44,7 +38,6 @@ interface MessageBag extends Arrayable
* Determine if messages exist for a given key.
*
* @param array|string $key
* @return bool
*/
public function has($key): bool;
@ -53,67 +46,48 @@ interface MessageBag extends Arrayable
*
* @param null|string $key
* @param null|string $format
* @return string
*/
public function first($key = null, $format = null): string;
public function first(?string $key = null, ?string $format = null): string;
/**
* Get all of the messages from the bag for a given key.
*
* @param string $key
* @param null|string $format
* @return array
*/
public function get(string $key, $format = null): array;
public function get(string $key, ?string $format = null): array;
/**
* Get all of the messages for every key in the bag.
*
* @param null|string $format
* @return array
*/
public function all($format = null): array;
public function all(?string $format = null): array;
/**
* Get the raw messages in the container.
*
* @return array
*/
public function getMessages(): array;
/**
* Get the default message format.
*
* @return string
*/
public function getFormat(): string;
/**
* Set the default message format.
*
* @param string $format
* @return $this
*/
public function setFormat(string $format = ':message');
/**
* Determine if the message bag has any messages.
*
* @return bool
*/
public function isEmpty(): bool;
/**
* Determine if the message bag has any messages.
*
* @return bool
*/
public function isNotEmpty(): bool;
/**
* Get the number of messages in the container.
*
* @return int
*/
public function count(): int;
}

View File

@ -10,14 +10,12 @@ declare(strict_types=1);
* @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Validation\Contracts\Support;
namespace Hyperf\Utils\Contracts;
interface MessageProvider
{
/**
* Get the messages for the instance.
*
* @return MessageBag
*/
public function getMessageBag();
public function getMessageBag(): MessageBag;
}

View File

@ -43,10 +43,17 @@ class Coroutine
/**
* Returns the parent coroutine ID.
* Returns -1 when running in non-coroutine context.
*
* @see https://github.com/swoole/swoole-src/pull/2669/files#diff-3bdf726b0ac53be7e274b60d59e6ec80R940
*/
public static function parentId(): int
{
return SwooleCoroutine::getPcid();
$cid = SwooleCoroutine::getPcid();
if ($cid === false) {
return -1;
}
return $cid;
}
/**

View File

@ -10,15 +10,13 @@ declare(strict_types=1);
* @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Validation\Support;
namespace Hyperf\Utils;
use Countable;
use Hyperf\Utils\Arr;
use Hyperf\Utils\Contracts\Arrayable;
use Hyperf\Utils\Contracts\Jsonable;
use Hyperf\Utils\Str;
use Hyperf\Validation\Contracts\Support\MessageBag as MessageBagContract;
use Hyperf\Validation\Contracts\Support\MessageProvider;
use Hyperf\Utils\Contracts\MessageBag as MessageBagContract;
use Hyperf\Utils\Contracts\MessageProvider;
use JsonSerializable;
class MessageBag implements Arrayable, Countable, Jsonable, JsonSerializable, MessageBagContract, MessageProvider
@ -39,8 +37,6 @@ class MessageBag implements Arrayable, Countable, Jsonable, JsonSerializable, Me
/**
* Create a new message bag instance.
*
* @param array $messages
*/
public function __construct(array $messages = [])
{
@ -53,8 +49,6 @@ class MessageBag implements Arrayable, Countable, Jsonable, JsonSerializable, Me
/**
* Convert the message bag to its string representation.
*
* @return string
*/
public function __toString(): string
{
@ -63,8 +57,6 @@ class MessageBag implements Arrayable, Countable, Jsonable, JsonSerializable, Me
/**
* Get the keys present in the message bag.
*
* @return array
*/
public function keys(): array
{
@ -73,12 +65,8 @@ class MessageBag implements Arrayable, Countable, Jsonable, JsonSerializable, Me
/**
* Add a message to the message bag.
*
* @param string $key
* @param string $message
* @return $this
*/
public function add(string $key, string $message)
public function add(string $key, string $message): MessageBagContract
{
if ($this->isUnique($key, $message)) {
$this->messages[$key][] = $message;
@ -108,7 +96,6 @@ class MessageBag implements Arrayable, Countable, Jsonable, JsonSerializable, Me
* Determine if messages exist for all of the given keys.
*
* @param array|string $key
* @return bool
*/
public function has($key): bool
{
@ -135,7 +122,6 @@ class MessageBag implements Arrayable, Countable, Jsonable, JsonSerializable, Me
* Determine if messages exist for any of the given keys.
*
* @param array|string $keys
* @return bool
*/
public function hasAny($keys = []): bool
{
@ -159,7 +145,6 @@ class MessageBag implements Arrayable, Countable, Jsonable, JsonSerializable, Me
*
* @param string $key
* @param string $format
* @return string
*/
public function first($key = null, $format = null): string
{
@ -172,12 +157,8 @@ class MessageBag implements Arrayable, Countable, Jsonable, JsonSerializable, Me
/**
* Get all of the messages from the message bag for a given key.
*
* @param string $key
* @param string $format
* @return array
*/
public function get(string $key, $format = null): array
public function get(string $key, ?string $format = null): array
{
// If the message exists in the message bag, we will transform it and return
// the message. Otherwise, we will check if the key is implicit & collect
@ -199,11 +180,8 @@ class MessageBag implements Arrayable, Countable, Jsonable, JsonSerializable, Me
/**
* Get all of the messages for every key in the message bag.
*
* @param string $format
* @return array
*/
public function all($format = null): array
public function all(?string $format = null): array
{
$format = $this->checkFormat($format);
@ -218,19 +196,14 @@ class MessageBag implements Arrayable, Countable, Jsonable, JsonSerializable, Me
/**
* Get all of the unique messages for every key in the message bag.
*
* @param string $format
* @return array
*/
public function unique($format = null): array
public function unique(?string $format = null): array
{
return array_unique($this->all($format));
}
/**
* Get the raw messages in the message bag.
*
* @return array
*/
public function messages(): array
{
@ -239,8 +212,6 @@ class MessageBag implements Arrayable, Countable, Jsonable, JsonSerializable, Me
/**
* Get the raw messages in the message bag.
*
* @return array
*/
public function getMessages(): array
{
@ -249,18 +220,14 @@ class MessageBag implements Arrayable, Countable, Jsonable, JsonSerializable, Me
/**
* Get the messages for the instance.
*
* @return MessageBag
*/
public function getMessageBag()
public function getMessageBag(): MessageBagContract
{
return $this;
}
/**
* Get the default message format.
*
* @return string
*/
public function getFormat(): string
{
@ -269,11 +236,8 @@ class MessageBag implements Arrayable, Countable, Jsonable, JsonSerializable, Me
/**
* Set the default message format.
*
* @param string $format
* @return MessageBag
*/
public function setFormat(string $format = ':message')
public function setFormat(string $format = ':message'): self
{
$this->format = $format;
@ -282,8 +246,6 @@ class MessageBag implements Arrayable, Countable, Jsonable, JsonSerializable, Me
/**
* Determine if the message bag has any messages.
*
* @return bool
*/
public function isEmpty(): bool
{
@ -292,8 +254,6 @@ class MessageBag implements Arrayable, Countable, Jsonable, JsonSerializable, Me
/**
* Determine if the message bag has any messages.
*
* @return bool
*/
public function isNotEmpty(): bool
{
@ -302,8 +262,6 @@ class MessageBag implements Arrayable, Countable, Jsonable, JsonSerializable, Me
/**
* Determine if the message bag has any messages.
*
* @return bool
*/
public function any(): bool
{
@ -312,8 +270,6 @@ class MessageBag implements Arrayable, Countable, Jsonable, JsonSerializable, Me
/**
* Get the number of messages in the message bag.
*
* @return int
*/
public function count(): int
{
@ -322,8 +278,6 @@ class MessageBag implements Arrayable, Countable, Jsonable, JsonSerializable, Me
/**
* Get the instance as an array.
*
* @return array
*/
public function toArray(): array
{
@ -332,8 +286,6 @@ class MessageBag implements Arrayable, Countable, Jsonable, JsonSerializable, Me
/**
* Convert the object into something JSON serializable.
*
* @return array
*/
public function jsonSerialize(): array
{
@ -342,9 +294,6 @@ class MessageBag implements Arrayable, Countable, Jsonable, JsonSerializable, Me
/**
* Convert the object to its JSON representation.
*
* @param int $options
* @return string
*/
public function toJson(int $options = 0): string
{
@ -353,10 +302,6 @@ class MessageBag implements Arrayable, Countable, Jsonable, JsonSerializable, Me
/**
* Determine if a key and message combination already exists.
*
* @param string $key
* @param string $message
* @return bool
*/
protected function isUnique(string $key, string $message): bool
{
@ -367,12 +312,8 @@ class MessageBag implements Arrayable, Countable, Jsonable, JsonSerializable, Me
/**
* Get the messages for a wildcard key.
*
* @param string $key
* @param null|string $format
* @return array
*/
protected function getMessagesForWildcardKey(string $key, $format): array
protected function getMessagesForWildcardKey(string $key, ?string $format): array
{
return collect($this->messages)
->filter(function ($messages, $messageKey) use ($key) {
@ -389,11 +330,6 @@ class MessageBag implements Arrayable, Countable, Jsonable, JsonSerializable, Me
/**
* Format an array of messages.
*
* @param array $messages
* @param string $format
* @param string $messageKey
* @return array
*/
protected function transform(array $messages, string $format, string $messageKey): array
{
@ -408,11 +344,8 @@ class MessageBag implements Arrayable, Countable, Jsonable, JsonSerializable, Me
/**
* Get the appropriate format based on the given format.
*
* @param string $format
* @return string
*/
protected function checkFormat($format)
protected function checkFormat(?string $format): string
{
return $format ?: $this->format;
}

View File

@ -3,7 +3,7 @@
## About
hyperf/validation 是对Laravel Validation的移植不包含门面部分具体使用方法可以参考Laravel Validation 的使用
[hyperf/validation](https://github.com/hyperf-cloud/validation) 组件衍生于 `Laravel Validation` 组件的,我们对它进行了一些改造,大部分功能保持了相同。在这里感谢一下 Laravel 开发组,实现了如此强大好用的 Validation 组件
## Install
@ -17,7 +17,7 @@ composer require hyperf/validation
### publish config
```
php bin/hyperf.php vendor:publish hyperf/translation
php bin/hyperf.php vendor:publish hyperf/validation
```

View File

@ -20,19 +20,18 @@
"require": {
"php": ">=7.2",
"ext-swoole": ">=4.3",
"hyperf/translation": "^1.0",
"egulias/email-validator": "^2.1",
"hyperf/command": "^1.0",
"hyperf/database": "^1.0",
"hyperf/devtool": "^1.0",
"hyperf/di": "1.0.*",
"hyperf/framework": "1.0.*",
"hyperf/command": "~1.0.0",
"hyperf/database": "~1.0.0",
"hyperf/devtool": "~1.0.0",
"hyperf/di": "~1.0.0",
"hyperf/framework": "~1.0.0",
"hyperf/http-server": "~1.0.0",
"hyperf/utils": "1.0.*",
"hyperf/utils": "~1.0.0",
"hyperf/translation": "~1.0.0",
"nesbot/carbon": "^2.21",
"psr/container": "^1.0",
"psr/http-message": "^1.0",
"symfony/http-foundation": "^4.3"
"psr/http-message": "^1.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.14",

View File

@ -69,10 +69,8 @@ class ClosureValidationRule implements RuleContract
/**
* Get the validation error message.
*
* @return string
*/
public function message()
public function message(): string
{
return $this->message;
}

View File

@ -13,9 +13,10 @@ declare(strict_types=1);
namespace Hyperf\Validation\Concerns;
use Closure;
use Hyperf\HttpMessage\Upload\UploadedFile;
use Hyperf\Utils\Arr;
use Hyperf\Utils\Str;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Hyperf\Validation\Validator;
trait FormatsMessages
{
@ -23,12 +24,6 @@ trait FormatsMessages
/**
* Replace all error message place-holders with actual values.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array $parameters
* @return string
*/
public function makeReplacements(string $message, string $attribute, string $rule, array $parameters): string
{
@ -51,9 +46,6 @@ trait FormatsMessages
/**
* Get the displayable name of the attribute.
*
* @param string $attribute
* @return string
*/
public function getDisplayableAttribute(string $attribute): string
{
@ -112,10 +104,6 @@ trait FormatsMessages
/**
* Get the validation message for an attribute and rule.
*
* @param string $attribute
* @param string $rule
* @return string
*/
protected function getMessage(string $attribute, string $rule): string
{
@ -167,8 +155,6 @@ trait FormatsMessages
/**
* Get the proper inline error message for standard and size rules.
*
* @param string $attribute
* @param string $rule
* @return null|string
*/
protected function getInlineMessage(string $attribute, string $rule)
@ -183,8 +169,6 @@ trait FormatsMessages
/**
* Get the inline message for a rule if it exists.
*
* @param string $attribute
* @param string $lowerRule
* @param null|array $source
* @return null|string
*/
@ -208,9 +192,6 @@ trait FormatsMessages
/**
* Get the custom error message from translator.
*
* @param string $key
* @return string
*/
protected function getCustomMessageFromTranslator(string $key): string
{
@ -234,11 +215,6 @@ trait FormatsMessages
/**
* Check the given messages for a wildcard key.
*
* @param array $messages
* @param string $search
* @param string $default
* @return string
*/
protected function getWildcardCustomMessages(array $messages, string $search, string $default): string
{
@ -253,10 +229,6 @@ trait FormatsMessages
/**
* Get the proper error message for an attribute and size rule.
*
* @param string $attribute
* @param string $rule
* @return string
*/
protected function getSizeMessage(string $attribute, string $rule): string
{
@ -274,9 +246,6 @@ trait FormatsMessages
/**
* Get the data type of the given attribute.
*
* @param string $attribute
* @return string
*/
protected function getAttributeType(string $attribute): string
{
@ -298,9 +267,6 @@ trait FormatsMessages
/**
* Get the given attribute from the attribute translations.
*
* @param string $name
* @return string
*/
protected function getAttributeFromTranslations(string $name): string
{
@ -309,10 +275,6 @@ trait FormatsMessages
/**
* Replace the :attribute placeholder in the given message.
*
* @param string $message
* @param string $value
* @return string
*/
protected function replaceAttributePlaceholder(string $message, string $value): string
{
@ -325,10 +287,6 @@ trait FormatsMessages
/**
* Replace the :input placeholder in the given message.
*
* @param string $message
* @param string $attribute
* @return string
*/
protected function replaceInputPlaceholder(string $message, string $attribute): string
{
@ -343,9 +301,6 @@ trait FormatsMessages
/**
* Transform an array of attributes to their displayable form.
*
* @param array $values
* @return array
*/
protected function getAttributeList(array $values): array
{
@ -363,15 +318,8 @@ trait FormatsMessages
/**
* Call a custom validator message replacer.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array $parameters
* @param \Hyperf\Validation\Validator $validator
* @return null|string
*/
protected function callReplacer(string $message, string $attribute, string $rule, array $parameters, $validator)
protected function callReplacer(string $message, string $attribute, string $rule, array $parameters, Validator $validator): ?string
{
$callback = $this->replacers[$rule];
@ -386,18 +334,12 @@ trait FormatsMessages
/**
* Call a class based validator message replacer.
*
* @param string $callback
* @param string $message
* @param string $attribute
* @param string $rule
* @param array $parameters
* @param \Hyperf\Validation\Validator $validator
* @return string
*/
protected function callClassBasedReplacer(string $callback, string $message, string $attribute, string $rule, array $parameters, $validator): string
{
[$class, $method] = Str::parseCallback($callback, 'replace');
return call_user_func_array([$this->container->make($class), $method], array_slice(func_get_args(), 1));
return call_user_func_array([make($class), $method], array_slice(func_get_args(), 1));
}
}

View File

@ -18,12 +18,6 @@ trait ReplacesAttributes
{
/**
* Replace all place-holders for the between rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array $parameters
* @return string
*/
protected function replaceBetween(string $message, string $attribute, string $rule, array $parameters): string
{
@ -32,12 +26,6 @@ trait ReplacesAttributes
/**
* Replace all place-holders for the date_format rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array $parameters
* @return string
*/
protected function replaceDateFormat(string $message, string $attribute, string $rule, array $parameters): string
{
@ -46,12 +34,6 @@ trait ReplacesAttributes
/**
* Replace all place-holders for the different rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array $parameters
* @return string
*/
protected function replaceDifferent(string $message, string $attribute, string $rule, array $parameters): string
{
@ -60,12 +42,6 @@ trait ReplacesAttributes
/**
* Replace all place-holders for the digits rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array $parameters
* @return string
*/
protected function replaceDigits(string $message, string $attribute, string $rule, array $parameters): string
{
@ -74,12 +50,6 @@ trait ReplacesAttributes
/**
* Replace all place-holders for the digits (between) rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array $parameters
* @return string
*/
protected function replaceDigitsBetween(string $message, string $attribute, string $rule, array $parameters): string
{
@ -88,12 +58,6 @@ trait ReplacesAttributes
/**
* Replace all place-holders for the min rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array $parameters
* @return string
*/
protected function replaceMin(string $message, string $attribute, string $rule, array $parameters): string
{
@ -102,26 +66,14 @@ trait ReplacesAttributes
/**
* Replace all place-holders for the max rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array $parameters
* @return string
*/
protected function replaceMax(string $message, string $attribute, string $rule, array $parameters)
protected function replaceMax(string $message, string $attribute, string $rule, array $parameters): string
{
return str_replace(':max', $parameters[0], $message);
}
/**
* Replace all place-holders for the in rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array $parameters
* @return string
*/
protected function replaceIn(string $message, string $attribute, string $rule, array $parameters): string
{
@ -134,12 +86,6 @@ trait ReplacesAttributes
/**
* Replace all place-holders for the not_in rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array $parameters
* @return string
*/
protected function replaceNotIn(string $message, string $attribute, string $rule, array $parameters): string
{
@ -148,12 +94,6 @@ trait ReplacesAttributes
/**
* Replace all place-holders for the in_array rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array $parameters
* @return string
*/
protected function replaceInArray(string $message, string $attribute, string $rule, array $parameters): string
{
@ -162,12 +102,6 @@ trait ReplacesAttributes
/**
* Replace all place-holders for the mimetypes rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array $parameters
* @return string
*/
protected function replaceMimetypes(string $message, string $attribute, string $rule, array $parameters): string
{
@ -176,12 +110,6 @@ trait ReplacesAttributes
/**
* Replace all place-holders for the mimes rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array $parameters
* @return string
*/
protected function replaceMimes(string $message, string $attribute, string $rule, array $parameters): string
{
@ -190,12 +118,6 @@ trait ReplacesAttributes
/**
* Replace all place-holders for the required_with rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array $parameters
* @return string
*/
protected function replaceRequiredWith(string $message, string $attribute, string $rule, array $parameters): string
{
@ -204,12 +126,6 @@ trait ReplacesAttributes
/**
* Replace all place-holders for the required_with_all rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array $parameters
* @return string
*/
protected function replaceRequiredWithAll(string $message, string $attribute, string $rule, array $parameters): string
{
@ -218,12 +134,6 @@ trait ReplacesAttributes
/**
* Replace all place-holders for the required_without rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array $parameters
* @return string
*/
protected function replaceRequiredWithout(string $message, string $attribute, string $rule, array $parameters): string
{
@ -232,12 +142,6 @@ trait ReplacesAttributes
/**
* Replace all place-holders for the required_without_all rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array $parameters
* @return string
*/
protected function replaceRequiredWithoutAll(string $message, string $attribute, string $rule, array $parameters): string
{
@ -246,12 +150,6 @@ trait ReplacesAttributes
/**
* Replace all place-holders for the size rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array $parameters
* @return string
*/
protected function replaceSize(string $message, string $attribute, string $rule, array $parameters): string
{
@ -260,12 +158,6 @@ trait ReplacesAttributes
/**
* Replace all place-holders for the gt rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array $parameters
* @return string
*/
protected function replaceGt(string $message, string $attribute, string $rule, array $parameters): string
{
@ -278,12 +170,6 @@ trait ReplacesAttributes
/**
* Replace all place-holders for the lt rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array $parameters
* @return string
*/
protected function replaceLt(string $message, string $attribute, string $rule, array $parameters): string
{
@ -296,12 +182,6 @@ trait ReplacesAttributes
/**
* Replace all place-holders for the gte rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array $parameters
* @return string
*/
protected function replaceGte(string $message, string $attribute, string $rule, array $parameters): string
{
@ -314,12 +194,6 @@ trait ReplacesAttributes
/**
* Replace all place-holders for the lte rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array $parameters
* @return string
*/
protected function replaceLte(string $message, string $attribute, string $rule, array $parameters): string
{
@ -332,12 +206,6 @@ trait ReplacesAttributes
/**
* Replace all place-holders for the required_if rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array $parameters
* @return string
*/
protected function replaceRequiredIf(string $message, string $attribute, string $rule, array $parameters): string
{
@ -350,12 +218,6 @@ trait ReplacesAttributes
/**
* Replace all place-holders for the required_unless rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array $parameters
* @return string
*/
protected function replaceRequiredUnless(string $message, string $attribute, string $rule, array $parameters): string
{
@ -372,12 +234,6 @@ trait ReplacesAttributes
/**
* Replace all place-holders for the same rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array $parameters
* @return string
*/
protected function replaceSame(string $message, string $attribute, string $rule, array $parameters): string
{
@ -386,12 +242,6 @@ trait ReplacesAttributes
/**
* Replace all place-holders for the before rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array $parameters
* @return string
*/
protected function replaceBefore(string $message, string $attribute, string $rule, array $parameters): string
{
@ -404,12 +254,6 @@ trait ReplacesAttributes
/**
* Replace all place-holders for the before_or_equal rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array $parameters
* @return string
*/
protected function replaceBeforeOrEqual(string $message, string $attribute, string $rule, array $parameters): string
{
@ -418,12 +262,6 @@ trait ReplacesAttributes
/**
* Replace all place-holders for the after rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array $parameters
* @return string
*/
protected function replaceAfter(string $message, string $attribute, string $rule, array $parameters): string
{
@ -432,12 +270,6 @@ trait ReplacesAttributes
/**
* Replace all place-holders for the after_or_equal rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array $parameters
* @return string
*/
protected function replaceAfterOrEqual(string $message, string $attribute, string $rule, array $parameters): string
{
@ -446,12 +278,6 @@ trait ReplacesAttributes
/**
* Replace all place-holders for the date_equals rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array $parameters
* @return string
*/
protected function replaceDateEquals(string $message, string $attribute, string $rule, array $parameters): string
{
@ -460,12 +286,6 @@ trait ReplacesAttributes
/**
* Replace all place-holders for the dimensions rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array $parameters
* @return string
*/
protected function replaceDimensions(string $message, string $attribute, string $rule, array $parameters): string
{
@ -482,12 +302,6 @@ trait ReplacesAttributes
/**
* Replace all place-holders for the ends_with rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array $parameters
* @return string
*/
protected function replaceEndsWith(string $message, string $attribute, string $rule, array $parameters): string
{
@ -500,12 +314,6 @@ trait ReplacesAttributes
/**
* Replace all place-holders for the starts_with rule.
*
* @param string $message
* @param string $attribute
* @param string $rule
* @param array $parameters
* @return string
*/
protected function replaceStartsWith(string $message, string $attribute, string $rule, array $parameters): string
{

View File

@ -21,14 +21,14 @@ use DateTimeZone;
use Egulias\EmailValidator\EmailValidator;
use Egulias\EmailValidator\Validation\RFCValidation;
use Exception;
use Hyperf\HttpMessage\Upload\UploadedFile;
use Hyperf\Utils\Arr;
use Hyperf\Utils\Str;
use Hyperf\Validation\Rules\Exists;
use Hyperf\Validation\Rules\Unique;
use Hyperf\Validation\ValidationData;
use InvalidArgumentException;
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use SplFileInfo;
use Throwable;
trait ValidatesAttributes
@ -38,9 +38,7 @@ trait ValidatesAttributes
*
* This validation rule implies the attribute is "required".
*
* @param string $attribute
* @param mixed $value
* @return bool
*/
public function validateAccepted(string $attribute, $value): bool
{
@ -52,9 +50,7 @@ trait ValidatesAttributes
/**
* Validate that an attribute is an active URL.
*
* @param string $attribute
* @param mixed $value
* @return bool
*/
public function validateActiveUrl(string $attribute, $value): bool
{
@ -77,8 +73,6 @@ trait ValidatesAttributes
* "Break" on first validation fail.
*
* Always returns true, just lets us put "bail" in rules.
*
* @return bool
*/
public function validateBail(): bool
{
@ -88,10 +82,7 @@ trait ValidatesAttributes
/**
* Validate the date is before a given date.
*
* @param string $attribute
* @param mixed $value
* @param array $parameters
* @return bool
*/
public function validateBefore(string $attribute, $value, array $parameters): bool
{
@ -103,10 +94,7 @@ trait ValidatesAttributes
/**
* Validate the date is before or equal a given date.
*
* @param string $attribute
* @param mixed $value
* @param array $parameters
* @return bool
*/
public function validateBeforeOrEqual(string $attribute, $value, array $parameters): bool
{
@ -118,10 +106,7 @@ trait ValidatesAttributes
/**
* Validate the date is after a given date.
*
* @param string $attribute
* @param mixed $value
* @param array $parameters
* @return bool
*/
public function validateAfter(string $attribute, $value, array $parameters): bool
{
@ -133,10 +118,7 @@ trait ValidatesAttributes
/**
* Validate the date is equal or after a given date.
*
* @param string $attribute
* @param mixed $value
* @param array $parameters
* @return bool
*/
public function validateAfterOrEqual(string $attribute, $value, array $parameters): bool
{
@ -148,9 +130,7 @@ trait ValidatesAttributes
/**
* Validate that an attribute contains only alphabetic characters.
*
* @param string $attribute
* @param mixed $value
* @return bool
*/
public function validateAlpha(string $attribute, $value): bool
{
@ -160,9 +140,7 @@ trait ValidatesAttributes
/**
* Validate that an attribute contains only alpha-numeric characters, dashes, and underscores.
*
* @param string $attribute
* @param mixed $value
* @return bool
*/
public function validateAlphaDash(string $attribute, $value): bool
{
@ -176,9 +154,7 @@ trait ValidatesAttributes
/**
* Validate that an attribute contains only alpha-numeric characters.
*
* @param string $attribute
* @param mixed $value
* @return bool
*/
public function validateAlphaNum(string $attribute, $value): bool
{
@ -192,9 +168,7 @@ trait ValidatesAttributes
/**
* Validate that an attribute is an array.
*
* @param string $attribute
* @param mixed $value
* @return bool
*/
public function validateArray(string $attribute, $value): bool
{
@ -204,10 +178,7 @@ trait ValidatesAttributes
/**
* Validate the size of an attribute is between a set of values.
*
* @param string $attribute
* @param mixed $value
* @param array $parameters
* @return bool
*/
public function validateBetween(string $attribute, $value, array $parameters): bool
{
@ -221,9 +192,7 @@ trait ValidatesAttributes
/**
* Validate that an attribute is a boolean.
*
* @param string $attribute
* @param mixed $value
* @return bool
*/
public function validateBoolean(string $attribute, $value): bool
{
@ -235,9 +204,7 @@ trait ValidatesAttributes
/**
* Validate that an attribute has a matching confirmation.
*
* @param string $attribute
* @param mixed $value
* @return bool
*/
public function validateConfirmed(string $attribute, $value): bool
{
@ -247,9 +214,7 @@ trait ValidatesAttributes
/**
* Validate that an attribute is a valid date.
*
* @param string $attribute
* @param mixed $value
* @return bool
*/
public function validateDate(string $attribute, $value): bool
{
@ -276,10 +241,7 @@ trait ValidatesAttributes
/**
* Validate that an attribute matches a date format.
*
* @param string $attribute
* @param mixed $value
* @param array $parameters
* @return bool
*/
public function validateDateFormat(string $attribute, $value, array $parameters): bool
{
@ -299,10 +261,7 @@ trait ValidatesAttributes
/**
* Validate that an attribute is equal to another date.
*
* @param string $attribute
* @param mixed $value
* @param array $parameters
* @return bool
*/
public function validateDateEquals(string $attribute, $value, array $parameters): bool
{
@ -314,10 +273,7 @@ trait ValidatesAttributes
/**
* Validate that an attribute is different from another attribute.
*
* @param string $attribute
* @param mixed $value
* @param array $parameters
* @return bool
*/
public function validateDifferent(string $attribute, $value, array $parameters): bool
{
@ -341,10 +297,7 @@ trait ValidatesAttributes
/**
* Validate that an attribute has a given number of digits.
*
* @param string $attribute
* @param mixed $value
* @param array $parameters
* @return bool
*/
public function validateDigits(string $attribute, $value, array $parameters): bool
{
@ -357,10 +310,7 @@ trait ValidatesAttributes
/**
* Validate that an attribute is between a given number of digits.
*
* @param string $attribute
* @param mixed $value
* @param array $parameters
* @return bool
*/
public function validateDigitsBetween(string $attribute, $value, array $parameters): bool
{
@ -375,17 +325,10 @@ trait ValidatesAttributes
/**
* Validate the dimensions of an image matches the given values.
*
* @param string $attribute
* @param mixed $value
* @param array $parameters
* @return bool
*/
public function validateDimensions(string $attribute, $value, array $parameters): bool
{
if ($this->isValidFileInstance($value) && $value->getClientMimeType() === 'image/svg+xml') {
return true;
}
if (! $this->isValidFileInstance($value) || ! $sizeDetails = @getimagesize($value->getRealPath())) {
return false;
}
@ -407,10 +350,7 @@ trait ValidatesAttributes
/**
* Validate an attribute is unique among other values.
*
* @param string $attribute
* @param mixed $value
* @param array $parameters
* @return bool
*/
public function validateDistinct(string $attribute, $value, array $parameters): bool
{
@ -426,9 +366,7 @@ trait ValidatesAttributes
/**
* Validate that an attribute is a valid e-mail address.
*
* @param string $attribute
* @param mixed $value
* @return bool
*/
public function validateEmail(string $attribute, $value): bool
{
@ -442,10 +380,7 @@ trait ValidatesAttributes
/**
* Validate the existence of an attribute value in a database table.
*
* @param string $attribute
* @param mixed $value
* @param array $parameters
* @return bool
*/
public function validateExists(string $attribute, $value, array $parameters): bool
{
@ -466,7 +401,7 @@ trait ValidatesAttributes
$column,
$value,
$parameters
) >= $expected;
) >= $expected;
}
/**
@ -474,10 +409,7 @@ trait ValidatesAttributes
*
* If a database column is not specified, the attribute will be used.
*
* @param string $attribute
* @param mixed $value
* @param array $parameters
* @return bool
*/
public function validateUnique(string $attribute, $value, array $parameters): bool
{
@ -518,28 +450,21 @@ trait ValidatesAttributes
$id,
$idColumn,
$extra
) == 0;
) == 0;
}
/**
* Parse the connection / table for the unique / exists rules.
*
* @param string $table
* @return array
*/
public function parseTable($table): array
public function parseTable(string $table): array
{
return Str::contains($table, '.') ? explode('.', $table, 2) : [null, $table];
}
/**
* Get the column name for an exists / unique query.
*
* @param array $parameters
* @param string $attribute
* @return bool
*/
public function getQueryColumn($parameters, $attribute): string
public function getQueryColumn(array $parameters, string $attribute): string
{
return isset($parameters[1]) && $parameters[1] !== 'NULL'
? $parameters[1] : $this->guessColumnForQuery($attribute);
@ -547,9 +472,6 @@ trait ValidatesAttributes
/**
* Guess the database column from the given attribute name.
*
* @param string $attribute
* @return string
*/
public function guessColumnForQuery(string $attribute): string
{
@ -564,9 +486,7 @@ trait ValidatesAttributes
/**
* Validate the given value is a valid file.
*
* @param string $attribute
* @param mixed $value
* @return bool
*/
public function validateFile(string $attribute, $value): bool
{
@ -576,9 +496,7 @@ trait ValidatesAttributes
/**
* Validate the given attribute is filled if it is present.
*
* @param string $attribute
* @param mixed $value
* @return bool
*/
public function validateFilled(string $attribute, $value): bool
{
@ -592,10 +510,7 @@ trait ValidatesAttributes
/**
* Validate that an attribute is greater than another attribute.
*
* @param string $attribute
* @param mixed $value
* @param array $parameters
* @return bool
*/
public function validateGt(string $attribute, $value, array $parameters): bool
{
@ -619,10 +534,7 @@ trait ValidatesAttributes
/**
* Validate that an attribute is less than another attribute.
*
* @param string $attribute
* @param mixed $value
* @param array $parameters
* @return bool
*/
public function validateLt(string $attribute, $value, array $parameters): bool
{
@ -646,10 +558,7 @@ trait ValidatesAttributes
/**
* Validate that an attribute is greater than or equal another attribute.
*
* @param string $attribute
* @param mixed $value
* @param array $parameters
* @return bool
*/
public function validateGte(string $attribute, $value, array $parameters): bool
{
@ -673,10 +582,7 @@ trait ValidatesAttributes
/**
* Validate that an attribute is less than or equal another attribute.
*
* @param string $attribute
* @param mixed $value
* @param array $parameters
* @return bool
*/
public function validateLte(string $attribute, $value, array $parameters): bool
{
@ -700,9 +606,7 @@ trait ValidatesAttributes
/**
* Validate the MIME type of a file is an image MIME type.
*
* @param string $attribute
* @param mixed $value
* @return bool
*/
public function validateImage(string $attribute, $value): bool
{
@ -712,10 +616,7 @@ trait ValidatesAttributes
/**
* Validate an attribute is contained within a list of values.
*
* @param string $attribute
* @param mixed $value
* @param array $parameters
* @return bool
*/
public function validateIn(string $attribute, $value, array $parameters): bool
{
@ -735,10 +636,7 @@ trait ValidatesAttributes
/**
* Validate that the values of an attribute is in another attribute.
*
* @param string $attribute
* @param mixed $value
* @param array $parameters
* @return bool
*/
public function validateInArray(string $attribute, $value, array $parameters): bool
{
@ -758,9 +656,7 @@ trait ValidatesAttributes
/**
* Validate that an attribute is an integer.
*
* @param string $attribute
* @param mixed $value
* @return bool
*/
public function validateInteger(string $attribute, $value): bool
{
@ -770,9 +666,7 @@ trait ValidatesAttributes
/**
* Validate that an attribute is a valid IP.
*
* @param string $attribute
* @param mixed $value
* @return bool
*/
public function validateIp(string $attribute, $value): bool
{
@ -782,9 +676,7 @@ trait ValidatesAttributes
/**
* Validate that an attribute is a valid IPv4.
*
* @param string $attribute
* @param mixed $value
* @return bool
*/
public function validateIpv4(string $attribute, $value): bool
{
@ -794,9 +686,7 @@ trait ValidatesAttributes
/**
* Validate that an attribute is a valid IPv6.
*
* @param string $attribute
* @param mixed $value
* @return bool
*/
public function validateIpv6(string $attribute, $value): bool
{
@ -806,9 +696,7 @@ trait ValidatesAttributes
/**
* Validate the attribute is a valid JSON string.
*
* @param string $attribute
* @param mixed $value
* @return bool
*/
public function validateJson(string $attribute, $value): bool
{
@ -824,10 +712,7 @@ trait ValidatesAttributes
/**
* Validate the size of an attribute is less than a maximum value.
*
* @param string $attribute
* @param mixed $value
* @param array $parameters
* @return bool
*/
public function validateMax(string $attribute, $value, array $parameters): bool
{
@ -843,10 +728,7 @@ trait ValidatesAttributes
/**
* Validate the guessed extension of a file upload is in a set of file extensions.
*
* @param string $attribute
* @param mixed $value
* @param array $parameters
* @return bool
* @param SplFileInfo $value
*/
public function validateMimes(string $attribute, $value, array $parameters): bool
{
@ -858,16 +740,25 @@ trait ValidatesAttributes
return false;
}
return $value->getPath() !== '' && in_array($value->guessExtension(), $parameters);
if (empty($value->getPath())) {
return false;
}
if (in_array($value->getExtension(), $parameters)) {
return true;
}
if ($value instanceof UploadedFile) {
return in_array($value->getMimeType(), $parameters);
}
return false;
}
/**
* Validate the MIME type of a file upload attribute is in a set of MIME types.
*
* @param string $attribute
* @param mixed $value
* @param array $parameters
* @return bool
* @param SplFileInfo $value
*/
public function validateMimetypes(string $attribute, $value, array $parameters): bool
{
@ -887,10 +778,7 @@ trait ValidatesAttributes
/**
* Validate the size of an attribute is greater than a minimum value.
*
* @param string $attribute
* @param mixed $value
* @param array $parameters
* @return bool
*/
public function validateMin(string $attribute, $value, array $parameters): bool
{
@ -903,8 +791,6 @@ trait ValidatesAttributes
* "Indicate" validation should pass if value is null.
*
* Always returns true, just lets us put "nullable" in rules.
*
* @return bool
*/
public function validateNullable(): bool
{
@ -914,10 +800,7 @@ trait ValidatesAttributes
/**
* Validate an attribute is not contained within a list of values.
*
* @param string $attribute
* @param mixed $value
* @param array $parameters
* @return bool
*/
public function validateNotIn(string $attribute, $value, array $parameters): bool
{
@ -927,9 +810,7 @@ trait ValidatesAttributes
/**
* Validate that an attribute is numeric.
*
* @param string $attribute
* @param mixed $value
* @return bool
*/
public function validateNumeric(string $attribute, $value): bool
{
@ -939,9 +820,7 @@ trait ValidatesAttributes
/**
* Validate that an attribute exists even if not filled.
*
* @param string $attribute
* @param mixed $value
* @return bool
*/
public function validatePresent(string $attribute, $value): bool
{
@ -951,10 +830,7 @@ trait ValidatesAttributes
/**
* Validate that an attribute passes a regular expression check.
*
* @param string $attribute
* @param mixed $value
* @param array $parameters
* @return bool
*/
public function validateRegex(string $attribute, $value, array $parameters): bool
{
@ -970,10 +846,7 @@ trait ValidatesAttributes
/**
* Validate that an attribute does not pass a regular expression check.
*
* @param string $attribute
* @param mixed $value
* @param array $parameters
* @return bool
*/
public function validateNotRegex(string $attribute, $value, array $parameters): bool
{
@ -989,9 +862,7 @@ trait ValidatesAttributes
/**
* Validate that a required attribute exists.
*
* @param string $attribute
* @param mixed $value
* @return bool
*/
public function validateRequired(string $attribute, $value): bool
{
@ -1004,7 +875,7 @@ trait ValidatesAttributes
if ((is_array($value) || $value instanceof Countable) && count($value) < 1) {
return false;
}
if ($value instanceof File) {
if ($value instanceof SplFileInfo) {
return (string) $value->getPath() !== '';
}
@ -1014,10 +885,7 @@ trait ValidatesAttributes
/**
* Validate that an attribute exists when another attribute has a given value.
*
* @param string $attribute
* @param mixed $value
* @param mixed $parameters
* @return bool
*/
public function validateRequiredIf(string $attribute, $value, array $parameters): bool
{
@ -1041,10 +909,7 @@ trait ValidatesAttributes
/**
* Validate that an attribute exists when another attribute does not have a given value.
*
* @param string $attribute
* @param mixed $value
* @param mixed $parameters
* @return bool
*/
public function validateRequiredUnless(string $attribute, $value, array $parameters): bool
{
@ -1064,10 +929,7 @@ trait ValidatesAttributes
/**
* Validate that an attribute exists when any other attribute exists.
*
* @param string $attribute
* @param mixed $value
* @param mixed $parameters
* @return bool
*/
public function validateRequiredWith(string $attribute, $value, array $parameters): bool
{
@ -1081,10 +943,7 @@ trait ValidatesAttributes
/**
* Validate that an attribute exists when all other attributes exists.
*
* @param string $attribute
* @param mixed $value
* @param mixed $parameters
* @return bool
*/
public function validateRequiredWithAll(string $attribute, $value, array $parameters): bool
{
@ -1098,10 +957,7 @@ trait ValidatesAttributes
/**
* Validate that an attribute exists when another attribute does not.
*
* @param string $attribute
* @param mixed $value
* @param mixed $parameters
* @return bool
*/
public function validateRequiredWithout(string $attribute, $value, array $parameters): bool
{
@ -1115,10 +971,7 @@ trait ValidatesAttributes
/**
* Validate that an attribute exists when all other attributes do not.
*
* @param string $attribute
* @param mixed $value
* @param mixed $parameters
* @return bool
*/
public function validateRequiredWithoutAll(string $attribute, $value, array $parameters): bool
{
@ -1132,10 +985,7 @@ trait ValidatesAttributes
/**
* Validate that two attributes match.
*
* @param string $attribute
* @param mixed $value
* @param array $parameters
* @return bool
*/
public function validateSame(string $attribute, $value, array $parameters): bool
{
@ -1149,10 +999,7 @@ trait ValidatesAttributes
/**
* Validate the size of an attribute.
*
* @param string $attribute
* @param mixed $value
* @param array $parameters
* @return bool
*/
public function validateSize(string $attribute, $value, array $parameters): bool
{
@ -1165,8 +1012,6 @@ trait ValidatesAttributes
* "Validate" optional attributes.
*
* Always returns true, just lets us put sometimes in rules.
*
* @return bool
*/
public function validateSometimes()
{
@ -1176,10 +1021,7 @@ trait ValidatesAttributes
/**
* Validate the attribute starts with a given substring.
*
* @param string $attribute
* @param mixed $value
* @param array $parameters
* @return bool
*/
public function validateStartsWith(string $attribute, $value, array $parameters): bool
{
@ -1189,10 +1031,7 @@ trait ValidatesAttributes
/**
* Validate the attribute ends with a given substring.
*
* @param string $attribute
* @param mixed $value
* @param array $parameters
* @return bool
*/
public function validateEndsWith(string $attribute, $value, array $parameters): bool
{
@ -1202,9 +1041,7 @@ trait ValidatesAttributes
/**
* Validate that an attribute is a string.
*
* @param string $attribute
* @param mixed $value
* @return bool
*/
public function validateString(string $attribute, $value): bool
{
@ -1214,9 +1051,7 @@ trait ValidatesAttributes
/**
* Validate that an attribute is a valid timezone.
*
* @param string $attribute
* @param mixed $value
* @return bool
*/
public function validateTimezone(string $attribute, $value): bool
{
@ -1234,9 +1069,7 @@ trait ValidatesAttributes
/**
* Validate that an attribute is a valid URL.
*
* @param string $attribute
* @param mixed $value
* @return bool
*/
public function validateUrl(string $attribute, $value): bool
{
@ -1271,9 +1104,7 @@ trait ValidatesAttributes
/**
* Validate that an attribute is a valid UUID.
*
* @param string $attribute
* @param mixed $value
* @return bool
*/
public function validateUuid(string $attribute, $value): bool
{
@ -1288,7 +1119,6 @@ trait ValidatesAttributes
* Check that the given value is a valid file instance.
*
* @param mixed $value
* @return bool
*/
public function isValidFileInstance($value): bool
{
@ -1296,16 +1126,12 @@ trait ValidatesAttributes
return false;
}
return $value instanceof File;
return $value instanceof SplFileInfo;
}
/**
* Require a certain number of parameters to be present.
*
* @param int $count
* @param array $parameters
* @param string $rule
*
* @throws \InvalidArgumentException
*/
public function requireParameterCount(int $count, array $parameters, string $rule)
@ -1318,11 +1144,7 @@ trait ValidatesAttributes
/**
* Compare a given date against another using an operator.
*
* @param string $attribute
* @param mixed $value
* @param array $parameters
* @param string $operator
* @return bool
*/
protected function compareDates(string $attribute, $value, array $parameters, string $operator): bool
{
@ -1344,7 +1166,6 @@ trait ValidatesAttributes
/**
* Get the date format for an attribute if it has one.
*
* @param string $attribute
* @return null|string
*/
protected function getDateFormat(string $attribute)
@ -1358,7 +1179,7 @@ trait ValidatesAttributes
* Get the date timestamp.
*
* @param mixed $value
* @return int
* @return bool|int
*/
protected function getDateTimestamp($value)
{
@ -1379,12 +1200,6 @@ trait ValidatesAttributes
/**
* Given two date/time strings, check that one is after the other.
*
* @param string $format
* @param string $first
* @param string $second
* @param string $operator
* @return bool
*/
protected function checkDateTimeOrder(string $format, string $first, string $second, string $operator): bool
{
@ -1400,8 +1215,6 @@ trait ValidatesAttributes
/**
* Get a DateTime instance from a string.
*
* @param string $format
* @param string $value
* @return null|\DateTime
*/
protected function getDateTimeWithOptionalFormat(string $format, string $value)
@ -1416,7 +1229,6 @@ trait ValidatesAttributes
/**
* Get a DateTime instance from a string with no format.
*
* @param string $value
* @return null|\DateTime
*/
protected function getDateTime(string $value)
@ -1435,22 +1247,16 @@ trait ValidatesAttributes
* Check if the given value should be adjusted to Carbon::getTestNow().
*
* @param mixed $value
* @return bool
*/
protected function isTestingRelativeDateTime($value): bool
{
return Carbon::hasTestNow() && is_string($value) && (
$value === 'now' || Carbon::hasRelativeKeywords($value)
);
);
}
/**
* Test if the given width and height fail any conditions.
*
* @param array $parameters
* @param int $width
* @param int $height
* @return bool
*/
protected function failsBasicDimensionChecks(array $parameters, int $width, int $height): bool
{
@ -1464,11 +1270,6 @@ trait ValidatesAttributes
/**
* Determine if the given parameters fail a dimension ratio check.
*
* @param array $parameters
* @param int $width
* @param int $height
* @return bool
*/
protected function failsRatioCheck(array $parameters, int $width, int $height): bool
{
@ -1488,9 +1289,6 @@ trait ValidatesAttributes
/**
* Get the values to distinct between.
*
* @param string $attribute
* @return array
*/
protected function getDistinctValues(string $attribute): array
{
@ -1509,9 +1307,6 @@ trait ValidatesAttributes
/**
* Extract the distinct values from the data.
*
* @param string $attribute
* @return array
*/
protected function extractDistinctValues(string $attribute): array
{
@ -1531,11 +1326,7 @@ trait ValidatesAttributes
* Get the number of records that exist in storage.
*
* @param mixed $connection
* @param string $table
* @param string $column
* @param mixed $value
* @param array $parameters
* @return int
*/
protected function getExistCount($connection, string $table, string $column, $value, array $parameters): int
{
@ -1556,9 +1347,6 @@ trait ValidatesAttributes
/**
* Get the excluded ID column and value for the unique rule.
*
* @param array $parameters
* @return array
*/
protected function getUniqueIds(array $parameters): array
{
@ -1592,9 +1380,6 @@ trait ValidatesAttributes
/**
* Get the extra conditions for a unique rule.
*
* @param array $parameters
* @return array
*/
protected function getUniqueExtra(array $parameters): array
{
@ -1607,9 +1392,6 @@ trait ValidatesAttributes
/**
* Get the extra conditions for a unique / exists rule.
*
* @param array $segments
* @return array
*/
protected function getExtraConditions(array $segments): array
{
@ -1627,9 +1409,7 @@ trait ValidatesAttributes
/**
* Check if PHP uploads are explicitly allowed.
*
* @param mixed $value
* @param array $parameters
* @return bool
* @param SplFileInfo $value
*/
protected function shouldBlockPhpUpload($value, array $parameters): bool
{
@ -1641,16 +1421,11 @@ trait ValidatesAttributes
'php', 'php3', 'php4', 'php5', 'phtml',
];
return ($value instanceof UploadedFile)
? in_array(trim(strtolower($value->getClientOriginalExtension())), $phpExtensions)
: in_array(trim(strtolower($value->getExtension())), $phpExtensions);
return in_array(trim(strtolower($value->getExtension())), $phpExtensions);
}
/**
* Convert the given values to boolean if they are string "true" / "false".
*
* @param array $values
* @return array
*/
protected function convertValuesToBoolean(array $values): array
{
@ -1668,9 +1443,6 @@ trait ValidatesAttributes
/**
* Determine if any of the given attributes fail the required test.
*
* @param array $attributes
* @return bool
*/
protected function anyFailingRequired(array $attributes): bool
{
@ -1685,9 +1457,6 @@ trait ValidatesAttributes
/**
* Determine if all of the given attributes fail the required test.
*
* @param array $attributes
* @return bool
*/
protected function allFailingRequired(array $attributes): bool
{
@ -1703,7 +1472,6 @@ trait ValidatesAttributes
/**
* Get the size of an attribute.
*
* @param string $attribute
* @param mixed $value
* @return mixed
*/
@ -1721,7 +1489,7 @@ trait ValidatesAttributes
if (is_array($value)) {
return count($value);
}
if ($value instanceof File) {
if ($value instanceof SplFileInfo) {
return $value->getSize() / 1024;
}
@ -1733,7 +1501,6 @@ trait ValidatesAttributes
*
* @param mixed $first
* @param mixed $second
* @param string $operator
* @throws \InvalidArgumentException
* @return bool
*/
@ -1757,11 +1524,8 @@ trait ValidatesAttributes
/**
* Parse named parameters to $key => $value items.
*
* @param array $parameters
* @return array
*/
protected function parseNamedParameters(array $parameters)
protected function parseNamedParameters(array $parameters): ?array
{
return array_reduce($parameters, function ($result, $item) {
[$key, $value] = array_pad(explode('=', $item, 2), 2, null);
@ -1777,7 +1541,6 @@ trait ValidatesAttributes
*
* @param mixed $first
* @param mixed $second
* @return bool
*/
protected function isSameType($first, $second): bool
{
@ -1786,9 +1549,6 @@ trait ValidatesAttributes
/**
* Adds the existing rule to the numericRules array if the attribute's value is numeric.
*
* @param string $attribute
* @param string $rule
*/
protected function shouldBeNumeric(string $attribute, string $rule)
{

View File

@ -12,15 +12,18 @@ declare(strict_types=1);
namespace Hyperf\Validation;
use Hyperf\Validation\Contracts\Validation\Factory as FactoryInterface;
use Hyperf\Contract\ValidatorInterface;
class ConfigProvider
{
public function __invoke(): array
{
return [
'dependencies' => [
\Hyperf\Validation\Contracts\Validation\Validator::class => \Hyperf\Validation\ValidatorFactory::class,
\Hyperf\Validation\PresenceVerifierInterface::class => \Hyperf\Validation\DatabasePresenceVerifierFactory::class,
\Hyperf\Validation\Contracts\Validation\Factory::class => \Hyperf\Validation\ValidatorFactory::class,
ValidatorInterface::class => ValidatorFactory::class,
PresenceVerifierInterface::class => DatabasePresenceVerifierFactory::class,
FactoryInterface::class => Factory::class,
],
'scan' => [
'paths' => [

View File

@ -17,18 +17,13 @@ interface Factory
/**
* Create a new Validator instance.
*
* @param array $data
* @param array $rules
* @param array $messages
* @param array $customAttributes
* @return \Hyperf\Validation\Contracts\Validation\Validator
* @return \Hyperf\Contract\ValidatorInterface
*/
public function make(array $data, array $rules, array $messages = [], array $customAttributes = []);
/**
* Register a custom validator extension.
*
* @param string $rule
* @param \Closure|string $extension
* @param null|string $message
*/
@ -37,7 +32,6 @@ interface Factory
/**
* Register a custom implicit validator extension.
*
* @param string $rule
* @param \Closure|string $extension
* @param null|string $message
*/
@ -46,7 +40,6 @@ interface Factory
/**
* Register a custom implicit validator message replacer.
*
* @param string $rule
* @param \Closure|string $replacer
*/
public function replacer(string $rule, $replacer);

View File

@ -17,9 +17,7 @@ interface Rule
/**
* Determine if the validation rule passes.
*
* @param string $attribute
* @param mixed $value
* @return bool
*/
public function passes(string $attribute, $value): bool;

View File

@ -45,13 +45,9 @@ class DatabasePresenceVerifier implements PresenceVerifierInterface
/**
* Count the number of objects in a collection having the given value.
*
* @param string $collection
* @param string $column
* @param string $value
* @param null|int $excludeId
* @param null|string $idColumn
* @param array $extra
* @return int
*/
public function getCount(string $collection, string $column, $value, $excludeId = null, $idColumn = null, array $extra = []): int
{
@ -66,12 +62,6 @@ class DatabasePresenceVerifier implements PresenceVerifierInterface
/**
* Count the number of objects in a collection with the given values.
*
* @param string $collection
* @param string $column
* @param array $values
* @param array $extra
* @return int
*/
public function getMultiCount(string $collection, string $column, array $values, array $extra = []): int
{
@ -83,7 +73,6 @@ class DatabasePresenceVerifier implements PresenceVerifierInterface
/**
* Get a query builder for the given table.
*
* @param string $table
* @return \Hyperf\Database\Query\Builder
*/
public function table(string $table)
@ -93,10 +82,8 @@ class DatabasePresenceVerifier implements PresenceVerifierInterface
/**
* Set the connection to be used.
*
* @param string $connection
*/
public function setConnection($connection)
public function setConnection(?string $connection)
{
$this->connection = $connection;
}
@ -105,7 +92,6 @@ class DatabasePresenceVerifier implements PresenceVerifierInterface
* Add the given conditions to the query.
*
* @param \Hyperf\Database\Query\Builder $query
* @param array $conditions
* @return \Hyperf\Database\Query\Builder
*/
protected function addConditions($query, array $conditions)
@ -127,7 +113,6 @@ class DatabasePresenceVerifier implements PresenceVerifierInterface
* Add a "where" clause to the given query.
*
* @param \Hyperf\Database\Query\Builder $query
* @param string $key
* @param string $extraValue
*/
protected function addWhere($query, string $key, $extraValue)

View File

@ -13,17 +13,17 @@ declare(strict_types=1);
namespace Hyperf\Validation;
use Closure;
use Hyperf\Di\Container;
use Hyperf\Translation\Contracts\Translator;
use Hyperf\Contract\TranslatorInterface;
use Hyperf\Utils\Str;
use Hyperf\Validation\Contracts\Validation\Factory as FactoryContract;
use Psr\Container\ContainerInterface;
class Factory implements FactoryContract
{
/**
* The Translator implementation.
*
* @var \Hyperf\Translation\Contracts\Translator
* @var TranslatorInterface
*/
protected $translator;
@ -37,7 +37,7 @@ class Factory implements FactoryContract
/**
* The IoC container instance.
*
* @var Container
* @var ContainerInterface
*/
protected $container;
@ -85,11 +85,8 @@ class Factory implements FactoryContract
/**
* Create a new Validator factory instance.
*
* @param null|\Hyperf\Translation\Contracts\Translator $translator
* @param Container
*/
public function __construct(Translator $translator, Container $container = null)
public function __construct(TranslatorInterface $translator, ContainerInterface $container = null)
{
$this->container = $container;
$this->translator = $translator;
@ -98,10 +95,6 @@ class Factory implements FactoryContract
/**
* Create a new Validator instance.
*
* @param array $data
* @param array $rules
* @param array $messages
* @param array $customAttributes
* @return \Hyperf\Validation\Validator
*/
public function make(array $data, array $rules, array $messages = [], array $customAttributes = [])
@ -135,12 +128,7 @@ class Factory implements FactoryContract
/**
* Validate the given data against the provided rules.
*
* @param array $data
* @param array $rules
* @param array $messages
* @param array $customAttributes
* @throws \Hyperf\Validation\ValidationException
* @return array
*/
public function validate(array $data, array $rules, array $messages = [], array $customAttributes = []): array
{
@ -150,7 +138,6 @@ class Factory implements FactoryContract
/**
* Register a custom validator extension.
*
* @param string $rule
* @param \Closure|string $extension
* @param null|string $message
*/
@ -166,7 +153,6 @@ class Factory implements FactoryContract
/**
* Register a custom implicit validator extension.
*
* @param string $rule
* @param \Closure|string $extension
* @param null|string $message
*/
@ -182,7 +168,6 @@ class Factory implements FactoryContract
/**
* Register a custom dependent validator extension.
*
* @param string $rule
* @param \Closure|string $extension
* @param null|string $message
*/
@ -198,7 +183,6 @@ class Factory implements FactoryContract
/**
* Register a custom validator message replacer.
*
* @param string $rule
* @param \Closure|string $replacer
*/
public function replacer(string $rule, $replacer)
@ -219,7 +203,7 @@ class Factory implements FactoryContract
/**
* Get the Translator implementation.
*
* @return \Hyperf\Translation\Contracts\Translator
* @return TranslatorInterface
*/
public function getTranslator()
{
@ -238,8 +222,6 @@ class Factory implements FactoryContract
/**
* Set the Presence Verifier implementation.
*
* @param \Hyperf\Validation\PresenceVerifierInterface $presenceVerifier
*/
public function setPresenceVerifier(PresenceVerifierInterface $presenceVerifier)
{
@ -249,10 +231,6 @@ class Factory implements FactoryContract
/**
* Resolve a new Validator instance.
*
* @param array $data
* @param array $rules
* @param array $messages
* @param array $customAttributes
* @return \Hyperf\Validation\Validator
*/
protected function resolve(array $data, array $rules, array $messages, array $customAttributes)
@ -266,8 +244,6 @@ class Factory implements FactoryContract
/**
* Add the extensions to a validator instance.
*
* @param \Hyperf\Validation\Validator $validator
*/
protected function addExtensions(Validator $validator)
{

View File

@ -17,24 +17,13 @@ interface PresenceVerifierInterface
/**
* Count the number of objects in a collection having the given value.
*
* @param string $collection
* @param string $column
* @param string $value
* @param null|int $excludeId
* @param null|string $idColumn
* @param array $extra
* @return int
*/
public function getCount(string $collection, string $column, string $value, $excludeId = null, $idColumn = null, array $extra = []): int;
/**
* Count the number of objects in a collection with the given values.
*
* @param string $collection
* @param string $column
* @param array $values
* @param array $extra
* @return int
*/
public function getMultiCount(string $collection, string $column, array $values, array $extra = []): int;
}

View File

@ -13,14 +13,14 @@ declare(strict_types=1);
namespace Hyperf\Validation\Request;
use Hyperf\HttpServer\Request;
use Hyperf\Validation\Contracts\Validation\Factory;
use Hyperf\Utils\Context;
use Hyperf\Validation\Contracts\Validation\Factory as ValidationFactory;
use Hyperf\Validation\Contracts\Validation\ValidatesWhenResolved;
use Hyperf\Validation\Contracts\Validation\Validator;
use Hyperf\Contract\ValidatorInterface;
use Hyperf\Validation\ValidatesWhenResolvedTrait;
use Hyperf\Validation\ValidationException;
use Psr\Container\ContainerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Psr\Http\Message\ResponseInterface;
class FormRequest extends Request implements ValidatesWhenResolved
{
@ -33,34 +33,6 @@ class FormRequest extends Request implements ValidatesWhenResolved
*/
protected $container;
// /**
// * The redirector instance.
// *
// * @var \Illuminate\Routing\Redirector
// */
// protected $redirector;
//
// /**
// * The URI to redirect to if validation fails.
// *
// * @var string
// */
// protected $redirect;
//
// /**
// * The route to redirect to if validation fails.
// *
// * @var string
// */
// protected $redirectRoute;
//
// /**
// * The controller action to redirect to if validation fails.
// *
// * @var string
// */
// protected $redirectAction;
/**
* The key to be used for the view error bag.
*
@ -83,58 +55,35 @@ class FormRequest extends Request implements ValidatesWhenResolved
/**
* Get the proper failed validation response for the request.
*
* @param array $errors
* @return \Symfony\Component\HttpFoundation\Response
* @return ResponseInterface
*/
public function response(array $errors)
public function response()
{
// if ($this->expectsJson()) {
// return new JsonResponse($errors, 422);
// }
/** @var ResponseInterface $response */
$response = Context::get(ResponseInterface::class);
// return $this->redirector->to($this->getRedirectUrl())
// ->withInput($this->except($this->dontFlash))
// ->withErrors($errors, $this->errorBag);
return new JsonResponse($errors, 422);
return $response->withStatus(422);
}
/**
* Get custom messages for validator errors.
*
* @return array
*/
public function messages()
public function messages(): array
{
return [];
}
/**
* Get custom attributes for validator errors.
*
* @return array
*/
public function attributes()
public function attributes(): array
{
return [];
}
// /**
// * Set the Redirector instance.
// *
// * @param \Illuminate\Routing\Redirector $redirector
// * @return $this
// */
// public function setRedirector(Redirector $redirector)
// {
// $this->redirector = $redirector;
//
// return $this;
// }
/**
* Set the container implementation.
*
* @param ContainerInterface $container
* @return $this
*/
public function setContainer(ContainerInterface $container)
@ -147,7 +96,7 @@ class FormRequest extends Request implements ValidatesWhenResolved
/**
* Get the validator instance for the request.
*
* @return \Hyperf\Validation\Contracts\Validation\Validator
* @return ValidatorInterface
*/
protected function getValidatorInstance()
{
@ -169,8 +118,7 @@ class FormRequest extends Request implements ValidatesWhenResolved
/**
* Create the default validator instance.
*
* @param Factory $factory
* @return \Hyperf\Validation\Contracts\Validation\Validator
* @return ValidatorInterface
*/
protected function createDefaultValidator(ValidationFactory $factory)
{
@ -195,48 +143,21 @@ class FormRequest extends Request implements ValidatesWhenResolved
/**
* Handle a failed validation attempt.
*
* @param Validator $validator
*
* @throws ValidationException
*/
protected function failedValidation(Validator $validator)
protected function failedValidation(ValidatorInterface $validator)
{
throw new ValidationException($validator, $this->response(
$this->formatErrors($validator)
));
throw new ValidationException($validator, $this->response());
}
/**
* Format the errors from the given Validator instance.
*
* @param Validator $validator
* @return array
*/
protected function formatErrors(Validator $validator)
protected function formatErrors(ValidatorInterface $validator): array
{
return $validator->getMessageBag()->toArray();
}
// /**
// * Get the URL to redirect to on a validation error.
// *
// * @return string
// */
// protected function getRedirectUrl()
// {
// $url = $this->redirector->getUrlGenerator();
//
// if ($this->redirect) {
// return $url->to($this->redirect);
// } elseif ($this->redirectRoute) {
// return $url->route($this->redirectRoute);
// } elseif ($this->redirectAction) {
// return $url->action($this->redirectAction);
// }
//
// return $url->previous();
// }
/**
* Determine if the request passes the authorization check.
*
@ -253,8 +174,6 @@ class FormRequest extends Request implements ValidatesWhenResolved
/**
* Handle a failed authorization attempt.
*
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
protected function failedAuthorization()
{

View File

@ -22,7 +22,6 @@ class Rule
/**
* Get a dimensions constraint builder instance.
*
* @param array $constraints
* @return \Hyperf\Validation\Rules\Dimensions
*/
public static function dimensions(array $constraints = [])
@ -33,8 +32,6 @@ class Rule
/**
* Get a exists constraint builder instance.
*
* @param string $table
* @param string $column
* @return \Hyperf\Validation\Rules\Exists
*/
public static function exists(string $table, string $column = 'NULL')

View File

@ -46,9 +46,6 @@ trait DatabaseRule
/**
* Create a new rule instance.
*
* @param string $table
* @param string $column
*/
public function __construct(string $table, string $column = 'NULL')
{
@ -81,7 +78,6 @@ trait DatabaseRule
/**
* Set a "where not" constraint on the query.
*
* @param string $column
* @param array|string $value
* @return $this
*/
@ -97,7 +93,6 @@ trait DatabaseRule
/**
* Set a "where null" constraint on the query.
*
* @param string $column
* @return $this
*/
public function whereNull(string $column)
@ -108,7 +103,6 @@ trait DatabaseRule
/**
* Set a "where not null" constraint on the query.
*
* @param string $column
* @return $this
*/
public function whereNotNull(string $column)
@ -119,8 +113,6 @@ trait DatabaseRule
/**
* Set a "where in" constraint on the query.
*
* @param string $column
* @param array $values
* @return $this
*/
public function whereIn(string $column, array $values)
@ -133,8 +125,6 @@ trait DatabaseRule
/**
* Set a "where not in" constraint on the query.
*
* @param string $column
* @param array $values
* @return $this
*/
public function whereNotIn(string $column, array $values)
@ -147,7 +137,6 @@ trait DatabaseRule
/**
* Register a custom query callback.
*
* @param \Closure $callback
* @return $this
*/
public function using(Closure $callback)
@ -159,8 +148,6 @@ trait DatabaseRule
/**
* Get the custom query callbacks for the rule.
*
* @return array
*/
public function queryCallbacks(): array
{
@ -169,8 +156,6 @@ trait DatabaseRule
/**
* Format the where clauses.
*
* @return string
*/
protected function formatWheres(): string
{

View File

@ -23,8 +23,6 @@ class Dimensions
/**
* Create a new dimensions rule instance.
*
* @param array $constraints;
*/
public function __construct(array $constraints = [])
{
@ -33,8 +31,6 @@ class Dimensions
/**
* Convert the rule to a validation string.
*
* @return string
*/
public function __toString(): string
{
@ -50,7 +46,6 @@ class Dimensions
/**
* Set the "width" constraint.
*
* @param int $value
* @return $this
*/
public function width(int $value)
@ -63,7 +58,6 @@ class Dimensions
/**
* Set the "height" constraint.
*
* @param int $value
* @return $this
*/
public function height(int $value)
@ -76,7 +70,6 @@ class Dimensions
/**
* Set the "min width" constraint.
*
* @param int $value
* @return $this
*/
public function minWidth(int $value)
@ -89,7 +82,6 @@ class Dimensions
/**
* Set the "min height" constraint.
*
* @param int $value
* @return $this
*/
public function minHeight(int $value)
@ -102,7 +94,6 @@ class Dimensions
/**
* Set the "max width" constraint.
*
* @param int $value
* @return $this
*/
public function maxWidth(int $value)
@ -115,7 +106,6 @@ class Dimensions
/**
* Set the "max height" constraint.
*
* @param int $value
* @return $this
*/
public function maxHeight(int $value)
@ -128,7 +118,6 @@ class Dimensions
/**
* Set the "ratio" constraint.
*
* @param float $value
* @return $this
*/
public function ratio(float $value)

View File

@ -18,8 +18,6 @@ class Exists
/**
* Convert the rule to a validation string.
*
* @return string
*/
public function __toString(): string
{

View File

@ -28,8 +28,6 @@ class In
/**
* Create a new in rule instance.
*
* @param array $values
*/
public function __construct(array $values)
{
@ -39,8 +37,6 @@ class In
/**
* Convert the rule to a validation string.
*
* @return string
*
* @see \Hyperf\Validation\ValidationRuleParser::parseParameters
*/
public function __toString(): string

Some files were not shown because too many files have changed in this diff Show More