Skip to content

Commit

Permalink
PHP 原生枚举深度支持 (#646)
Browse files Browse the repository at this point in the history
* beans 配置注入支持枚举

* InEnum 验证器支持原生枚举

* 修复

* 修复

* 修复一个类有多个Bean名称时,beans 注入的属性值不正确

* 完善测试

* 修复

* 修复
  • Loading branch information
Yurunsoft authored Nov 14, 2023
1 parent 62c9d6f commit ba917f2
Show file tree
Hide file tree
Showing 14 changed files with 397 additions and 50 deletions.
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 @@ class BeanFactory
*/
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 @@ public static function newInstanceNoInit(string $class, ...$args)
}
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 newInstanceNoInit(string $class, ...$args)
*/
public static function newInstanceEx(string $class, array $args = [])
{
$object = self::newInstanceExNoInit($class, $args, $resultArgs);
static::initInstance($object, $resultArgs);
return static::newBeanInstanceEx($class, null, $args);
}

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

return $object;
}
Expand Down Expand Up @@ -137,16 +166,16 @@ public static function newInstanceExNoInit(string $class, array $args, ?array &$
}
}

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

/**
* 初始化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 &call(object $object, string $className, string $method,
/**
* 注入属性.
*/
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 @@ public static function injectProps(object $object, string $className, bool $reIn
{
$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 injectProps(object $object, string $className, bool $reIn
/**
* 获取注入属性的配置们.
*/
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];
}
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];
}
}
if (null === $originBeanName)
{
break;
}
else
{
return $beans[$className];
// 下次循环会根据类名尝试获取注入配置
$beanName = null;
}
}

Expand All @@ -221,9 +277,9 @@ public static function getConfigInjects(string $className): array
*
* 返回:[$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 @@ private static function doAspect(string $className, string $method, string $poin
*
* @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

0 comments on commit ba917f2

Please sign in to comment.