Passed
Push — master ( 4efb65...8ee2ab )
by Mr
01:53
created

FromToNativeTrait::inferValueFactories()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 21
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 4

Importance

Changes 3
Bugs 0 Features 1
Metric Value
eloc 12
c 3
b 0
f 1
dl 0
loc 21
rs 9.8666
ccs 13
cts 13
cp 1
cc 4
nc 4
nop 1
crap 4
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
use ReflectionParameter;
13
14
trait FromToNativeTrait
15
{
16
    /** @psalm-suppress MissingParamType */
17 11
    public static function fromNative($state): object
18
    {
19 11
        Assertion::isArray($state, 'This trait only works with array state.');
20
21 7
        $classReflection = new ReflectionClass(static::class);
22 7
        list($valueFactories, $product) = static::construct($classReflection, $state);
23 6
        foreach ($valueFactories as $propertyName => $factory) {
24 4
            if (array_key_exists($propertyName, $state)) {
25 3
                $product->$propertyName = call_user_func($factory, $state[$propertyName]);
26 2
            } elseif (is_a($factory[0], MakeEmptyInterface::class, true)) {
27 2
                $product->$propertyName = call_user_func([$factory[0], 'makeEmpty']);
28
            }
29
        }
30
31 4
        return $product;
32
    }
33
34
    public function toNative(): array
35
    {
36
        $state = [];
37
        $classReflection = new ReflectionClass($this);
38
        foreach (static::getInheritanceTree($classReflection, true) as $currentClass) {
39
            foreach ($currentClass->getProperties() as $property) {
40
                $propertyName = $property->getName();
41
                if ($currentClass->isTrait()) {
42
                    $property = $classReflection->getProperty($propertyName);
43
                }
44
                $property->setAccessible(true);
45
                $value = $property->getValue($this);
46
                if (is_a($value, ToNativeInterface::class, true)) {
47
                    $state[$propertyName] = call_user_func([$value, 'toNative']);
48
                } else {
49
                    $state[$propertyName] = $value;
50
                }
51
            }
52
        }
53
54
        return $state;
55
    }
56
57 7
    private static function construct(ReflectionClass $classReflection, array $payload): array
58
    {
59 7
        $valueFactories = static::inferValueFactories($classReflection);
60 7
        $constructor = $classReflection->getConstructor();
61 7
        if (is_null($constructor) || $constructor->getNumberOfParameters() === 0) {
62
            /** @psalm-suppress TooFewArguments */
63 4
            return [$valueFactories, new static];
64
        }
65
66 4
        $constructorArgs = [];
67
        /** @var ReflectionParameter $constructorParam */
68 4
        foreach ($constructor->getParameters() as $constructorParam) {
69 4
            $paramName = $constructorParam->getName();
70 4
            if (isset($payload[$paramName])) {
71 3
                if (isset($valueFactories[$paramName])) {
72
                    $constructorArgs[] = call_user_func($valueFactories[$paramName], $payload[$paramName]);
73
                    unset($valueFactories[$paramName]);
74
                } else {
75 3
                    $constructorArgs[] = $payload[$paramName];
76
                }
77 3
            } elseif ($constructorParam->allowsNull()) {
78 3
                $constructorArgs[] = null;
79
            } else {
80
                throw new InvalidArgumentException(
81
                    "Missing required value for key '$paramName' while constructing from native state."
82
                );
83
            }
84
        }
85
86
        /** @psalm-suppress TooManyArguments */
87 4
        return [$valueFactories, new static(...$constructorArgs)];
0 ignored issues
show
Unused Code introduced by
The call to Daikon\Interop\FromToNativeTrait::__construct() has too many arguments starting with $constructorArgs. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

87
        return [$valueFactories, /** @scrutinizer ignore-call */ new static(...$constructorArgs)];

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.

Loading history...
88
    }
89
90 7
    private static function inferValueFactories(ReflectionClass $classReflection): array
91
    {
92 7
        $valueFactories = [];
93
        /** @var ReflectionClass $currentClass */
94 7
        foreach (static::getInheritanceTree($classReflection, true) as $currentClass) {
95 7
            if (!($docComment = $currentClass->getDocComment())) {
96 7
                continue;
97
            }
98 4
            preg_match_all('#@(?:id|rev|map)\(((.+),(.+))\)#', $docComment, $matches);
99
            //@todo don't allow duplicate id/rev
100 4
            foreach ($matches[2] as $index => $propertyName) {
101 4
                $callable = array_map('trim', explode('::', $matches[3][$index]));
102 4
                Assertion::isCallable(
103 4
                    $callable,
104 4
                    sprintf("Value factory '%s' is not callable in '%s'.", implode('::', $callable), static::class)
105
                );
106 4
                $valueFactories[$propertyName] = $callable;
107
            }
108
        }
109
110 7
        return $valueFactories;
111
    }
112
113 7
    private static function getInheritanceTree(ReflectionClass $classReflection, bool $includeTraits = false): array
114
    {
115 7
        $parent = $classReflection;
116 7
        $classes = $includeTraits
117 7
            ? array_merge([$classReflection], static::flatMapTraits($classReflection))
118 7
            : [$classReflection];
119
120 7
        while ($parent = $parent->getParentClass()) {
121
            $classes = $includeTraits
122
                ? array_merge($classes, [$parent], static::flatMapTraits($parent))
123
                : array_merge($classes, [$classReflection]);
124
        }
125
126 7
        return $classes;
127
    }
128
129 7
    private static function flatMapTraits(ReflectionClass $classReflection): array
130
    {
131 7
        $traits = [];
132 7
        $currentTrait = $classReflection;
133 7
        foreach ($currentTrait->getTraits() as $trait) {
134 7
            $traits = array_merge($traits, [$trait], static::flatMapTraits($trait));
135
        }
136
137 7
        return $traits;
138
    }
139
}
140