CollectionReadTrait::getIterator()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 3
Bugs 0 Features 2
Metric Value
c 3
b 0
f 2
dl 0
loc 7
ccs 2
cts 2
cp 1
rs 9.4285
cc 1
eloc 2
nc 1
nop 0
crap 1
1
<?php
2
3
namespace Nayjest\Collection;
4
5
use ArrayIterator;
6
use IteratorIterator;
7
use RuntimeException;
8
9
/**
10
 * Trait CollectionReadTrait.
11
 *
12
 * @implements \Nayjest\Collection\CollectionWriteInterface
13
 */
14
trait CollectionReadTrait
15
{
16
    /**
17
     * Returns reference to array storing collection items.
18
     *
19
     * @return array
20
     */
21
    abstract protected function &items();
22
23
    /**
24
     * Creates collection of items.
25
     *
26
     * Override it if you need to implement
27
     * derived collection that requires specific initialization.
28
     *
29
     * @param array $items
30
     *
31
     * @return static
32
     */
33
    protected function createCollection(array $items)
34
    {
35
        $collection = new static();
36
        $itemsRef = &$collection->items();
37
        $itemsRef = $items;
38
39
        return $collection;
40
    }
41
42
    /**
43
     * Returns collection items in array.
44
     *
45
     * @return array
46
     */
47 15
    public function toArray()
48
    {
49 15
        return $this->items();
50
    }
51
52
    /**
53
     * Returns true if collection is empty.
54
     *
55
     * @return bool
56
     */
57 12
    public function isEmpty()
58
    {
59 12
        return count($this->items()) === 0;
60
    }
61
62
    /**
63
     * {@inheritdoc}
64
     */
65 5
    public function count()
66
    {
67 5
        return count($this->items());
68
    }
69
70
    /**
71
     * Checks that collections contains target item.
72
     *
73
     * @param $item
74
     *
75
     * @return bool
76
     */
77 6
    public function contains($item)
78
    {
79 6
        return in_array($item, $this->items(), true);
80
    }
81
82
    /**
83
     * {@inheritdoc}
84
     */
85 12
    public function getIterator()
86
    {
87
        // Modifying or deleting values while iterating will not affect collection data.
88
        // ArrayIterator wrapped to IteratorIterator to deny modifying and deleting values while iterating.
89
        // That will help to avoid possible errors in client code.
90 12
        return new IteratorIterator(new ArrayIterator($this->items()));
91
    }
92
93
    /**
94
     * Returns first item of the collection or null if collection is empty.
95
     *
96
     * @return mixed|null
97
     */
98 4
    public function first()
99
    {
100 4
        return $this->isEmpty() ? null : array_values($this->items())[0];
101
    }
102
103
    /**
104
     * Iterates over each value in the <b>collection</b>
105
     * passing them to the <b>callback</b> function.
106
     * If the <b>callback</b> function returns true, the
107
     * current value from <b>collection</b> is returned into
108
     * the result collection.
109
     *
110
     * @param callable $callback the callback function to use
111
     * @param array|null $optionalArguments [optional] additional arguments passed to callback
112
     *
113
     * @return CollectionReadInterface|static filtered collection
114
     */
115 5
    public function filter(callable $callback, array $optionalArguments = null)
116
    {
117 5
        return $this->createCollection(array_filter(
118 5
            $this->items(),
119 5
            self::bindAdditionalArguments($callback, $optionalArguments)
120 5
        ));
121
    }
122
123
    /**
124
     * Iterates over each value in the <b>collection</b>
125
     * passing them to the <b>callback</b> function.
126
     * If the <b>callback</b> function returns true, the
127
     * current value from <b>collection</b> is returned.
128
     *
129
     * @param callable $callback the callback function to use
130
     * @param array|null $optionalArguments [optional] additional arguments passed to callback
131
     *
132
     * @return mixed|FALSE collection item or false if item is not found.
133
     */
134 5
    public function find(callable $callback, array $optionalArguments = null)
135
    {
136 5
        foreach ($this->items() as $item) {
137 5
            $arguments = ($optionalArguments === null)
138 5
                ? [$item]
139 5
                : array_merge([$item], $optionalArguments);
140 5
            if (call_user_func_array($callback, $arguments)) {
141 5
                return $item;
142
            }
143 5
        }
144
145 1
        return false;
146
    }
147
148
149
    /**
150
     * @param callable $callback the callback function to use
151
     * @param array|null $optionalArguments [optional] additional arguments passed to callback
152
     * @return CollectionReadInterface|static
153
     */
154
    public function map(callable $callback, array $optionalArguments = null)
155
    {
156
        return $this->createCollection(array_map(
157
            self::bindAdditionalArguments($callback, $optionalArguments),
158
            $this->items()
159
        ));
160
    }
161
162
    /**
163
     * Sorts collection items.
164
     *
165
     * This method preserves key order (stable sort) using Schwartzian Transform.
166
     * @see http://stackoverflow.com/questions/4353739/preserve-key-order-stable-sort-when-sorting-with-phps-uasort
167
     * @see http://en.wikipedia.org/wiki/Schwartzian_transform
168
     *
169
     * @param callable $compareFunction
170
     * @return CollectionReadInterface|static new collection with sorted items.
171
     */
172
    public function sort(callable $compareFunction)
173
    {
174
        $items = $this->toArray();
175
176
        # Sorting with Schwartzian Transform
177
        # If stable sort is not required,
178
        # following code can be replaced to usort($items, $compareFunction);
179
        $index = 0;
180
        foreach ($items as &$item) {
181
            $item = [$index++, $item];
182
        }
183
        usort($items, function ($a, $b) use ($compareFunction) {
184
            $result = call_user_func($compareFunction, $a[1], $b[1]);
185
            return $result == 0 ? $a[0] - $b[0] : $result;
186
        });
187
        foreach ($items as &$item) {
188
            $item = $item[1];
189
        }
190
        # End of sorting with Schwartzian Transform
191
192
        return $this->createCollection($items);
193
    }
194
195
    /**
196
     * @return mixed|null
197
     */
198 4
    public function random()
199
    {
200 4
        if ($this->isEmpty()) {
201 4
            return null;
202
        }
203 4
        $index = array_rand($this->items(), 1);
204 4
        return $index === null ? null : $this->items()[$index];
205
    }
206
207
    /**
208
     * @param $item
209
     * @return CollectionReadInterface|static
210
     */
211
    public function beforeItem($item)
212
    {
213
        if (!$this->contains($item)) {
214
            throw new RuntimeException(
215
                'Can\'t select collection items before target element'
216
                . ' because collection does not contain target element'
217
            );
218
        }
219
        $items = [];
220
        foreach ($this->items() as $i) {
221
            if ($i === $item) {
222
                break;
223
            }
224
            $items[] = $i;
225
        }
226
        return $this->createCollection($items);
227
    }
228
229
    /**
230
     * @param $item
231
     * @return CollectionReadInterface|static
232
     */
233
    public function afterItem($item)
234
    {
235
        if (!$this->contains($item)) {
236
            throw new RuntimeException(
237
                'Can\'t select collection items before target element'
238
                . ' because collection does not contain target element'
239
            );
240
        }
241
        $items = [];
242
        $found = false;
243
        foreach ($this->items() as $i) {
244
            if ($i === $item) {
245
                $found = true;
246
                continue;
247
            }
248
            if ($found) {
249
                $items[] = $i;
250
            }
251
        }
252
        return $this->createCollection($items);
253
    }
254
255
    public function isWritable()
256
    {
257
        return $this instanceof CollectionWriteInterface;
258
    }
259
260 5
    protected static function bindAdditionalArguments(callable $callback, array $additionalArguments = null)
261
    {
262 5
        if ($additionalArguments === null) {
263 5
            return $callback;
264
        }
265 4
        return function ($item) use ($callback, $additionalArguments) {
266 4
            $arguments = array_merge([$item], $additionalArguments);
267 4
            return call_user_func_array($callback, $arguments);
268 4
        };
269
    }
270
}
271