Passed
Push — master ( 2f6942...d6d802 )
by Chris
28:11
created

CollectionKernel   B

Complexity

Total Complexity 44

Size/Duplication

Total Lines 305
Duplicated Lines 0 %

Test Coverage

Coverage 91.23%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 44
eloc 89
dl 0
loc 305
ccs 104
cts 114
cp 0.9123
rs 8.8798
c 2
b 0
f 0

43 Methods

Rating   Name   Duplication   Size   Complexity  
A hasItems() 0 3 1
A first() 0 3 1
A last() 0 7 1
A getItemsWhere() 0 4 1
A where() 0 3 1
A performQuery() 0 3 1
A matches() 0 3 1
A hasWhere() 0 3 1
A spawnFrom() 0 8 1
A sortCustom() 0 7 1
A diff() 0 3 1
A __construct() 0 34 1
A filter() 0 4 1
A contrast() 0 3 1
A spawnWith() 0 3 1
A sortMapped() 0 5 1
A loop() 0 3 1
A walk() 0 3 1
A toArray() 0 3 1
A contains() 0 3 1
A firstWhere() 0 5 2
A jsonSerialize() 0 3 1
A getIterator() 0 3 1
A sortBy() 0 5 1
A foreach() 0 3 1
A getFusion() 0 3 1
A column() 0 4 1
A map() 0 3 1
A intersect() 0 3 1
A collect() 0 3 1
A merge() 0 3 1
A __serialize() 0 3 1
A remove() 0 3 1
A sortWith() 0 3 1
A values() 0 3 1
A remix() 0 3 1
A getPropertyValue() 0 3 1
A fetch() 0 3 1
A count() 0 3 1
A getBasicQuery() 0 8 1
A insert() 0 3 1
A toJson() 0 3 1
A query() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like CollectionKernel often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use CollectionKernel, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace WebTheory\Collection\Kernel;
4
5
use ArrayIterator;
6
use Closure;
7
use IteratorAggregate;
8
use Traversable;
9
use WebTheory\Collection\Contracts\ArrayDriverInterface;
10
use WebTheory\Collection\Contracts\ArrayFusionInterface;
11
use WebTheory\Collection\Contracts\CollectionComparatorInterface;
12
use WebTheory\Collection\Contracts\CollectionKernelInterface;
13
use WebTheory\Collection\Contracts\CollectionQueryInterface;
14
use WebTheory\Collection\Contracts\CollectionSorterInterface;
15
use WebTheory\Collection\Contracts\JsonSerializerInterface;
16
use WebTheory\Collection\Contracts\LoopInterface;
17
use WebTheory\Collection\Contracts\OperationProviderInterface;
18
use WebTheory\Collection\Contracts\PropertyResolverInterface;
19
use WebTheory\Collection\Enum\Order;
20
use WebTheory\Collection\Fusion\Collection\FusionSelection;
21
use WebTheory\Collection\Fusion\Contrast;
22
use WebTheory\Collection\Fusion\Diff;
23
use WebTheory\Collection\Fusion\Intersection;
24
use WebTheory\Collection\Fusion\Merger;
25
use WebTheory\Collection\Iteration\ForeachLoop;
26
use WebTheory\Collection\Json\BasicJsonSerializer;
27
use WebTheory\Collection\Kernel\Factory\CollectionKernelSubsystemFactory;
28
use WebTheory\Collection\Query\BasicQuery;
29
use WebTheory\Collection\Query\Operation\Operations;
30
use WebTheory\Collection\Sorting\MapBasedSorter;
31
use WebTheory\Collection\Sorting\PropertyBasedSorter;
32
33
class CollectionKernel implements CollectionKernelInterface, IteratorAggregate
34
{
35
    /**
36
     * Array of objects to be operated on.
37
     *
38
     * @var array<int|string,object>
39
     */
40
    protected array $items = [];
41
42
    /**
43
     * Function to create a new instance of the client collection class when
44
     * performing an operation that spawns a new collection. The callback will
45
     * be passed a clone of the kernel with the mutated array.
46
     */
47
    protected Closure $generator;
48
49
    protected ArrayDriverInterface $driver;
50
51
    protected PropertyResolverInterface $propertyResolver;
52
53
    protected CollectionComparatorInterface $aggregateComparator;
54
55
    protected OperationProviderInterface $operationProvider;
56
57
    protected JsonSerializerInterface $jsonSerializer;
58
59
    protected FusionSelection $fusions;
60
61 309
    public function __construct(
62
        array $items,
63
        Closure $generator,
64
        ?string $identifier = null,
65
        array $accessors = [],
66
        bool $mapToIdentifier = false,
67
        ?JsonSerializerInterface $jsonSerializer = null,
68
        ?OperationProviderInterface $operationProvider = null
69
    ) {
70 309
        $this->generator = $generator;
71
72 309
        $this->jsonSerializer = $jsonSerializer ?? new BasicJsonSerializer();
73 309
        $this->operationProvider = $operationProvider ?? new Operations();
74
75 309
        $subsystems = new CollectionKernelSubsystemFactory(
76
            $identifier,
77
            $accessors,
78
            $mapToIdentifier
79
        );
80
81 309
        $this->driver = $subsystems->getArrayDriver();
82 309
        $this->propertyResolver = $subsystems->getPropertyResolver();
83 309
        $this->aggregateComparator = $subsystems->getCollectionComparator();
84
85 309
        $objectComparator = $subsystems->getObjectComparator();
86
87 309
        $this->fusions = new FusionSelection([
88 309
            'contrast' => new Contrast($objectComparator),
89 309
            'diff' => new Diff($objectComparator),
90 309
            'intersect' => new Intersection($objectComparator),
91 309
            'merge' => new Merger($objectComparator),
0 ignored issues
show
Unused Code introduced by
The call to WebTheory\Collection\Fusion\Merger::__construct() has too many arguments starting with $objectComparator. ( Ignorable by Annotation )

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

91
            'merge' => /** @scrutinizer ignore-call */ new Merger($objectComparator),

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...
92
        ]);
93
94 309
        $this->collect($items);
95
    }
96
97 3
    public function __serialize(): array
98
    {
99 3
        return $this->toArray();
100
    }
101
102 309
    public function collect(array $items): void
103
    {
104 309
        array_walk($items, [$this, 'insert']);
105
    }
106
107 309
    public function insert(object $item, $offset = null): bool
108
    {
109 309
        return $this->driver->insert($this->items, $item, $offset);
110
    }
111
112
    public function fetch($item): object
113
    {
114
        return $this->driver->fetch($this->items, $item);
115
    }
116
117 9
    public function remove($item): bool
118
    {
119 9
        return $this->driver->remove($this->items, $item);
120
    }
121
122 24
    public function contains($item): bool
123
    {
124 24
        return $this->driver->contains($this->items, $item);
125
    }
126
127 3
    public function first(): object
128
    {
129 3
        return reset($this->items);
130
    }
131
132 3
    public function last(): object
133
    {
134 3
        $last = end($this->items);
135
136 3
        reset($this->items);
137
138 3
        return $last;
139
    }
140
141 6
    public function hasItems(): bool
142
    {
143 6
        return !empty($this->items);
144
    }
145
146
    public function hasWhere(string $property, string $operator, $value): bool
147
    {
148
        return !empty($this->getItemsWhere($property, $operator, $value));
149
    }
150
151 6
    public function firstWhere(string $property, string $operator, $value): ?object
152
    {
153 6
        $items = $this->getItemsWhere($property, $operator, $value);
154
155 6
        return reset($items) ?: null;
156
    }
157
158 30
    public function query(CollectionQueryInterface $query): object
159
    {
160 30
        return $this->spawnFrom($this->performQuery($query));
161
    }
162
163 18
    public function where(string $property, string $operator, $value): object
164
    {
165 18
        return $this->query($this->getBasicQuery($property, $operator, $value));
166
    }
167
168 12
    public function filter(callable $callback): object
169
    {
170 12
        return $this->spawnFrom(
171 12
            array_values(array_filter($this->items, $callback))
172
        );
173
    }
174
175 6
    public function column(string $property): array
176
    {
177 6
        return $this->map(
178 6
            fn ($item) => $this->getPropertyValue($item, $property)
179
        );
180
    }
181
182 6
    public function matches(array $collection): bool
183
    {
184 6
        return $this->aggregateComparator->matches($this->items, $collection);
185
    }
186
187 69
    public function remix(ArrayFusionInterface $fusion, array ...$collections): object
188
    {
189 69
        return $this->spawnFrom($fusion->remix($this->items, ...$collections));
190
    }
191
192 18
    public function diff(array ...$collections): object
193
    {
194 18
        return $this->remix($this->getFusion('diff'), ...$collections);
195
    }
196
197 21
    public function contrast(array ...$collections): object
198
    {
199 21
        return $this->remix($this->getFusion('contrast'), ...$collections);
200
    }
201
202 21
    public function intersect(array ...$collections): object
203
    {
204 21
        return $this->remix($this->getFusion('intersect'), ...$collections);
205
    }
206
207 9
    public function merge(array ...$collections): object
208
    {
209 9
        return $this->remix($this->getFusion('merge'), ...$collections);
210
    }
211
212 102
    public function sortWith(CollectionSorterInterface $sorter, string $order = Order::Asc): object
213
    {
214 102
        return $this->spawnFrom($sorter->sort($this->items, $order));
215
    }
216
217 51
    public function sortBy(string $property, string $order = Order::Asc): object
218
    {
219 51
        return $this->sortWith(
220 51
            new PropertyBasedSorter($this->propertyResolver, $property),
221
            $order
222
        );
223
    }
224
225 51
    public function sortMapped(array $map, string $property, string $order = Order::Asc): object
226
    {
227 51
        return $this->sortWith(
228 51
            new MapBasedSorter($this->propertyResolver, $property, $map),
229
            $order
230
        );
231
    }
232
233
    public function sortCustom(callable $callback): object
234
    {
235
        $clone = clone $this;
236
237
        usort($clone->items, $callback);
238
239
        return $this->spawnWith($clone);
240
    }
241
242 9
    public function map(callable $callback): array
243
    {
244 9
        return array_map($callback, $this->items);
245
    }
246
247 3
    public function walk(callable $callback): void
248
    {
249 3
        array_walk($this->items, $callback);
250
    }
251
252 3
    public function loop(LoopInterface $loop, callable $callback): void
253
    {
254 3
        $loop->iterate($this->items, $callback);
255
    }
256
257 3
    public function foreach(callable $callback): void
258
    {
259 3
        $this->loop(new ForeachLoop(), $callback);
260
    }
261
262
    public function values(): array
263
    {
264
        return array_values($this->items);
265
    }
266
267 234
    public function toArray(): array
268
    {
269 234
        return $this->items;
270
    }
271
272 3
    public function toJson(): string
273
    {
274 3
        return $this->jsonSerializer->serialize($this->items);
275
    }
276
277 3
    public function count(): int
278
    {
279 3
        return count($this->items);
280
    }
281
282 3
    public function jsonSerialize(): array
283
    {
284 3
        return $this->items;
285
    }
286
287 3
    public function getIterator(): Traversable
288
    {
289 3
        return new ArrayIterator($this->items);
290
    }
291
292 24
    protected function getBasicQuery(string $property, string $operator, $value): CollectionQueryInterface
293
    {
294 24
        return new BasicQuery(
295 24
            $this->propertyResolver,
296
            $property,
297
            $operator,
298
            $value,
299 24
            $this->operationProvider
300
        );
301
    }
302
303 69
    protected function getFusion(string $fusion): ArrayFusionInterface
304
    {
305 69
        return $this->fusions->fetch($fusion);
306
    }
307
308 6
    protected function getPropertyValue(object $item, string $property)
309
    {
310 6
        return $this->propertyResolver->resolveProperty($item, $property);
311
    }
312
313 36
    protected function performQuery(CollectionQueryInterface $query): array
314
    {
315 36
        return $query->query($this->items);
316
    }
317
318 6
    protected function getItemsWhere(string $property, string $operator, $value): array
319
    {
320 6
        return $this->performQuery(
321 6
            $this->getBasicQuery($property, $operator, $value)
322
        );
323
    }
324
325 207
    protected function spawnWith(self $clone): object
326
    {
327 207
        return ($this->generator)($clone);
328
    }
329
330 207
    protected function spawnFrom(array $items): object
331
    {
332 207
        $clone = clone $this;
333
334 207
        $clone->items = [];
335 207
        $clone->collect($items);
336
337 207
        return $this->spawnWith($clone);
338
    }
339
}
340