Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PHP 原生枚举深度支持 #646

Merged
merged 8 commits into from
Nov 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,14 @@ parameters:
message: '#(Extending|Creating new|Calling) \S+ is not covered by backward compatibility promise\. The (class|method) might change in a minor PHPStan version\.#'
paths:
- dev/PHPStan/FileFinder.php
- '#Class UnitEnum not found#'
- '#Class BackedEnum not found#'
- '#Call to static method .+\(\) on an unknown class (Backed|Unit)Enum#'
- '#Method Imi\\Util\\EnumUtil::.+\(\) has invalid return type (Backed|Unit)Enum#'
- '#unknown class Imi\\Test\\Component\\Bean\\EnumBean#'
- '#unknown class Imi\\Test\\Component\\Enum\\TestEnumBean#'
- '#unknown class Imi\\Test\\Component\\Enum\\TestEnumBeanBacked#'
- '#Class Imi\\Test\\Component\\Bean\\EnumBean not found#'
- '#Class Imi\\Test\\Component\\Enum\\TestEnumBean not found#'
- '#Class Imi\\Test\\Component\\Enum\\TestEnumBeanBacked not found#'
services:
47 changes: 38 additions & 9 deletions src/Bean/BeanFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,23 @@
*/
public static function newInstance(string $class, ...$args)
{
$object = self::newInstanceNoInit($class, ...$args);
static::initInstance($object, $args);
return static::newBeanInstance($class, null, ...$args);
}

/**
* 实例化.
*
* @template T
*
* @param class-string<T> $class
* @param mixed ...$args
*
* @return T
*/
public static function newBeanInstance(string $class, ?string $beanName = null, ...$args)
{
$object = static::newInstanceNoInit($class, ...$args);
static::initInstance($object, $args, $beanName);

return $object;
}
Expand All @@ -63,7 +78,7 @@
}
else
{
if (self::$enableFileCache)
if (static::$enableFileCache)
{
static::parseEvalName($class, $fileName, $className);
if (is_file($fileName))
Expand Down Expand Up @@ -102,8 +117,22 @@
*/
public static function newInstanceEx(string $class, array $args = [])
{
$object = self::newInstanceExNoInit($class, $args, $resultArgs);
static::initInstance($object, $resultArgs);
return static::newBeanInstanceEx($class, null, $args);

Check warning on line 120 in src/Bean/BeanFactory.php

View check run for this annotation

Codecov / codecov/patch

src/Bean/BeanFactory.php#L120

Added line #L120 was not covered by tests
}

/**
* 增强实例化.
*
* @template T
*
* @param class-string<T> $class
*
* @return T
*/
public static function newBeanInstanceEx(string $class, ?string $beanName = null, array $args = [])

Check warning on line 132 in src/Bean/BeanFactory.php

View check run for this annotation

Codecov / codecov/patch

src/Bean/BeanFactory.php#L132

Added line #L132 was not covered by tests
{
$object = static::newInstanceExNoInit($class, $args, $resultArgs);
static::initInstance($object, $resultArgs, $beanName);

Check warning on line 135 in src/Bean/BeanFactory.php

View check run for this annotation

Codecov / codecov/patch

src/Bean/BeanFactory.php#L134-L135

Added lines #L134 - L135 were not covered by tests

return $object;
}
Expand Down Expand Up @@ -137,16 +166,16 @@
}
}

return self::newInstanceNoInit($class, ...$resultArgs);
return static::newInstanceNoInit($class, ...$resultArgs);

Check warning on line 169 in src/Bean/BeanFactory.php

View check run for this annotation

Codecov / codecov/patch

src/Bean/BeanFactory.php#L169

Added line #L169 was not covered by tests
}

/**
* 初始化Bean对象
*/
public static function initInstance(object $object, array $args = []): void
public static function initInstance(object $object, array $args = [], ?string $beanName = null): void
{
$class = self::getObjectClass($object);
BeanProxy::injectProps($object, $class);
$class = static::getObjectClass($object);
BeanProxy::injectProps($object, $class, false, $beanName);
$ref = ReflectionContainer::getClassReflection($class);
if ($ref->hasMethod('__init'))
{
Expand Down
120 changes: 88 additions & 32 deletions src/Bean/BeanProxy.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use Imi\Aop\JoinPoint;
use Imi\Aop\Model\AopItem;
use Imi\Config;
use Imi\Util\EnumUtil;
use Imi\Util\Imi;

class BeanProxy
Expand Down Expand Up @@ -137,9 +138,9 @@
/**
* 注入属性.
*/
public static function injectProps(object $object, string $className, bool $reInit = false): void
public static function injectProps(object $object, string $className, bool $reInit = false, ?string $beanName = null): void
{
[$injects, $configs] = static::getInjects($className);
[$injects, $configs] = static::getInjects($className, $beanName);
if (!$injects && !$configs)
{
return;
Expand Down Expand Up @@ -171,6 +172,44 @@
{
$propRef = $refClass->getProperty($name);
$propRef->setAccessible(true);
if ($propRef->hasType())
{
$type = $propRef->getType();
foreach ((static function () use ($type) {
if ($type instanceof \ReflectionNamedType)
{
if (is_subclass_of($typeName = $type->getName(), \UnitEnum::class))
{
yield $typeName;
}
}
elseif ($type instanceof \ReflectionUnionType)
{
foreach ($type->getTypes() as $type)
{
if (is_subclass_of($typeName = $type->getName(), \UnitEnum::class))
{
yield $typeName;
}
}
}
})() as $enumType)
{
if (is_subclass_of($enumType, \BackedEnum::class))
{
$case = $enumType::tryFrom($value);
}
else
{
$case = EnumUtil::tryFromName($enumType, $value);
}
if ($case)
{
$value = $case;
break;
}
}
}
$propRef->setValue($object, $value);
}
}
Expand All @@ -179,37 +218,54 @@
/**
* 获取注入属性的配置们.
*/
public static function getConfigInjects(string $className): array
public static function getConfigInjects(string $className, ?string $beanName = null): array
{
// 配置文件注入
$beanData = BeanManager::get($className);
if ($beanData)
$originBeanName = $beanName;
$count = 2;
while ($count--)
{
$beanName = $beanData['beanName'];
}
else
{
$beanName = $className;
}
$beans = Config::get('@currentServer.beans');
if (isset($beans[$beanName]))
{
return $beans[$beanName];
}
elseif ($beanName !== $className && isset($beans[$className]))
{
return $beans[$className];
}
else
{
$beans = Config::get('@app.beans');
if (isset($beans[$beanName]))
// 配置文件注入
if (null === $beanName)
{
$beanData = BeanManager::get($className);
if ($beanData)
{
$beanName = $beanData['beanName'];
}
else
{
$beanName = $className;
}
}
$serverBeans ??= Config::get('@currentServer.beans');
if (isset($serverBeans[$beanName]))
{
return $serverBeans[$beanName];
}
elseif ($beanName !== $className && isset($serverBeans[$className]))
{
return $beans[$beanName];
return $serverBeans[$className];

Check warning on line 247 in src/Bean/BeanProxy.php

View check run for this annotation

Codecov / codecov/patch

src/Bean/BeanProxy.php#L247

Added line #L247 was not covered by tests
}
elseif ($beanName !== $className && isset($beans[$className]))
else
{
$appBeans ??= Config::get('@app.beans');
if (isset($appBeans[$beanName]))
{
return $appBeans[$beanName];
}
elseif ($beanName !== $className && isset($appBeans[$className]))
{
return $appBeans[$className];

Check warning on line 258 in src/Bean/BeanProxy.php

View check run for this annotation

Codecov / codecov/patch

src/Bean/BeanProxy.php#L258

Added line #L258 was not covered by tests
}
}
if (null === $originBeanName)
{
break;
}
else
{
return $beans[$className];
// 下次循环会根据类名尝试获取注入配置
$beanName = null;
}
}

Expand All @@ -221,9 +277,9 @@
*
* 返回:[$annotations, $configs]
*/
public static function getInjects(string $className): array
public static function getInjects(string $className, ?string $beanName = null): array
{
$configs = static::getConfigInjects($className);
$configs = static::getConfigInjects($className, $beanName);
$injects = BeanManager::getPropertyInjects($className);
if ($configs && $injects)
{
Expand Down Expand Up @@ -318,9 +374,9 @@
*
* @return mixed
*/
public static function getInjectValue(string $className, string $propertyName)
public static function getInjectValue(string $className, string $propertyName, ?string $beanName = null)
{
[$annotations, $configs] = static::getInjects($className);
[$annotations, $configs] = static::getInjects($className, $beanName);
if (isset($configs[$propertyName]))
{
return $configs[$propertyName];
Expand Down
2 changes: 1 addition & 1 deletion src/Bean/Container.php
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ private function __newInstance(string $id, array $params, bool $allowStore)
if ($data['recursion'] ?? true)
{
// @phpstan-ignore-next-line
BeanFactory::initInstance($object, $params);
BeanFactory::initInstance($object, $params, $originId);
if ($stored && $object !== $beanObjects[$originId])
{
// 防止类 __init() 方法有协程上下文切换,导致单例被覆盖
Expand Down
4 changes: 3 additions & 1 deletion src/Components/grpc/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
"yurunsoft/yurun-http": "^4.0.0",
"google/protobuf": "^3.10.0"
},
"require-dev": {},
"require-dev": {
"psr/http-message": "~1.0"
},
"autoload": {
"psr-4": {
"Imi\\Grpc\\": "src/",
Expand Down
54 changes: 54 additions & 0 deletions src/Util/EnumUtil.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

declare(strict_types=1);

namespace Imi\Util;

use Imi\Util\Traits\TStaticClass;

if (\PHP_VERSION_ID >= 80100)
{
class EnumUtil
{
use TStaticClass;

public static function fromName(string $enum, string $case): \UnitEnum
{
$result = static::tryFromName($enum, $case);
if ($result)
{
return $result;
}
throw new \ValueError('"' . $case . '" is not a valid name for enum "' . static::class . '"');
}

public static function tryFromName(string $enum, string $case): ?\UnitEnum
{
foreach ($enum::cases() as $c)
{
if ($c->name === $case)
{
return $c;
}
}

return null;
}

/**
* @param mixed $value
*/
public static function in(string $enum, $value): bool
{
foreach ($enum::cases() as $case)
{
if ($case === $value || ($case->value ?? $case->name) === $value)
{
return true;
}
}

return false;
}
}
}
19 changes: 12 additions & 7 deletions src/Validate/ValidatorHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace Imi\Validate;

use Imi\Enum\BaseEnum;
use Imi\Util\EnumUtil;

/**
* 验证器工具类.
Expand Down Expand Up @@ -404,23 +404,28 @@ public static function notIn($value, $list): bool
/**
* 值在枚举值范围内.
*
* @param mixed $value
* @param class-string<BaseEnum> $enumClass
* @param mixed $value
*/
public static function inEnum($value, string $enumClass): bool
{
return \in_array($value, $enumClass::getValues());
if (is_subclass_of($enumClass, \UnitEnum::class))
{
return EnumUtil::in($enumClass, $value);
}
else
{
return \in_array($value, $enumClass::getValues());
}
}

/**
* 值不在枚举值范围内.
*
* @param mixed $value
* @param class-string<BaseEnum> $enumClass
* @param mixed $value
*/
public static function notInEnum($value, string $enumClass): bool
{
return !\in_array($value, $enumClass::getValues());
return !static::inEnum($value, $enumClass);
}

/**
Expand Down
Loading
Loading