Completed
Push — master ( ff32ef...16b7ec )
by Lars
01:38
created

AbstractCollection::offsetSet()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 4.8437

Importance

Changes 0
Metric Value
cc 4
nc 3
nop 2
dl 0
loc 16
ccs 5
cts 8
cp 0.625
crap 4.8437
rs 9.7333
c 0
b 0
f 0
1
<?php
2
3
/** @noinspection ClassOverridesFieldOfSuperClassInspection */
4
/** @noinspection PropertyInitializationFlawsInspection */
5
/** @noinspection PhpSuperClassIncompatibleWithInterfaceInspection */
6
7
declare(strict_types=1);
8
9
namespace Arrayy\Collection;
10
11
use Arrayy\Arrayy;
12
use Arrayy\ArrayyIterator;
13
use Arrayy\ArrayyRewindableGenerator;
14
use Arrayy\Type\TypeInterface;
15
use Arrayy\TypeCheck\TypeCheckArray;
16
use Arrayy\TypeCheck\TypeCheckInterface;
17
use Arrayy\TypeCheck\TypeCheckSimple;
18
19
/**
20
 * This class provides a full implementation of `CollectionInterface`, to
21
 * minimize the effort required to implement this interface.
22
 *
23
 * INFO: this collection thingy is inspired by https://github.com/ramsey/collection/
24
 *
25
 * @template   TKey of array-key
26
 * @template   T
27
 * @extends    Arrayy<TKey,T>
28
 * @implements CollectionInterface<TKey,T>
29
 */
30
abstract class AbstractCollection extends Arrayy implements CollectionInterface
31
{
32
    /**
33
     * @var array
34
     * @psalm-var array<T>
35
     */
36
    protected $array = [];
37
38
    /**
39
     * @var ArrayyRewindableGenerator|null
40
     * @psalm-var \Arrayy\ArrayyRewindableGenerator<TKey,T>|null
41
     */
42
    protected $generator;
43
44
    /**
45
     * @var bool
46
     */
47
    protected $checkPropertyTypes = true;
48
49
    /**
50
     * @var bool
51
     */
52
    protected $checkPropertiesMismatch = false;
53
54
    /**
55
     * @var bool
56
     */
57
    protected $checkForMissingPropertiesInConstructor = true;
58
59
    /**
60
     * Constructs a collection object of the specified type, optionally with the
61
     * specified data.
62
     *
63
     * @param mixed  $data
64
     *                                             <p>
65
     *                                             The initial items to store in the collection.
66
     *                                             </p>
67
     * @param string $iteratorClass                optional <p>
68
     *                                             You can overwrite the ArrayyIterator, but mostly you don't
69
     *                                             need this option.
70
     *                                             </p>
71
     * @param bool   $checkPropertiesInConstructor optional <p>
72
     *                                             You need to extend the "Arrayy"-class and you need to set
73
     *                                             the $checkPropertiesMismatchInConstructor class property
74
     *                                             to
75
     *                                             true, otherwise this option didn't not work anyway.
76
     *                                             </p>
77
     *
78
     * @psalm-param array<TKey,T>|\Arrayy\Arrayy<TKey,T>|\Closure():array<TKey,T>|mixed $data
79
     * @psalm-param class-string<\Arrayy\ArrayyIterator> $iteratorClass
80
     */
81 94
    public function __construct(
82
        $data = [],
83
        string $iteratorClass = ArrayyIterator::class,
84
        bool $checkPropertiesInConstructor = true
85
    ) {
86 94
        $type = $this->getType();
87
88 94
        $type = self::convertIntoTypeCheckArray($type);
89
90 94
        $this->properties = $type;
0 ignored issues
show
Documentation Bug introduced by
It seems like $type of type object<Arrayy\TypeCheck\TypeCheckArray> is incompatible with the declared type array of property $properties.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
91
92
        // cast into array, if needed
93
        if (
94 94
            !\is_array($data)
95
            &&
96 94
            !($data instanceof \Traversable)
97
            &&
98 94
            !($data instanceof \Closure)
99
        ) {
100 2
            $data = [$data];
101
        }
102
103 94
        parent::__construct(
104 94
            $data,
105 94
            $iteratorClass,
106 94
            $checkPropertiesInConstructor
107
        );
108 76
    }
109
110
    /**
111
     * Append a (key) + value to the current array.
112
     *
113
     * @param mixed $value
114
     * @param mixed $key
115
     *
116
     * @return $this
117
     *               <p>(Mutable) Return this CollectionInterface object, with the appended values.</p>
118
     *
119
     * @psalm-param T|array<TKey,T>|static<TKey,T> $value
120
     * @psalm-param TKey|null $key
121
     * @psalm-return static<TKey,T>
122
     */
123 6 View Code Duplication
    public function append($value, $key = null): Arrayy
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
124
    {
125
        if (
126 6
            $value instanceof self
127
            &&
128 6
            !$value instanceof TypeInterface
129
        ) {
130 1
            foreach ($value as $valueTmp) {
131 1
                parent::append($valueTmp, $key);
132
            }
133
134 1
            return $this;
135
        }
136
137 5
        $return = parent::append($value, $key);
138 4
        $this->array = $return->array;
139
140 4
        return $this;
141
    }
142
143
    /**
144
     * {@inheritdoc}
145
     */
146 2
    public function offsetSet($offset, $value)
147
    {
148
        if (
149 2
            $value instanceof self
150
            &&
151 2
            !$value instanceof TypeInterface
152
        ) {
153
            foreach ($value as $valueTmp) {
154
                parent::offsetSet($offset, $valueTmp);
155
            }
156
157
            return;
158
        }
159
160 2
        parent::offsetSet($offset, $value);
161 1
    }
162
163
    /**
164
     * Prepend a (key) + value to the current array.
165
     *
166
     * @param mixed $value
167
     * @param mixed $key
168
     *
169
     * @return $this
170
     *               <p>(Mutable) Return this CollectionInterface object, with the prepended value.</p>
171
     *
172
     * @psalm-param T|array<TKey,T>|static<TKey,T> $value
173
     * @psalm-param TKey|null $key
174
     * @psalm-return static<TKey,T>
175
     */
176 3 View Code Duplication
    public function prepend($value, $key = null): Arrayy
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
177
    {
178
        if (
179 3
            $value instanceof self
180
            &&
181 3
            !$value instanceof TypeInterface
182
        ) {
183
            foreach ($value as $valueTmp) {
184
                parent::prepend($valueTmp, $key);
185
            }
186
187
            return $this;
188
        }
189
190 3
        $return = parent::prepend($value, $key);
191 1
        $this->array = $return->array;
192
193 1
        return $this;
194
    }
195
196
    /**
197
     * {@inheritdoc}
198
     */
199 1
    public function column(string $keyOrPropertyOrMethod): array
200
    {
201
        // init
202 1
        $temp = [];
203
204 1
        foreach ($this->getGenerator() as $item) {
205 1
            $temp[] = $this->extractValue($item, $keyOrPropertyOrMethod);
206
        }
207
208 1
        return $temp;
209
    }
210
211
    /**
212
     * {@inheritdoc}
213
     */
214 7
    public function getCollection(): array
215
    {
216 7
        return $this->array;
217
    }
218
219
    /**
220
     * {@inheritdoc}
221
     */
222
    abstract public function getType();
223
224
    /**
225
     * Merge current items and items of given collections into a new one.
226
     *
227
     * @param CollectionInterface|static ...$collections
228
     *                                                   <p>The collections to merge.</p>
229
     *
230
     *@throws \InvalidArgumentException if any of the given collections are not of the same type
231
     *
232
     * @return $this
233
     *
234
     * @psalm-param CollectionInterface<TKey,T> ...$collections
235
     * @psalm-return static<TKey,T>
236
     */
237 1
    public function merge(CollectionInterface ...$collections): self
238
    {
239 1
        foreach ($collections as $collection) {
240 1
            foreach ($collection as $item) {
241 1
                $this->append($item);
242
            }
243
        }
244
245 1
        return $this;
246
    }
247
248
    /**
249
     * Creates an CollectionInterface object.
250
     *
251
     * @param mixed  $data
252
     * @param string $iteratorClass
253
     * @param bool   $checkPropertiesInConstructor
254
     *
255
     * @return static
256
     *                <p>(Immutable) Returns an new instance of the CollectionInterface object.</p>
257
     *
258
     * @template     TKeyCreate as int|string
259
     * @template     TCreate
260
     * @psalm-param  array<TKeyCreate,TCreate> $data
261
     * @psalm-param  class-string<\Arrayy\ArrayyIterator> $iteratorClass
262
     * @psalm-return static<TKeyCreate,TCreate>
263
     * @psalm-mutation-free
264
     */
265 24
    public static function create(
266
        $data = [],
267
        string $iteratorClass = ArrayyIterator::class,
268
        bool $checkPropertiesInConstructor = true
269
    ) {
270 24
        return new static(
271 24
            $data,
272 24
            $iteratorClass,
273 24
            $checkPropertiesInConstructor
274
        );
275
    }
276
277
    /**
278
     * @param string $json
279
     *
280
     * @return static
281
     *                <p>(Immutable) Returns an new instance of the CollectionInterface object.</p>
282
     *
283
     * @psalm-return static<mixed,T>
284
     *
285
     * @psalm-mutation-free
286
     */
287 7
    public static function createFromJsonMapper(string $json)
288
    {
289
        // init
290 7
        $return = static::create();
291 7
        $jsonObject = \json_decode($json, false);
292 7
        $mapper = new \Arrayy\Mapper\Json();
293 View Code Duplication
        $mapper->undefinedPropertyHandler = static function ($object, $key, $jsonValue) use ($return) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
294 1
            if ($return->checkForMissingPropertiesInConstructor) {
295 1
                throw new \TypeError('Property mismatch - input: ' . \print_r(['key' => $key, 'jsonValue' => $jsonValue], true) . ' for object: ' . \get_class($object));
0 ignored issues
show
Unused Code introduced by
The call to TypeError::__construct() has too many arguments starting with 'Property mismatch - inp...' . \get_class($object).

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.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
296
            }
297
        };
298
299 7
        $type = $return->getType();
300
301
        if (
302 7
            \is_string($type)
303
            &&
304 7
            \class_exists($type)
305
        ) {
306 3
            if (\is_array($jsonObject)) {
307 1
                foreach ($jsonObject as $jsonObjectSingle) {
308 1
                    $collectionData = $mapper->map($jsonObjectSingle, $type);
309 1
                    $return->add($collectionData);
310
                }
311
            } else {
312 2
                $collectionData = $mapper->map($jsonObject, $type);
313 2
                $return->add($collectionData);
314
            }
315
        } else {
316 4
            foreach ($jsonObject as $key => $jsonValue) {
317 4
                $return->add($jsonValue, $key);
318
            }
319
        }
320
321 4
        return $return;
322
    }
323
324
    /**
325
     * Internal mechanic of set method.
326
     *
327
     * @param int|string|null $key
328
     * @param mixed           $value
329
     * @param bool            $checkProperties
330
     *
331
     * @return bool
332
     */
333 88
    protected function internalSet(
334
        $key,
335
        &$value,
336
        bool $checkProperties = true
337
    ): bool {
338
        if (
339 88
            $value instanceof self
340
            &&
341 88
            !$value instanceof TypeInterface
342
        ) {
343
            foreach ($value as $valueTmp) {
344
                parent::internalSet(
345
                    $key,
346
                    $valueTmp,
347
                    $checkProperties
348
                );
349
            }
350
351
            return true;
352
        }
353
354 88
        return parent::internalSet(
355 88
            $key,
356 88
            $value,
357 88
            $checkProperties
358
        );
359
    }
360
361
    /**
362
     * @param string|string[]|TypeCheckArray|TypeCheckInterface[]|null $type
363
     *
364
     * @return TypeCheckArray
365
     *
366
     * @psalm-param null|string|string[]|class-string|class-string[]|TypeCheckArray<array-key,TypeCheckInterface>|array<array-key,TypeCheckInterface>|mixed $type
367
     * @psalm-return TypeCheckArray<array-key,TypeCheckInterface>
368
     */
369 94
    protected static function convertIntoTypeCheckArray($type): TypeCheckArray
370
    {
371 94
        $is_array = false;
372
        if (
373 94
            \is_scalar($type)
374
            ||
375 94
            $is_array = \is_array($type)
376
        ) {
377
            /** @noinspection CallableParameterUseCaseInTypeContextInspection */
378 94
            $type = TypeCheckArray::create(
379
                [
380 94
                    Arrayy::ARRAYY_HELPER_TYPES_FOR_ALL_PROPERTIES => new TypeCheckSimple($is_array ? $type : (string) $type),
381
                ]
382
            );
383
        }
384
385 94
        return $type;
386
    }
387
}
388