Passed
Push — master ( 2d9553...41ca68 )
by Jesse
01:31
created

Extract::properties()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 19
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 10
nc 4
nop 4
dl 0
loc 19
rs 9.9332
c 0
b 0
f 0
1
<?php
2
declare(strict_types=1);
3
4
namespace Stratadox\EntityState;
5
6
use function array_map as extractWith;
7
use function array_merge as these;
8
use function get_class as classOfThe;
9
use function is_iterable as itIsACollection;
10
use function is_object as itIsAnObject;
11
use function sprintf;
12
use Stratadox\EntityState\Internal\Name;
13
use Stratadox\EntityState\Internal\ReflectionProperties;
14
use Stratadox\EntityState\Internal\ShouldStringify;
15
use Stratadox\EntityState\Internal\Unsatisfiable;
16
use Stratadox\EntityState\Internal\Visited;
17
use Stratadox\IdentityMap\MapsObjectsByIdentity as Map;
18
use Stratadox\IdentityMap\NoSuchObject;
19
use Stratadox\Specification\Contract\Satisfiable;
20
21
/**
22
 * Extract the state of the entities.
23
 *
24
 * @author Stratadox
25
 */
26
final class Extract implements ExtractsEntityState
27
{
28
    private $stringifier;
29
30
    private function __construct(Satisfiable $constraint)
31
    {
32
        $this->stringifier = $constraint;
33
    }
34
35
    /**
36
     * Produces a state extractor.
37
     *
38
     * @return ExtractsEntityState
39
     */
40
    public static function state(): ExtractsEntityState
41
    {
42
        return new Extract(Unsatisfiable::constraint());
43
    }
44
45
    /**
46
     * Produces a state extractor that converts certain types to string.
47
     *
48
     * @param mixed ...$classes
49
     * @return ExtractsEntityState
50
     */
51
    public static function stringifying(...$classes): ExtractsEntityState
52
    {
53
        return new Extract(ShouldStringify::these(...$classes));
54
    }
55
56
    /** @inheritdoc */
57
    public function from(Map $map): ListsEntityStates
58
    {
59
        return $this->fromOnly($map, ...$map->objects());
60
    }
61
62
    /** @inheritdoc */
63
    public function fromOnly(Map $map, object ...$objects): ListsEntityStates
64
    {
65
        return EntityStates::list(...extractWith(
66
            function (object $entity) use ($map): RepresentsEntity {
67
                return $this->stateOfThe($entity, $map);
68
            }, $objects
69
        ));
70
    }
71
72
    /** @throws NoSuchObject */
73
    private function stateOfThe(object $entity, Map $map): RepresentsEntity
74
    {
75
        $properties = [];
76
        if (itIsACollection($entity)) {
77
            $count = 0;
78
            foreach ($entity as $key => $item) {
79
                $properties[] = $this->extract(
80
                    Name::fromCollectionEntry($entity, (string) $key),
81
                    $item,
82
                    $map,
83
                    Visited::noneYet()
84
                );
85
                $count++;
86
            }
87
            $properties[] = [PropertyState::with(sprintf(
88
                'count(%s)',
89
                classOfThe($entity)
90
            ), $count)];
91
        }
92
        foreach (ReflectionProperties::ofThe($entity) as $property) {
93
            $properties[] = $this->extract(
94
                Name::fromReflection($property),
95
                $property->getValue($entity),
96
                $map,
97
                Visited::noneYet()
98
            );
99
        }
100
        if (empty($properties)) {
101
            return EntityState::ofThe(
102
                classOfThe($entity),
103
                $map->idOf($entity),
104
                PropertyStates::list()
105
            );
106
        }
107
        return EntityState::ofThe(
108
            classOfThe($entity),
109
            $map->idOf($entity),
110
            PropertyStates::list(...these(...$properties))
111
        );
112
    }
113
114
    /**
115
     * @return RepresentsProperty[]
116
     * @throws NoSuchObject
117
     */
118
    private function extract(
119
        Name $name,
120
        $value,
121
        Map $map,
122
        Visited $visited
123
    ): array {
124
        if ($visited->alreadyThe($value)) {
125
            return [PropertyState::with((string) $name, [$visited->name($value)])];
126
        }
127
        $visited = $visited->the($value, $name);
128
        if (itIsACollection($value)) {
129
            return $this->collectionState($name, $value, $map, $visited);
130
        }
131
        if (itIsAnObject($value)) {
132
            return $this->objectState($name->for($value), $value, $map, $visited);
133
        }
134
        return [PropertyState::with((string) $name, $value)];
135
    }
136
137
    /**
138
     * @return RepresentsProperty[]
139
     * @throws NoSuchObject
140
     */
141
    private function collectionState(
142
        Name $name,
143
        iterable $collection,
144
        Map $identityMap,
145
        Visited $visited
146
    ): array {
147
        $properties = [];
148
        $count = 0;
149
        foreach ($collection as $key => $value) {
150
            $properties[] = $this->extract(
151
                $name->forItem($collection, (string) $key),
152
                $value,
153
                $identityMap,
154
                $visited
155
            );
156
            $count++;
157
        }
158
        return these(
159
            [PropertyState::with((string) $name->toCount($collection), $count)],
160
            ...$properties
161
        );
162
    }
163
164
    /**
165
     * @return RepresentsProperty[]
166
     * @throws NoSuchObject
167
     */
168
    private function objectState(
169
        Name $name,
170
        object $object,
171
        Map $map,
172
        Visited $visited
173
    ): array {
174
        if ($this->stringifier->isSatisfiedBy($object)) {
175
            return [PropertyState::with((string) $name, (string) $object)];
176
        }
177
        if ($map->hasThe($object)) {
178
            return [PropertyState::with((string) $name, $map->idOf($object))];
179
        }
180
        $properties = [];
181
        foreach (ReflectionProperties::ofThe($object) as $property) {
182
            $properties[] = $this->extract(
183
                $name->forNested($property),
184
                $property->getValue($object),
185
                $map,
186
                $visited
187
            );
188
        }
189
        if (empty($properties)) {
190
            return [PropertyState::with((string) $name, null)];
191
        }
192
        return these(...$properties);
193
    }
194
}
195