Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
83.33% |
60 / 72 |
|
42.86% |
3 / 7 |
CRAP | |
0.00% |
0 / 1 |
EntityUtils | |
83.33% |
60 / 72 |
|
42.86% |
3 / 7 |
38.04 | |
0.00% |
0 / 1 |
__construct | n/a |
0 / 0 |
n/a |
0 / 0 |
1 | |||||
isEntity | |
66.67% |
4 / 6 |
|
0.00% |
0 / 1 |
4.59 | |||
isNonPolymorphicEntity | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
30 | |||
isPolymorphicEntity | |
66.67% |
4 / 6 |
|
0.00% |
0 / 1 |
4.59 | |||
getDiscriminatorValues | |
100.00% |
19 / 19 |
|
100.00% |
1 / 1 |
7 | |||
getDiscriminatorClasses | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
3 | |||
getContextParameters | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
5 | |||
findClass | |
92.86% |
13 / 14 |
|
0.00% |
0 / 1 |
4.01 |
1 | <?php |
2 | namespace Apie\Core\Utils; |
3 | |
4 | use Apie\Core\Attributes\Context; |
5 | use Apie\Core\Entities\EntityInterface; |
6 | use Apie\Core\Entities\PolymorphicEntityInterface; |
7 | use Apie\Core\Exceptions\IndexNotFoundException; |
8 | use Apie\Core\Lists\ReflectionClassList; |
9 | use Apie\Core\Other\DiscriminatorMapping; |
10 | use ReflectionClass; |
11 | use ReflectionException; |
12 | use ReflectionMethod; |
13 | use ReflectionParameter; |
14 | use ReflectionProperty; |
15 | use ReflectionType; |
16 | |
17 | final class EntityUtils |
18 | { |
19 | /** |
20 | * @codeCoverageIgnore |
21 | */ |
22 | private function __construct() |
23 | { |
24 | } |
25 | |
26 | /** |
27 | * @param string|ReflectionClass<object>|ReflectionProperty|ReflectionType|ReflectionMethod $input |
28 | */ |
29 | public static function isEntity(string|ReflectionClass|ReflectionProperty|ReflectionType|ReflectionMethod $input): bool |
30 | { |
31 | try { |
32 | $class = ConverterUtils::toReflectionClass($input); |
33 | } catch (ReflectionException) { |
34 | return false; |
35 | } |
36 | return $class !== null |
37 | && $class->implementsInterface(EntityInterface::class) |
38 | && !$class->isInterface(); |
39 | } |
40 | |
41 | /** |
42 | * @param string|ReflectionClass<object>|ReflectionProperty|ReflectionType|ReflectionMethod $input |
43 | */ |
44 | public static function isNonPolymorphicEntity(string|ReflectionClass|ReflectionProperty|ReflectionType|ReflectionMethod $input): bool |
45 | { |
46 | try { |
47 | $class = ConverterUtils::toReflectionClass($input); |
48 | } catch (ReflectionException) { |
49 | return false; |
50 | } |
51 | return $class !== null |
52 | && !$class->implementsInterface(PolymorphicEntityInterface::class) |
53 | && $class->implementsInterface(EntityInterface::class) |
54 | && !$class->isInterface(); |
55 | } |
56 | |
57 | /** |
58 | * @param string|ReflectionClass<object>|ReflectionProperty|ReflectionType|ReflectionMethod $input |
59 | */ |
60 | public static function isPolymorphicEntity(string|ReflectionClass|ReflectionProperty|ReflectionType|ReflectionMethod $input): bool |
61 | { |
62 | try { |
63 | $class = ConverterUtils::toReflectionClass($input); |
64 | } catch (ReflectionException) { |
65 | return false; |
66 | } |
67 | return $class !== null |
68 | && $class->implementsInterface(PolymorphicEntityInterface::class) |
69 | && !$class->isInterface(); |
70 | } |
71 | |
72 | /** |
73 | * @param PolymorphicEntityInterface|ReflectionClass<PolymorphicEntityInterface> $entity |
74 | * @param ReflectionClass<PolymorphicEntityInterface>|null $base |
75 | * @return array<string, string> |
76 | */ |
77 | public static function getDiscriminatorValues(PolymorphicEntityInterface|ReflectionClass $entity, ?ReflectionClass $base = null): array |
78 | { |
79 | $currentRefl = $entity instanceof ReflectionClass ? $entity : new ReflectionClass($entity); |
80 | if (!$base) { |
81 | $refl = $currentRefl; |
82 | while ($refl) { |
83 | if ($refl->getMethod('getDiscriminatorMapping')->getDeclaringClass()->name === $refl->name) { |
84 | $base = $refl; |
85 | } |
86 | $refl = $refl->getParentClass(); |
87 | } |
88 | } |
89 | assert($base !== null); |
90 | $entityClass = $currentRefl->name; |
91 | $result = []; |
92 | $current = $base; |
93 | $last = null; |
94 | while ($current->getMethod('getDiscriminatorMapping')->getDeclaringClass()->name !== $last && $current->name !== $entityClass) { |
95 | /** @var DiscriminatorMapping $mapping */ |
96 | $mapping = $current->getMethod('getDiscriminatorMapping')->invoke(null); |
97 | $config = $mapping->getConfigForClass($entity); |
98 | $result[$mapping->getPropertyName()] = $config->getDiscriminator(); |
99 | $last = $current->getMethod('getDiscriminatorMapping')->getDeclaringClass()->name; |
100 | $current = new ReflectionClass($config->getClassName()); |
101 | } |
102 | return $result; |
103 | } |
104 | |
105 | /** |
106 | * @param ReflectionClass<PolymorphicEntityInterface> $base |
107 | */ |
108 | public static function getDiscriminatorClasses(ReflectionClass $base): ReflectionClassList |
109 | { |
110 | $list = []; |
111 | /** @var DiscriminatorMapping $mapping */ |
112 | $mapping = $base->getMethod('getDiscriminatorMapping')->invoke(null); |
113 | foreach ($mapping->getConfigs() as $config) { |
114 | $refl = new ReflectionClass($config->getClassName()); |
115 | if ($refl->isInstantiable()) { |
116 | $list[] = $refl; |
117 | } else { |
118 | $list = [...$list, ...self::getDiscriminatorClasses($refl)]; |
119 | } |
120 | } |
121 | return new ReflectionClassList($list); |
122 | } |
123 | |
124 | /** |
125 | * Returns context related parameters of a method. This depends on the method type. |
126 | * A context related parameter gets his value not from user input, but from a |
127 | * Apie context variable. |
128 | * |
129 | * @return ReflectionParameter[] |
130 | */ |
131 | public static function getContextParameters(ReflectionMethod $method): array |
132 | { |
133 | // getters: all arguments are context related |
134 | if (preg_match('/^(get|is|has).+/i', $method->name)) { |
135 | return $method->getParameters(); |
136 | } |
137 | // setters: all arguments, except the last one |
138 | if (preg_match('/^set.+/i', $method->name)) { |
139 | $parameters = $method->getParameters(); |
140 | array_pop($parameters); |
141 | return $parameters; |
142 | } |
143 | // other methods: constructor, actions. Only with #[Context] attributes. |
144 | $parameters = []; |
145 | foreach ($method->getParameters() as $parameter) { |
146 | $attributes = $parameter->getAttributes(Context::class); |
147 | if (!empty($attributes)) { |
148 | $parameters[] = $parameter; |
149 | } |
150 | } |
151 | return $parameters; |
152 | } |
153 | |
154 | /** |
155 | * Converts discriminator mappings from an array into the linked class. |
156 | * |
157 | * @template T of PolymorphicEntityInterface |
158 | * @param array<string, string> $discriminators |
159 | * @param ReflectionClass<T> $base |
160 | * @return ReflectionClass<T> |
161 | */ |
162 | public static function findClass(array $discriminators, ReflectionClass $base): ReflectionClass |
163 | { |
164 | /** @var DiscriminatorMapping $mapping */ |
165 | $mapping = $base->getMethod('getDiscriminatorMapping')->invoke(null); |
166 | $value = $discriminators[$mapping->getPropertyName()] ?? null; |
167 | if (!isset($value)) { |
168 | throw new IndexNotFoundException($mapping->getPropertyName()); |
169 | } |
170 | $current = new ReflectionClass($mapping->getClassNameFromDiscriminator($value)); |
171 | $last = $base->name; |
172 | while ($current->getMethod('getDiscriminatorMapping')->getDeclaringClass()->name !== $last) { |
173 | $mapping = $current->getMethod('getDiscriminatorMapping')->invoke(null); |
174 | $value = $discriminators[$mapping->getPropertyName()] ?? null; |
175 | if (!isset($value)) { |
176 | throw new IndexNotFoundException($mapping->getPropertyName()); |
177 | } |
178 | $last = $current->getMethod('getDiscriminatorMapping')->getDeclaringClass()->name; |
179 | $current = new ReflectionClass($mapping->getClassNameFromDiscriminator($value)); |
180 | } |
181 | |
182 | return $current; |
183 | } |
184 | } |