Completed
Push — master ( b1da28...d63431 )
by Lars
02:17
created

AbstractCollection::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 5
ccs 4
cts 4
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Arrayy\Collection;
6
7
use Arrayy\Arrayy;
8
9
/**
10
 * This class provides a full implementation of `CollectionInterface`, to
11
 * minimize the effort required to implement this interface.
12
 */
13
abstract class AbstractCollection extends Arrayy implements CollectionInterface
14
{
15
    /**
16
     * The type of elements stored in this collection.
17
     *
18
     * @var string
19
     */
20
    private $collectionType;
21
22
    /**
23
     * Constructs a collection object of the specified type, optionally with the
24
     * specified data.
25
     *
26
     * @param mixed $data
27
     *                     <p>
28
     *                     The initial items to store in the collection.
29
     *                     </p>
30
     *                     <p>
31
     *                     Should be an array or a generator, otherwise it will try
32
     *                     to convert it into an array.
33
     *                     </p>
34
     */
35 11
    public function __construct($data = [])
36
    {
37 11
        $this->collectionType = $this->getType();
38 11
        parent::__construct($data);
39 10
    }
40
41
    /**
42
     * @return static[]
43
     */
44 3
    public function getCollection(): array
45
    {
46 3
        return $this->array;
47
    }
48
49
    /**
50
     * The type (FQCN) associated with this collection.
51
     *
52
     * @return string
53
     */
54
    abstract public function getType(): string;
55
56
    /**
57
     * Merge current items and items of given collections into a new one.
58
     *
59
     * @param CollectionInterface ...$collections The collections to merge.
60
     *
61
     * @throws \InvalidArgumentException if any of the given collections are not of the same type
62
     *
63
     * @return CollectionInterface
64
     */
65 1
    public function merge(CollectionInterface ...$collections): CollectionInterface
66
    {
67 1
        foreach ($collections as $collection) {
68 1
            if ($collection instanceof Arrayy) {
69 1
                foreach ($collection as $item) {
70 1
                    $this->append($item);
71
                }
72
            }
73
        }
74
75 1
        return $this;
76
    }
77
78
    /**
79
     * Assigns a value to the specified offset + check the type.
80
     *
81
     * @param int|string|null $offset
82
     * @param mixed           $value
83
     */
84 1 View Code Duplication
    public function offsetSet($offset, $value)
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...
85
    {
86 1
        if ($this->checkType($this->collectionType, $value) === false) {
87 1
            throw new \InvalidArgumentException(
88 1
                'Value must be of type ' . $this->collectionType . '; value is ' . $this->valueToString($value)
89
            );
90
        }
91
92
        parent::offsetSet($offset, $value);
93
    }
94
95
    /**
96
     * Prepend a (key) + value to the current array.
97
     *
98
     * @param mixed $value
99
     * @param mixed $key
100
     *
101
     * @return static
102
     *                <p>(Mutable) Return this Arrayy object, with the prepended value.</p>
103
     */
104 2 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...
105
    {
106 2
        if ($this->checkType($this->collectionType, $value) === false) {
107 2
            throw new \InvalidArgumentException(
108 2
                'Value must be of type ' . $this->collectionType . '; value is ' . $this->valueToString($value)
109
            );
110
        }
111
112
        return parent::prepend($value, $key);
113
    }
114
115
    /**
116
     * Append a (key) + value to the current array.
117
     *
118
     * @param mixed $value
119
     * @param mixed $key
120
     *
121
     * @return static
122
     *                <p>(Mutable) Return this Arrayy object, with the appended values.</p>
123
     */
124 3 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...
125
    {
126 3
        if ($this->checkType($this->collectionType, $value) === false) {
127 1
            throw new \InvalidArgumentException(
128 1
                'Value must be of type ' . $this->collectionType . '; value is ' . $this->valueToString($value)
129
            );
130
        }
131
132 2
        return parent::append($value, $key);
133
    }
134
135
    /**
136
     * Returns the values from given property or method.
137
     *
138
     * @param string $keyOrPropertyOrMethod the property or method name to filter by
139
     *
140
     * @throws \InvalidArgumentException if property or method is not defined
141
     *
142
     * @return array
143
     */
144 1
    public function column(string $keyOrPropertyOrMethod): array
145
    {
146
        // init
147 1
        $temp = [];
148
149 1
        foreach ($this->array as $item) {
150 1
            $temp[] = $this->extractValue($item, $keyOrPropertyOrMethod);
151
        }
152
153 1
        return $temp;
154
    }
155
156
    /**
157
     * Returns a collection of matching items.
158
     *
159
     * @param string $keyOrPropertyOrMethod the property or method to evaluate
160
     * @param mixed  $value                 the value to match
161
     *
162
     * @throws \InvalidArgumentException if property or method is not defined
163
     *
164
     * @return CollectionInterface
165
     */
166 1
    public function where(string $keyOrPropertyOrMethod, $value): CollectionInterface
167
    {
168 1
        return $this->filter(
169
            function ($item) use ($keyOrPropertyOrMethod, $value) {
170 1
                $accessorValue = $this->extractValue(
171 1
                    $item,
172 1
                    $keyOrPropertyOrMethod
173
                );
174
175 1
                return $accessorValue === $value;
176 1
            }
177
        );
178
    }
179
180
    /**
181
     * Internal mechanic of set method.
182
     *
183
     * @param string|null $key
184
     * @param mixed       $value
185
     * @param bool        $checkProperties
186
     *
187
     * @return bool
188
     */
189 10 View Code Duplication
    protected function internalSet($key, $value, $checkProperties = true): bool
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...
190
    {
191 10
        if ($this->checkType($this->collectionType, $value) === false) {
192 1
            throw new \InvalidArgumentException(
193 1
                'Value must be of type ' . $this->collectionType . '; value is ' . $this->valueToString($value)
194
            );
195
        }
196
197 9
        return parent::internalSet($key, $value, $checkProperties);
198
    }
199
200
    /**
201
     * Extracts the value of the given property or method from the object.
202
     *
203
     * @param Arrayy $object                the object to extract the value from
204
     * @param string $keyOrPropertyOrMethod the property or method for which the
205
     *                                      value should be extracted
206
     *
207
     * @throws \InvalidArgumentException if the method or property is not defined
208
     *
209
     * @return mixed the value extracted from the specified property or method
210
     */
211 2
    private function extractValue(Arrayy $object, string $keyOrPropertyOrMethod)
212
    {
213 2
        if (isset($object[$keyOrPropertyOrMethod])) {
214 2
            $return = $object->get($keyOrPropertyOrMethod);
215
216 2
            if ($return instanceof Arrayy) {
217 1
                return $return->getArray();
218
            }
219
220 1
            return $return;
221
        }
222
223
        if (\property_exists($object, $keyOrPropertyOrMethod)) {
224
            return $object->{$keyOrPropertyOrMethod};
225
        }
226
227
        if (\method_exists($object, $keyOrPropertyOrMethod)) {
228
            return $object->{$keyOrPropertyOrMethod}();
229
        }
230
231
        throw new \InvalidArgumentException(
232
            \sprintf('array-key & property & method "%s" not defined in %s', $keyOrPropertyOrMethod, \gettype($object))
233
        );
234
    }
235
236
    /**
237
     * Returns `true` if value is of the specified type.
238
     *
239
     * @param string $type  the type to check the value against
240
     * @param mixed  $value the value to check
241
     *
242
     * @return bool
243
     */
244 10
    private function checkType(string $type, $value): bool
245
    {
246
        switch ($type) {
247 10
            case 'array':
248
                return \is_array($value);
249 10
            case 'bool':
250 10
            case 'boolean':
251
                return \is_bool($value);
252 10
            case 'callable':
253
                return \is_callable($value);
254 10
            case 'float':
255 10
            case 'double':
256
                return \is_float($value);
257 10
            case 'int':
258 10
            case 'integer':
259
                return \is_int($value);
260 10
            case 'null':
261
                return $value === null;
262 10
            case 'numeric':
263
                return \is_numeric($value);
264 10
            case 'object':
265
                return \is_object($value);
266 10
            case 'resource':
267
                return \is_resource($value);
268 10
            case 'scalar':
269
                return \is_scalar($value);
270 10
            case 'string':
271
                return \is_string($value);
272 10
            case 'mixed':
273
                return true;
274
            default:
275 10
                return $value instanceof $type;
276
        }
277
    }
278
279
    /**
280
     * @param mixed $value
281
     *
282
     * @return string
283
     */
284 5
    private function valueToString($value): string
285
    {
286
        // null
287 5
        if ($value === null) {
288
            return 'NULL';
289
        }
290
291
        // bool
292 5
        if (\is_bool($value)) {
293
            return $value ? 'TRUE' : 'FALSE';
294
        }
295
296
        // array
297 5
        if (\is_array($value)) {
298
            return 'Array';
299
        }
300
301
        // scalar types (integer, float, string)
302 5
        if (\is_scalar($value)) {
303
            return (string) $value;
304
        }
305
306
        // resource
307 5
        if (\is_resource($value)) {
308
            return \get_resource_type($value) . ' resource #' . (int) $value;
309
        }
310
311
        // object
312 5
        return \get_class($value) . ' Object';
313
    }
314
}
315