1 | <?php declare(strict_types=1); |
||
2 | /** |
||
3 | * This file is part of the daikon-cqrs/interop project. |
||
4 | * |
||
5 | * For the full copyright and license information, please view the LICENSE |
||
6 | * file that was distributed with this source code. |
||
7 | */ |
||
8 | |||
9 | namespace Daikon\Interop; |
||
10 | |||
11 | use ReflectionClass; |
||
12 | |||
13 | trait FromToNativeTrait |
||
14 | { |
||
15 | use SupportsAnnotations; |
||
16 | |||
17 | /** |
||
18 | * @psalm-suppress MissingParamType |
||
19 | * @return static |
||
20 | */ |
||
21 | 11 | public static function fromNative($state): self |
|
22 | { |
||
23 | 11 | Assertion::isArray($state, 'This trait only works with array state.'); |
|
24 | |||
25 | 7 | list($valueFactories, $product) = static::construct($state); |
|
26 | 6 | foreach ($valueFactories as $propertyName => $factory) { |
|
27 | 4 | if (array_key_exists($propertyName, $state)) { |
|
28 | 3 | $product->$propertyName = $factory($state[$propertyName]); |
|
29 | 2 | } elseif (is_a($factory[0], MakeEmptyInterface::class, true)) { |
|
30 | 2 | $product->$propertyName = ([$factory[0], 'makeEmpty'])(); |
|
31 | } |
||
32 | } |
||
33 | |||
34 | 4 | return $product; |
|
35 | } |
||
36 | |||
37 | public function toNative(): array |
||
38 | { |
||
39 | $state = []; |
||
40 | $reflectionClass = new ReflectionClass($this); |
||
41 | foreach (static::getInheritance() as $currentClass) { |
||
42 | foreach ($currentClass->getProperties() as $property) { |
||
43 | $propertyName = $property->getName(); |
||
44 | if ($currentClass->isTrait()) { |
||
45 | $property = $reflectionClass->getProperty($propertyName); |
||
46 | } |
||
47 | $property->setAccessible(true); |
||
48 | $value = $property->getValue($this); |
||
49 | if (is_a($value, ToNativeInterface::class)) { |
||
50 | $state[$propertyName] = $value->toNative(); |
||
51 | } else { |
||
52 | $state[$propertyName] = $value; |
||
53 | } |
||
54 | } |
||
55 | } |
||
56 | |||
57 | return $state; |
||
58 | } |
||
59 | |||
60 | 7 | private static function construct(array $payload): array |
|
61 | { |
||
62 | 7 | $valueFactories = static::inferValueFactories(); |
|
63 | 7 | $constructor = (new ReflectionClass(static::class))->getConstructor(); |
|
64 | 7 | if (is_null($constructor) || $constructor->getNumberOfParameters() === 0) { |
|
65 | /** @psalm-suppress UnsafeInstantiation */ |
||
66 | 4 | return [$valueFactories, new static]; |
|
67 | } |
||
68 | |||
69 | 4 | $constructorArgs = []; |
|
70 | 4 | foreach ($constructor->getParameters() as $constructorParam) { |
|
71 | 4 | $paramName = $constructorParam->getName(); |
|
72 | 4 | if (isset($payload[$paramName])) { |
|
73 | 3 | if (isset($valueFactories[$paramName])) { |
|
74 | $constructorArgs[] = $valueFactories[$paramName]($payload[$paramName]); |
||
75 | unset($valueFactories[$paramName]); |
||
76 | } else { |
||
77 | 3 | $constructorArgs[] = $payload[$paramName]; |
|
78 | } |
||
79 | 3 | } elseif ($constructorParam->allowsNull()) { |
|
80 | 3 | $constructorArgs[] = null; |
|
81 | } else { |
||
82 | throw new InvalidArgumentException( |
||
83 | "Missing required value for key '$paramName' while constructing from native state." |
||
84 | ); |
||
85 | } |
||
86 | } |
||
87 | |||
88 | /** |
||
89 | * @psalm-suppress UnsafeInstantiation |
||
90 | * @psalm-suppress TooManyArguments |
||
91 | */ |
||
92 | 4 | return [$valueFactories, new static(...$constructorArgs)]; |
|
0 ignored issues
–
show
|
|||
93 | } |
||
94 | } |
||
95 |
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.
If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.