Passed
Push — master ( bfd014...4ffbdb )
by Mr
06:05
created

ValueObjectCollectionTrait::equals()   A

Complexity

Conditions 5
Paths 3

Size

Total Lines 25
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 5.0113

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
eloc 12
c 1
b 0
f 0
nc 3
nop 1
dl 0
loc 25
ccs 12
cts 13
cp 0.9231
crap 5.0113
rs 9.5555
1
<?php declare(strict_types=1);
2
/**
3
 * This file is part of the daikon-cqrs/value-object 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\ValueObject;
10
11
use Daikon\Interop\Assertion;
12
use Daikon\Interop\InvalidArgumentException;
13
use ReflectionClass;
14
15
trait ValueObjectCollectionTrait
16
{
17 9
    public function __construct(iterable $objects = [])
18
    {
19 9
        $validTypes = array_keys(static::getTypeFactories());
20 9
        $this->init($objects, $validTypes);
0 ignored issues
show
Bug introduced by
It seems like init() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

20
        $this->/** @scrutinizer ignore-call */ 
21
               init($objects, $validTypes);
Loading history...
21 9
    }
22
23
    public static function makeEmpty(): self
24
    {
25
        return new static;
26
    }
27
28
    /** @param static $comparator */
29 4
    public function equals($comparator): bool
30
    {
31 4
        $this->assertInitialized();
0 ignored issues
show
Bug introduced by
It seems like assertInitialized() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

31
        $this->/** @scrutinizer ignore-call */ 
32
               assertInitialized();
Loading history...
32
        /**
33
         * @psalm-suppress RedundantConditionGivenDocblockType
34
         * @psalm-suppress DocblockTypeContradiction
35
         */
36 4
        Assertion::isInstanceOf(
37 4
            $comparator,
38 4
            static::class,
39
            sprintf(
40 4
                "Invalid comparator type '%s' given to ".static::class,
41 4
                is_object($comparator) ? get_class($comparator) : @gettype($comparator)
42
            )
43
        );
44
45
        /** @var ValueObjectInterface $object */
46 4
        foreach ($this as $index => $object) {
47 4
            $comparison = $comparator->get($index, null);
0 ignored issues
show
Bug introduced by
It seems like get() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

47
            /** @scrutinizer ignore-call */ 
48
            $comparison = $comparator->get($index, null);
Loading history...
48 4
            if (!$comparison || !$object->equals($comparison)) {
49 2
                return false;
50
            }
51
        }
52
53 2
        return true;
54
    }
55
56
    /** @param null|iterable $state */
57 1
    public static function fromNative($state): self
58
    {
59 1
        Assertion::nullOrIsTraversable($state, 'State provided to '.static::class.' must be null or iterable.');
60 1
        $typeFactories = static::getTypeFactories();
61
        // Override fromNative() to support multiple types
62 1
        Assertion::count($typeFactories, 1, sprintf("Only 1 @type annotation is supported by '%s'.", static::class));
63
        /** @var array $typeFactory */
64 1
        $typeFactory = current($typeFactories);
65 1
        Assertion::isCallable($typeFactory, sprintf(
66 1
            "@type factory '%s' is not callable in '%s'.",
67 1
            implode('::', $typeFactory),
68 1
            static::class
69
        ));
70
71 1
        $objects = [];
72 1
        if (!is_null($state)) {
73 1
            foreach ($state as $key => $data) {
74 1
                $objects[$key] = $typeFactory($data);
75
            }
76
        }
77
78 1
        return new static($objects);
79
    }
80
81
    /** @return array */
82
    public function toNative()
83
    {
84
        $this->assertInitialized();
85
        $objects = [];
86
        foreach ($this as $key => $object) {
87
            $objects[$key] = $object->toNative();
88
        }
89
        return $objects;
90
    }
91
92 2
    public function __toString(): string
93
    {
94 2
        $this->assertInitialized();
95 2
        $parts = [];
96 2
        foreach ($this as $key => $object) {
97 2
            $parts[] = $key.':'.(string)$object;
98
        }
99 2
        return implode(', ', $parts);
100
    }
101
102 9
    private static function getTypeFactories(): array
103
    {
104 9
        $classReflection = new ReflectionClass(static::class);
105 9
        if (!preg_match_all('#@type\s+(?<type>.+)#', (string)$classReflection->getDocComment(), $matches)) {
106
            throw new InvalidArgumentException(sprintf("Missing @type annotation on '%s'.", static::class));
107
        }
108
109 9
        $callables = [];
110 9
        foreach ($matches['type'] as $type) {
111 9
            $callable = array_map('trim', explode('::', $type));
112 9
            Assertion::keyNotExists(
113 9
                $callables,
114 9
                $callable[0],
115 9
                sprintf("Ambiguous @type annotation for '$callable[0]' in '%s'.", static::class)
116
            );
117 9
            if (!isset($callable[1])) {
118 5
                $callable[1] = 'fromNative';
119
            }
120 9
            $callables[$callable[0]] = $callable;
121
        }
122
123 9
        return $callables;
124
    }
125
}
126