Passed
Push — master ( afdec1...60a2b8 )
by Chris
07:28
created

CollectionKernel::objectsMatch()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
dl 0
loc 3
ccs 0
cts 2
cp 0
rs 10
cc 1
nc 1
nop 2
crap 2
1
<?php
2
3
namespace WebTheory\Collection\Kernel;
4
5
use ArrayIterator;
6
use Closure;
7
use IteratorAggregate;
8
use OutOfBoundsException;
9
use Throwable;
10
use Traversable;
11
use WebTheory\Collection\Comparison\PropertyBasedCollectionComparator;
12
use WebTheory\Collection\Comparison\PropertyBasedObjectComparator;
13
use WebTheory\Collection\Comparison\RuntimeIdBasedCollectionComparator;
14
use WebTheory\Collection\Comparison\RuntimeIdBasedObjectComparator;
15
use WebTheory\Collection\Contracts\ArrayDriverInterface;
16
use WebTheory\Collection\Contracts\CollectionComparatorInterface;
17
use WebTheory\Collection\Contracts\CollectionKernelInterface;
18
use WebTheory\Collection\Contracts\CollectionQueryInterface;
19
use WebTheory\Collection\Contracts\CollectionSorterInterface;
20
use WebTheory\Collection\Contracts\JsonSerializerInterface;
21
use WebTheory\Collection\Contracts\LoopInterface;
22
use WebTheory\Collection\Contracts\OperationProviderInterface;
23
use WebTheory\Collection\Contracts\PropertyResolverInterface;
24
use WebTheory\Collection\Driver\AutoKeyedMap;
25
use WebTheory\Collection\Driver\IdentifiableItemList;
26
use WebTheory\Collection\Driver\StandardList;
27
use WebTheory\Collection\Enum\Order;
28
use WebTheory\Collection\Iteration\ForeachLoop;
29
use WebTheory\Collection\Json\BasicJsonSerializer;
30
use WebTheory\Collection\Query\BasicQuery;
31
use WebTheory\Collection\Query\Operation\Operations;
32
use WebTheory\Collection\Resolution\PropertyResolver;
33
use WebTheory\Collection\Sorting\MapBasedSorter;
34
use WebTheory\Collection\Sorting\PropertyBasedSorter;
35
36
class CollectionKernel implements CollectionKernelInterface, IteratorAggregate
37
{
38
    /**
39
     * Array of objects to be operated on.
40
     *
41
     * @var array<int,object>|array<string,object>
42
     */
43
    protected array $items = [];
44
45
    /**
46
     * Function to create a new instance of the interfacing collection.
47
     */
48
    protected Closure $generator;
49
50
    /**
51
     * Whether or not to map the identifier to items in the collection.
52
     */
53
    protected bool $map = false;
54
55
    protected ArrayDriverInterface $driver;
56
57
    protected PropertyResolverInterface $propertyResolver;
58
59
    protected CollectionComparatorInterface $aggregateComparator;
60
61
    protected OperationProviderInterface $operationsProvider;
62
63
    protected JsonSerializerInterface $jsonSerializer;
64
65 219
    public function __construct(
66
        array $items,
67
        Closure $generator,
68
        ?string $identifier = null,
69
        array $accessors = [],
70
        ?bool $map = false,
71
        ?JsonSerializerInterface $jsonSerializer = null,
72
        ?OperationProviderInterface $operations = null
73
    ) {
74 219
        $this->generator = $generator;
75
76 219
        $this->jsonSerializer = $jsonSerializer ?? new BasicJsonSerializer();
77 219
        $this->operationsProvider = $operations ?? new Operations();
78
79 219
        $this->propertyResolver = new PropertyResolver($accessors);
80
81 219
        $this->aggregateComparator = $identifier
82 219
            ? new PropertyBasedCollectionComparator($this->propertyResolver, $identifier)
83 75
            : new RuntimeIdBasedCollectionComparator();
84
85 219
        $objectComparator = $identifier
86 219
            ? new PropertyBasedObjectComparator($this->propertyResolver, $identifier)
87 123
            : new RuntimeIdBasedObjectComparator();
88
89 219
        if ($identifier && $map) {
90 33
            $this->driver = new AutoKeyedMap($identifier, $this->propertyResolver, $objectComparator);
91 219
        } elseif ($identifier) {
92 219
            $this->driver = new IdentifiableItemList($identifier, $this->propertyResolver, $objectComparator);
93
        } else {
94 75
            $this->driver = new StandardList($objectComparator);
95
        }
96
97 219
        $this->collect(...$items);
98
    }
99
100 3
    public function __serialize(): array
101
    {
102 3
        return $this->toArray();
103
    }
104
105 219
    public function collect(object ...$items): void
106
    {
107 219
        array_map([$this, 'add'], $items);
108
    }
109
110 219
    public function add(object $item): bool
111
    {
112 219
        return $this->driver->insert($this->items, $item);
113
    }
114
115 9
    public function remove($item): bool
116
    {
117 9
        return $this->driver->remove($this->items, $item);
118
    }
119
120 18
    public function contains($item): bool
121
    {
122 18
        return $this->driver->contains($this->items, $item);
123
    }
124
125 3
    public function first(): object
126
    {
127 3
        return reset($this->items);
128
    }
129
130 3
    public function last(): object
131
    {
132 3
        $last = end($this->items);
133
134 3
        reset($this->items);
135
136 3
        return $last;
137
    }
138
139 6
    public function hasItems(): bool
140
    {
141 6
        return !empty($this->items);
142
    }
143
144
    public function hasWhere(string $property, string $operator, $value): bool
145
    {
146
        return !empty($this->getItemsWhere($property, $operator, $value));
147
    }
148
149 6
    public function firstWhere(string $property, string $operator, $value): object
150
    {
151 6
        $items = $this->getItemsWhere($property, $operator, $value);
152 6
        $exception = new OutOfBoundsException(
153 6
            "Cannot find item where {$property} is equal to {$value}."
154
        );
155
156 6
        return $this->extractFirstItem($items, $exception);
157
    }
158
159 30
    public function query(CollectionQueryInterface $query): object
160
    {
161 30
        return $this->spawnFrom(...$this->performQuery($query));
162
    }
163
164 18
    public function where(string $property, string $operator, $value): object
165
    {
166 18
        return $this->query($this->getBasicQuery($property, $operator, $value));
167
    }
168
169 12
    public function filter(callable $callback): object
170
    {
171 12
        return $this->spawnFrom(
172 12
            ...array_values(array_filter($this->items, $callback))
173
        );
174
    }
175
176 6
    public function column(string $property): array
177
    {
178 6
        return $this->map(
179 6
            fn ($item) => $this->getPropertyValue($item, $property)
180
        );
181
    }
182
183 6
    public function matches(array $collection): bool
184
    {
185 6
        return $this->aggregateComparator->matches($this->items, $collection);
186
    }
187
188 12
    public function diff(array $collection): object
189
    {
190 12
        return $this->spawnFrom(
191 12
            ...$this->aggregateComparator->diff($this->items, $collection)
192
        );
193
    }
194
195 15
    public function contrast(array $collection): object
196
    {
197 15
        return $this->spawnFrom(
198 15
            ...$this->aggregateComparator->contrast($this->items, $collection)
199
        );
200
    }
201
202 15
    public function intersect(array $collection): object
203
    {
204 15
        return $this->spawnFrom(
205 15
            ...$this->aggregateComparator->intersect($this->items, $collection)
206
        );
207
    }
208
209 3
    public function merge(array ...$collections): object
210
    {
211 3
        return $this->spawnFrom(
212 3
            ...array_merge(array_values($this->items), ...$collections)
213
        );
214
    }
215
216 42
    public function sortWith(CollectionSorterInterface $sorter, string $order = Order::Asc): object
217
    {
218 42
        return $this->spawnFrom(...$sorter->sort($this->items, $order));
219
    }
220
221 21
    public function sortBy(string $property, string $order = Order::Asc): object
222
    {
223 21
        return $this->sortWith(
224 21
            new PropertyBasedSorter($this->propertyResolver, $property),
225
            $order
226
        );
227
    }
228
229 21
    public function sortMapped(array $map, string $property, string $order = Order::Asc): object
230
    {
231 21
        return $this->sortWith(
232 21
            new MapBasedSorter($this->propertyResolver, $property, $map),
233
            $order
234
        );
235
    }
236
237
    public function sortCustom(callable $callback): object
238
    {
239
        $clone = clone $this;
240
241
        usort($clone->items, $callback);
242
243
        return $this->spawnWith($clone);
244
    }
245
246 9
    public function map(callable $callback): array
247
    {
248 9
        return array_map($callback, $this->items);
249
    }
250
251 3
    public function walk(callable $callback): void
252
    {
253 3
        array_walk($this->items, $callback);
254
    }
255
256 3
    public function loop(LoopInterface $loop, callable $callback): void
257
    {
258 3
        $loop->iterate($this->items, $callback);
259
    }
260
261 3
    public function foreach(callable $callback): void
262
    {
263 3
        $this->loop(new ForeachLoop(), $callback);
264
    }
265
266 150
    public function toArray(): array
267
    {
268 150
        return $this->items;
269
    }
270
271 3
    public function toJson(): string
272
    {
273 3
        return $this->jsonSerializer->serialize($this->items);
274
    }
275
276 3
    public function count(): int
277
    {
278 3
        return count($this->items);
279
    }
280
281 3
    public function jsonSerialize(): array
282
    {
283 3
        return $this->items;
284
    }
285
286 3
    public function getIterator(): Traversable
287
    {
288 3
        return new ArrayIterator($this->items);
289
    }
290
291 24
    protected function getBasicQuery(string $property, string $operator, $value): CollectionQueryInterface
292
    {
293 24
        return new BasicQuery(
294 24
            $this->propertyResolver,
295
            $property,
296
            $operator,
297
            $value,
298 24
            $this->operationsProvider
299
        );
300
    }
301
302 36
    protected function performQuery(CollectionQueryInterface $query): array
303
    {
304 36
        return $query->query($this->items);
305
    }
306
307 6
    protected function extractFirstItem(array $items, Throwable $throwable): object
308
    {
309
        try {
310 6
            return $items[0];
311 3
        } catch (Throwable $e) {
312 3
            throw $throwable;
313
        }
314
    }
315
316 6
    protected function getPropertyValue(object $item, string $property)
317
    {
318 6
        return $this->propertyResolver->resolveProperty($item, $property);
319
    }
320
321 6
    protected function getItemsWhere(string $property, string $operator, $value): array
322
    {
323 6
        return $this->performQuery($this->getBasicQuery($property, $operator, $value));
324
    }
325
326 123
    protected function spawnWith(self $clone): object
327
    {
328 123
        return ($this->generator)($clone);
329
    }
330
331 123
    protected function spawnFrom(object ...$items): object
332
    {
333 123
        $clone = clone $this;
334
335 123
        $clone->items = [];
336 123
        $clone->collect(...$items);
337
338 123
        return $this->spawnWith($clone);
339
    }
340
}
341