Passed
Push — master ( 1d459e...f70c41 )
by Mr
02:10
created

FromToNativeTrait::flatMapTraits()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 1
Metric Value
eloc 5
c 1
b 0
f 1
dl 0
loc 9
rs 10
ccs 6
cts 6
cp 1
cc 2
nc 2
nop 1
crap 2
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
    use InheritanceReader;
17
18
    /** @psalm-suppress MissingParamType */
19 11
    public static function fromNative($state): object
20
    {
21 11
        Assertion::isArray($state, 'This trait only works with array state.');
22
23 7
        $classReflection = new ReflectionClass(static::class);
24 7
        list($valueFactories, $product) = static::construct($classReflection, $state);
25 6
        foreach ($valueFactories as $propertyName => $factory) {
26 4
            if (array_key_exists($propertyName, $state)) {
27 3
                $product->$propertyName = $factory($state[$propertyName]);
28 2
            } elseif (is_a($factory[0], MakeEmptyInterface::class, true)) {
29 2
                $product->$propertyName = ([$factory[0], 'makeEmpty'])();
30
            }
31
        }
32
33 4
        return $product;
34
    }
35
36
    public function toNative(): array
37
    {
38
        $state = [];
39
        $classReflection = new ReflectionClass($this);
40
        foreach (static::getInheritance($classReflection, true) as $currentClass) {
41
            foreach ($currentClass->getProperties() as $property) {
42
                $propertyName = $property->getName();
43
                if ($currentClass->isTrait()) {
44
                    $property = $classReflection->getProperty($propertyName);
45
                }
46
                $property->setAccessible(true);
47
                $value = $property->getValue($this);
48
                if (is_a($value, ToNativeInterface::class)) {
49
                    $state[$propertyName] = $value->toNative();
50
                } else {
51
                    $state[$propertyName] = $value;
52
                }
53
            }
54
        }
55
56
        return $state;
57
    }
58
59 7
    private static function construct(ReflectionClass $classReflection, array $payload): array
60
    {
61 7
        $valueFactories = static::inferValueFactories($classReflection);
62 7
        $constructor = $classReflection->getConstructor();
63 7
        if (is_null($constructor) || $constructor->getNumberOfParameters() === 0) {
64
            /** @psalm-suppress TooFewArguments */
65 4
            return [$valueFactories, new static];
66
        }
67
68 4
        $constructorArgs = [];
69
        /** @var ReflectionParameter $constructorParam */
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
        /** @psalm-suppress TooManyArguments */
89 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

89
        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...
90
    }
91
92 7
    private static function inferValueFactories(ReflectionClass $classReflection): array
93
    {
94 7
        $valueFactories = [];
95
        /** @var ReflectionClass $currentClass */
96 7
        foreach (static::getInheritance($classReflection, true) as $currentClass) {
97 7
            if (!($docComment = $currentClass->getDocComment())) {
98 7
                continue;
99
            }
100 4
            preg_match_all('#@(?:id|rev|map)\(((.+),(.+))\)#', $docComment, $matches);
101
            //@todo don't allow duplicate id/rev
102 4
            foreach ($matches[2] as $index => $propertyName) {
103 4
                $callable = array_map('trim', explode('::', $matches[3][$index]));
104 4
                if (count($callable) === 1 && is_a($callable[0], FromNativeInterface::class, true)) {
105 4
                    $callable[1] = 'fromNative';
106
                }
107 4
                Assertion::isCallable(
108 4
                    $callable,
109 4
                    sprintf("Value factory '%s' is not callable in '%s'.", implode('::', $callable), static::class)
110
                );
111 4
                $valueFactories[$propertyName] = $callable;
112
            }
113
        }
114
115 7
        return $valueFactories;
116
    }
117
}
118