Completed
Push — master ( 70e886...cfba1b )
by Lars
02:04
created

AbstractCollection::column()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 1
dl 0
loc 11
ccs 5
cts 5
cp 1
crap 2
rs 9.9
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 array $data the initial items to store in the collection
27
     */
28 8
    public function __construct(array $data = [])
29
    {
30 8
        $this->collectionType = $this->getType();
31 8
        parent::__construct($data);
32 7
    }
33
34
    /**
35
     * @return static[]
36
     */
37 2
    public function getCollection(): array
38
    {
39 2
        return $this->array;
40
    }
41
42
    /**
43
     * The type (FQCN) associated with this collection.
44
     *
45
     * @return string
46
     */
47
    abstract public function getType(): string;
48
49
    /**
50
     * Merge current items and items of given collections into a new one.
51
     *
52
     * @param CollectionInterface ...$collections The collections to merge.
53
     *
54
     * @throws \InvalidArgumentException if any of the given collections are not of the same type
55
     *
56
     * @return CollectionInterface
57
     */
58
    public function merge(CollectionInterface ...$collections): CollectionInterface
59
    {
60
        $temp = [$this->array];
61
62
        foreach ($collections as $index => $collection) {
63
            if (!$collection instanceof static) {
64
                throw new \InvalidArgumentException(
65
                    \sprintf('Collection with index %d must be of type %s', $index, static::class)
66
                );
67
            }
68
69
            $temp[] = $collection->toArray();
70
        }
71
72
        return new static(\array_replace(...$temp));
73
    }
74
75
    /**
76
     * Assigns a value to the specified offset + check the type.
77
     *
78
     * @param int|string|null $offset
79
     * @param mixed           $value
80
     */
81 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...
82
    {
83 1
        if ($this->checkType($this->getType(), $value) === false) {
84 1
            throw new \InvalidArgumentException(
85 1
                'Value must be of type ' . $this->getType() . '; value is ' . $this->valueToString($value)
86
            );
87
        }
88
89
        parent::offsetSet($offset, $value);
90
    }
91
92
    /**
93
     * Prepend a (key) + value to the current array.
94
     *
95
     * @param mixed $value
96
     * @param mixed $key
97
     *
98
     * @return static
99
     *                <p>(Mutable) Return this Arrayy object, with the prepended value.</p>
100
     */
101 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...
102
    {
103 2
        if ($this->checkType($this->getType(), $value) === false) {
104 2
            throw new \InvalidArgumentException(
105 2
                'Value must be of type ' . $this->getType() . '; value is ' . $this->valueToString($value)
106
            );
107
        }
108
109
        return parent::prepend($value, $key);
110
    }
111
112
    /**
113
     * Append a (key) + value to the current array.
114
     *
115
     * @param mixed $value
116
     * @param mixed $key
117
     *
118
     * @return static
119
     *                <p>(Mutable) Return this Arrayy object, with the appended values.</p>
120
     */
121 2 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...
122
    {
123 2
        if ($this->checkType($this->getType(), $value) === false) {
124 1
            throw new \InvalidArgumentException(
125 1
                'Value must be of type ' . $this->getType() . '; value is ' . $this->valueToString($value)
126
            );
127
        }
128
129 1
        return parent::append($value, $key);
130
    }
131
132
    /**
133
     * Returns the values from given property or method.
134
     *
135
     * @param string $keyOrPropertyOrMethod the property or method name to filter by
136
     *
137
     * @throws \InvalidArgumentException if property or method is not defined
138
     *
139
     * @return array
140
     */
141 1
    public function column(string $keyOrPropertyOrMethod): array
142
    {
143
        // init
144 1
        $temp = [];
145
146 1
        foreach ($this->array as $item) {
147 1
            $temp[] = $this->extractValue($item, $keyOrPropertyOrMethod);
148
        }
149
150 1
        return $temp;
151
    }
152
153
    /**
154
     * Returns a collection of matching items.
155
     *
156
     * @param string $keyOrPropertyOrMethod the property or method to evaluate
157
     * @param mixed  $value                 the value to match
158
     *
159
     * @throws \InvalidArgumentException if property or method is not defined
160
     *
161
     * @return CollectionInterface
162
     */
163
    public function where(string $keyOrPropertyOrMethod, $value): CollectionInterface
164
    {
165
        return $this->filter(
166
            function ($item) use ($keyOrPropertyOrMethod, $value) {
167
                $accessorValue = $this->extractValue(
168
                    $item,
169
                    $keyOrPropertyOrMethod
170
                );
171
172
                return $accessorValue === $value;
173
            }
174
        );
175
    }
176
177
    /**
178
     * Internal mechanic of set method.
179
     *
180
     * @param string|null $key
181
     * @param mixed       $value
182
     * @param bool        $checkProperties
183
     *
184
     * @return bool
185
     */
186 8 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...
187
    {
188 8
        if ($this->checkType($this->getType(), $value) === false) {
189 1
            throw new \InvalidArgumentException(
190 1
                'Value must be of type ' . $this->getType() . '; value is ' . $this->valueToString($value)
191
            );
192
        }
193
194 7
        return parent::internalSet($key, $value, $checkProperties);
195
    }
196
197
    /**
198
     * Extracts the value of the given property or method from the object.
199
     *
200
     * @param object $object                the object to extract the value from
201
     * @param string $keyOrPropertyOrMethod the property or method for which the
202
     *                                      value should be extracted
203
     *
204
     * @throws \InvalidArgumentException if the method or property is not defined
205
     *
206
     * @return mixed the value extracted from the specified property or method
207
     */
208 1
    private function extractValue($object, string $keyOrPropertyOrMethod)
209
    {
210 1
        if (\array_key_exists($keyOrPropertyOrMethod, $object->array)) {
211 1
            return $object[$keyOrPropertyOrMethod];
212
        }
213
214
        if (\property_exists($object, $keyOrPropertyOrMethod)) {
215
            return $object->{$propertyOrMethod};
0 ignored issues
show
Bug introduced by
The variable $propertyOrMethod does not exist. Did you mean $keyOrPropertyOrMethod?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
216
        }
217
218
        if (\method_exists($object, $keyOrPropertyOrMethod)) {
219
            return $object->{$keyOrPropertyOrMethod}();
220
        }
221
222
        throw new \InvalidArgumentException(
223
            \sprintf('array-key & property & method "%s" not defined in %s', $keyOrPropertyOrMethod, \get_class($object))
224
        );
225
    }
226
227
    /**
228
     * Returns `true` if value is of the specified type.
229
     *
230
     * @param string $type  the type to check the value against
231
     * @param mixed  $value the value to check
232
     *
233
     * @return bool
234
     */
235 8
    private function checkType(string $type, $value): bool
236
    {
237
        switch ($type) {
238 8
            case 'array':
239
                return \is_array($value);
240 8
            case 'bool':
241 8
            case 'boolean':
242
                return \is_bool($value);
243 8
            case 'callable':
244
                return \is_callable($value);
245 8
            case 'float':
246 8
            case 'double':
247
                return \is_float($value);
248 8
            case 'int':
249 8
            case 'integer':
250
                return \is_int($value);
251 8
            case 'null':
252
                return $value === null;
253 8
            case 'numeric':
254
                return \is_numeric($value);
255 8
            case 'object':
256
                return \is_object($value);
257 8
            case 'resource':
258
                return \is_resource($value);
259 8
            case 'scalar':
260
                return \is_scalar($value);
261 8
            case 'string':
262
                return \is_string($value);
263 8
            case 'mixed':
264
                return true;
265
            default:
266 8
                return $value instanceof $type;
267
        }
268
    }
269
270
    /**
271
     * @param mixed $value
272
     *
273
     * @return string
274
     */
275 5
    private function valueToString($value): string
276
    {
277
        // null
278 5
        if ($value === null) {
279
            return 'NULL';
280
        }
281
282
        // bool
283 5
        if (\is_bool($value)) {
284
            return $value ? 'TRUE' : 'FALSE';
285
        }
286
287
        // array
288 5
        if (\is_array($value)) {
289
            return 'Array';
290
        }
291
292
        // scalar types (integer, float, string)
293 5
        if (\is_scalar($value)) {
294
            return (string) $value;
295
        }
296
297
        // resource
298 5
        if (\is_resource($value)) {
299
            return \get_resource_type($value) . ' resource #' . (int) $value;
300
        }
301
302
        // object
303 5
        return \get_class($value) . ' Object';
304
    }
305
}
306