Skip to content

Commit ba917f2

Browse files
authored
PHP 原生枚举深度支持 (#646)
* beans 配置注入支持枚举 * InEnum 验证器支持原生枚举 * 修复 * 修复 * 修复一个类有多个Bean名称时,beans 注入的属性值不正确 * 完善测试 * 修复 * 修复
1 parent 62c9d6f commit ba917f2

14 files changed

+397
-50
lines changed

phpstan.neon

+10
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,14 @@ parameters:
4949
message: '#(Extending|Creating new|Calling) \S+ is not covered by backward compatibility promise\. The (class|method) might change in a minor PHPStan version\.#'
5050
paths:
5151
- dev/PHPStan/FileFinder.php
52+
- '#Class UnitEnum not found#'
53+
- '#Class BackedEnum not found#'
54+
- '#Call to static method .+\(\) on an unknown class (Backed|Unit)Enum#'
55+
- '#Method Imi\\Util\\EnumUtil::.+\(\) has invalid return type (Backed|Unit)Enum#'
56+
- '#unknown class Imi\\Test\\Component\\Bean\\EnumBean#'
57+
- '#unknown class Imi\\Test\\Component\\Enum\\TestEnumBean#'
58+
- '#unknown class Imi\\Test\\Component\\Enum\\TestEnumBeanBacked#'
59+
- '#Class Imi\\Test\\Component\\Bean\\EnumBean not found#'
60+
- '#Class Imi\\Test\\Component\\Enum\\TestEnumBean not found#'
61+
- '#Class Imi\\Test\\Component\\Enum\\TestEnumBeanBacked not found#'
5262
services:

src/Bean/BeanFactory.php

+38-9
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,23 @@ class BeanFactory
3838
*/
3939
public static function newInstance(string $class, ...$args)
4040
{
41-
$object = self::newInstanceNoInit($class, ...$args);
42-
static::initInstance($object, $args);
41+
return static::newBeanInstance($class, null, ...$args);
42+
}
43+
44+
/**
45+
* 实例化.
46+
*
47+
* @template T
48+
*
49+
* @param class-string<T> $class
50+
* @param mixed ...$args
51+
*
52+
* @return T
53+
*/
54+
public static function newBeanInstance(string $class, ?string $beanName = null, ...$args)
55+
{
56+
$object = static::newInstanceNoInit($class, ...$args);
57+
static::initInstance($object, $args, $beanName);
4358

4459
return $object;
4560
}
@@ -63,7 +78,7 @@ public static function newInstanceNoInit(string $class, ...$args)
6378
}
6479
else
6580
{
66-
if (self::$enableFileCache)
81+
if (static::$enableFileCache)
6782
{
6883
static::parseEvalName($class, $fileName, $className);
6984
if (is_file($fileName))
@@ -102,8 +117,22 @@ public static function newInstanceNoInit(string $class, ...$args)
102117
*/
103118
public static function newInstanceEx(string $class, array $args = [])
104119
{
105-
$object = self::newInstanceExNoInit($class, $args, $resultArgs);
106-
static::initInstance($object, $resultArgs);
120+
return static::newBeanInstanceEx($class, null, $args);
121+
}
122+
123+
/**
124+
* 增强实例化.
125+
*
126+
* @template T
127+
*
128+
* @param class-string<T> $class
129+
*
130+
* @return T
131+
*/
132+
public static function newBeanInstanceEx(string $class, ?string $beanName = null, array $args = [])
133+
{
134+
$object = static::newInstanceExNoInit($class, $args, $resultArgs);
135+
static::initInstance($object, $resultArgs, $beanName);
107136

108137
return $object;
109138
}
@@ -137,16 +166,16 @@ public static function newInstanceExNoInit(string $class, array $args, ?array &$
137166
}
138167
}
139168

140-
return self::newInstanceNoInit($class, ...$resultArgs);
169+
return static::newInstanceNoInit($class, ...$resultArgs);
141170
}
142171

143172
/**
144173
* 初始化Bean对象
145174
*/
146-
public static function initInstance(object $object, array $args = []): void
175+
public static function initInstance(object $object, array $args = [], ?string $beanName = null): void
147176
{
148-
$class = self::getObjectClass($object);
149-
BeanProxy::injectProps($object, $class);
177+
$class = static::getObjectClass($object);
178+
BeanProxy::injectProps($object, $class, false, $beanName);
150179
$ref = ReflectionContainer::getClassReflection($class);
151180
if ($ref->hasMethod('__init'))
152181
{

src/Bean/BeanProxy.php

+88-32
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Imi\Aop\JoinPoint;
1818
use Imi\Aop\Model\AopItem;
1919
use Imi\Config;
20+
use Imi\Util\EnumUtil;
2021
use Imi\Util\Imi;
2122

2223
class BeanProxy
@@ -137,9 +138,9 @@ public static function &call(object $object, string $className, string $method,
137138
/**
138139
* 注入属性.
139140
*/
140-
public static function injectProps(object $object, string $className, bool $reInit = false): void
141+
public static function injectProps(object $object, string $className, bool $reInit = false, ?string $beanName = null): void
141142
{
142-
[$injects, $configs] = static::getInjects($className);
143+
[$injects, $configs] = static::getInjects($className, $beanName);
143144
if (!$injects && !$configs)
144145
{
145146
return;
@@ -171,6 +172,44 @@ public static function injectProps(object $object, string $className, bool $reIn
171172
{
172173
$propRef = $refClass->getProperty($name);
173174
$propRef->setAccessible(true);
175+
if ($propRef->hasType())
176+
{
177+
$type = $propRef->getType();
178+
foreach ((static function () use ($type) {
179+
if ($type instanceof \ReflectionNamedType)
180+
{
181+
if (is_subclass_of($typeName = $type->getName(), \UnitEnum::class))
182+
{
183+
yield $typeName;
184+
}
185+
}
186+
elseif ($type instanceof \ReflectionUnionType)
187+
{
188+
foreach ($type->getTypes() as $type)
189+
{
190+
if (is_subclass_of($typeName = $type->getName(), \UnitEnum::class))
191+
{
192+
yield $typeName;
193+
}
194+
}
195+
}
196+
})() as $enumType)
197+
{
198+
if (is_subclass_of($enumType, \BackedEnum::class))
199+
{
200+
$case = $enumType::tryFrom($value);
201+
}
202+
else
203+
{
204+
$case = EnumUtil::tryFromName($enumType, $value);
205+
}
206+
if ($case)
207+
{
208+
$value = $case;
209+
break;
210+
}
211+
}
212+
}
174213
$propRef->setValue($object, $value);
175214
}
176215
}
@@ -179,37 +218,54 @@ public static function injectProps(object $object, string $className, bool $reIn
179218
/**
180219
* 获取注入属性的配置们.
181220
*/
182-
public static function getConfigInjects(string $className): array
221+
public static function getConfigInjects(string $className, ?string $beanName = null): array
183222
{
184-
// 配置文件注入
185-
$beanData = BeanManager::get($className);
186-
if ($beanData)
223+
$originBeanName = $beanName;
224+
$count = 2;
225+
while ($count--)
187226
{
188-
$beanName = $beanData['beanName'];
189-
}
190-
else
191-
{
192-
$beanName = $className;
193-
}
194-
$beans = Config::get('@currentServer.beans');
195-
if (isset($beans[$beanName]))
196-
{
197-
return $beans[$beanName];
198-
}
199-
elseif ($beanName !== $className && isset($beans[$className]))
200-
{
201-
return $beans[$className];
202-
}
203-
else
204-
{
205-
$beans = Config::get('@app.beans');
206-
if (isset($beans[$beanName]))
227+
// 配置文件注入
228+
if (null === $beanName)
229+
{
230+
$beanData = BeanManager::get($className);
231+
if ($beanData)
232+
{
233+
$beanName = $beanData['beanName'];
234+
}
235+
else
236+
{
237+
$beanName = $className;
238+
}
239+
}
240+
$serverBeans ??= Config::get('@currentServer.beans');
241+
if (isset($serverBeans[$beanName]))
242+
{
243+
return $serverBeans[$beanName];
244+
}
245+
elseif ($beanName !== $className && isset($serverBeans[$className]))
207246
{
208-
return $beans[$beanName];
247+
return $serverBeans[$className];
209248
}
210-
elseif ($beanName !== $className && isset($beans[$className]))
249+
else
250+
{
251+
$appBeans ??= Config::get('@app.beans');
252+
if (isset($appBeans[$beanName]))
253+
{
254+
return $appBeans[$beanName];
255+
}
256+
elseif ($beanName !== $className && isset($appBeans[$className]))
257+
{
258+
return $appBeans[$className];
259+
}
260+
}
261+
if (null === $originBeanName)
262+
{
263+
break;
264+
}
265+
else
211266
{
212-
return $beans[$className];
267+
// 下次循环会根据类名尝试获取注入配置
268+
$beanName = null;
213269
}
214270
}
215271

@@ -221,9 +277,9 @@ public static function getConfigInjects(string $className): array
221277
*
222278
* 返回:[$annotations, $configs]
223279
*/
224-
public static function getInjects(string $className): array
280+
public static function getInjects(string $className, ?string $beanName = null): array
225281
{
226-
$configs = static::getConfigInjects($className);
282+
$configs = static::getConfigInjects($className, $beanName);
227283
$injects = BeanManager::getPropertyInjects($className);
228284
if ($configs && $injects)
229285
{
@@ -318,9 +374,9 @@ private static function doAspect(string $className, string $method, string $poin
318374
*
319375
* @return mixed
320376
*/
321-
public static function getInjectValue(string $className, string $propertyName)
377+
public static function getInjectValue(string $className, string $propertyName, ?string $beanName = null)
322378
{
323-
[$annotations, $configs] = static::getInjects($className);
379+
[$annotations, $configs] = static::getInjects($className, $beanName);
324380
if (isset($configs[$propertyName]))
325381
{
326382
return $configs[$propertyName];

src/Bean/Container.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ private function __newInstance(string $id, array $params, bool $allowStore)
171171
if ($data['recursion'] ?? true)
172172
{
173173
// @phpstan-ignore-next-line
174-
BeanFactory::initInstance($object, $params);
174+
BeanFactory::initInstance($object, $params, $originId);
175175
if ($stored && $object !== $beanObjects[$originId])
176176
{
177177
// 防止类 __init() 方法有协程上下文切换,导致单例被覆盖

src/Components/grpc/composer.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
"yurunsoft/yurun-http": "^4.0.0",
99
"google/protobuf": "^3.10.0"
1010
},
11-
"require-dev": {},
11+
"require-dev": {
12+
"psr/http-message": "~1.0"
13+
},
1214
"autoload": {
1315
"psr-4": {
1416
"Imi\\Grpc\\": "src/",

src/Util/EnumUtil.php

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Imi\Util;
6+
7+
use Imi\Util\Traits\TStaticClass;
8+
9+
if (\PHP_VERSION_ID >= 80100)
10+
{
11+
class EnumUtil
12+
{
13+
use TStaticClass;
14+
15+
public static function fromName(string $enum, string $case): \UnitEnum
16+
{
17+
$result = static::tryFromName($enum, $case);
18+
if ($result)
19+
{
20+
return $result;
21+
}
22+
throw new \ValueError('"' . $case . '" is not a valid name for enum "' . static::class . '"');
23+
}
24+
25+
public static function tryFromName(string $enum, string $case): ?\UnitEnum
26+
{
27+
foreach ($enum::cases() as $c)
28+
{
29+
if ($c->name === $case)
30+
{
31+
return $c;
32+
}
33+
}
34+
35+
return null;
36+
}
37+
38+
/**
39+
* @param mixed $value
40+
*/
41+
public static function in(string $enum, $value): bool
42+
{
43+
foreach ($enum::cases() as $case)
44+
{
45+
if ($case === $value || ($case->value ?? $case->name) === $value)
46+
{
47+
return true;
48+
}
49+
}
50+
51+
return false;
52+
}
53+
}
54+
}

src/Validate/ValidatorHelper.php

+12-7
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
namespace Imi\Validate;
66

7-
use Imi\Enum\BaseEnum;
7+
use Imi\Util\EnumUtil;
88

99
/**
1010
* 验证器工具类.
@@ -404,23 +404,28 @@ public static function notIn($value, $list): bool
404404
/**
405405
* 值在枚举值范围内.
406406
*
407-
* @param mixed $value
408-
* @param class-string<BaseEnum> $enumClass
407+
* @param mixed $value
409408
*/
410409
public static function inEnum($value, string $enumClass): bool
411410
{
412-
return \in_array($value, $enumClass::getValues());
411+
if (is_subclass_of($enumClass, \UnitEnum::class))
412+
{
413+
return EnumUtil::in($enumClass, $value);
414+
}
415+
else
416+
{
417+
return \in_array($value, $enumClass::getValues());
418+
}
413419
}
414420

415421
/**
416422
* 值不在枚举值范围内.
417423
*
418-
* @param mixed $value
419-
* @param class-string<BaseEnum> $enumClass
424+
* @param mixed $value
420425
*/
421426
public static function notInEnum($value, string $enumClass): bool
422427
{
423-
return !\in_array($value, $enumClass::getValues());
428+
return !static::inEnum($value, $enumClass);
424429
}
425430

426431
/**

0 commit comments

Comments
 (0)