Passed
Push — 1.x ( b7bde1...3cc805 )
by Ulises Jeremias
02:11
created

ImmutableArray::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php namespace Mbh\Collection;
2
3
/**
4
 * MBHFramework
5
 *
6
 * @link      https://github.com/MBHFramework/mbh-framework
7
 * @copyright Copyright (c) 2017 Ulises Jeremias Cornejo Fandos
8
 * @license   https://github.com/MBHFramework/mbh-framework/blob/master/LICENSE (MIT License)
9
 */
10
11
use Mbh\Collection\Interfaces\Sequenceable as SequenceableInterface;
12
use Mbh\Collection\CallbackHeap;
13
use Mbh\Iterator\SliceIterator;
14
use Mbh\Iterator\ConcatIterator;
15
use SplFixedArray;
16
use SplHeap;
17
use SplStack;
18
use LimitIterator;
19
use Iterator;
20
use ArrayAccess;
21
use Countable;
22
use CallbackFilterIterator;
23
use JsonSerializable;
24
use RuntimeException;
25
use Traversable;
26
use ReflectionClass;
27
28
/**
29
 * The Immutable Array
30
 *
31
 * This provides special methods for quickly creating an immutable array,
32
 * either from any Traversable, or using a C-optimized fromArray() to directly
33
 * instantiate from. Also includes methods fundamental to functional
34
 * programming, e.g. map, filter, join, and sort.
35
 *
36
 * @package structures
37
 * @author Ulises Jeremias Cornejo Fandos <[email protected]>
38
 */
39
40
class ImmutableArray implements SequenceableInterface
41
{
42
    use Traits\Collection;
43
    use Traits\Sort {
44
        Traits\Sort::heapSort as heapSortWithCallback;
45
    }
46
47
    // The secondary flash array - fixed array
48
    private $sfa = null;
49
50
    /**
51
     * Create an immutable array
52
     *
53
     * @param Traversable $immute Data guaranteed to be immutable
54
     */
55
    private function __construct(Traversable $immute)
56
    {
57
        $this->sfa = $immute;
58
    }
59
60
    /**
61
     * Map elements to a new ImmutableArray via a callback
62
     *
63
     * @param callable $callback Function to map new data
64
     * @return ImmutableArray
65
     */
66
    public function map(callable $callback): self
67
    {
68
        $count = count($this);
69
        $sfa = new SplFixedArray($count);
70
71
        for ($i = 0; $i < $count; $i++) {
72
            $sfa[$i] = $callback($this->sfa[$i], $i, $this);
73
        }
74
75
        return new static($sfa);
76
    }
77
78
    /**
79
     * forEach, or "walk" the data
80
     * Exists primarily to provide a consistent interface, though it's seldom
81
     * any better than a simple php foreach. Mainly useful for chaining.
82
     * Named walk for historic reasons - forEach is reserved in PHP
83
     *
84
     * @param callable $callback Function to call on each element
85
     * @return ImmutableArray
86
     */
87
    public function walk(callable $callback): self
88
    {
89
        foreach ($this as $i => $elem) {
90
            $callback($elem, $i, $this);
91
        }
92
93
        return $this;
94
    }
95
96
    /**
97
     * Filter out elements
98
     *
99
     * @param callable $callback Function to filter out on false
100
     * @return ImmutableArray
101
     */
102
    public function filter(callable $callback): self
103
    {
104
        $count = count($this);
105
        $sfa = new SplFixedArray($count);
106
        $newCount = 0;
107
108
        foreach ($this as $elem) {
109
            if ($callback($elem)) {
110
                $sfa[$newCount++] = $elem;
111
            }
112
        }
113
114
        $sfa->setSize($newCount);
115
        return new static($sfa);
116
    }
117
118
    /**
119
     * Reduce to a single value
120
     *
121
     * @param callable $callback Callback(
122
     *     mixed $previous, mixed $current[, mixed $index, mixed $immArray]
123
     * ):mixed Callback to run reducing function
124
     * @param mixed $accumulator Initial value for first argument
125
     */
126
    public function reduce(callable $callback, $accumulator = null)
127
    {
128
        foreach ($this as $i => $elem) {
129
            $accumulator = $callback($accumulator, $elem, $i, $this);
130
        }
131
132
        return $accumulator;
133
    }
134
135
    /**
136
     * Join a set of strings together.
137
     *
138
     * @param string $token Main token to put between elements
139
     * @param string $secondToken If set, $token on left $secondToken on right
140
     * @return string
141
     */
142
    public function join(string $token = ',', string $secondToken = null): string
143
    {
144
        $str = "";
145
        if ($secondToken) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $secondToken of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
146
            foreach ($this as $i => $elem) {
147
                $str .= $token . (string) $elem . $secondToken;
148
            }
149
        } else {
150
            $this->rewind();
151
            while ($this->valid()) {
152
                $str .= (string) $this->current();
153
                $this->next();
154
                if ($this->valid()) {
155
                    $str .= $token;
156
                }
157
            }
158
        }
159
160
        return $str;
161
    }
162
163
    /**
164
     * Take a slice of the array
165
     *
166
     * @param int $begin Start index of slice
167
     * @param int $end End index of slice
168
     * @return ImmutableArray
169
     */
170
    public function slice(int $begin = 0, int $end = null): self
171
    {
172
        $it = new SliceIterator($this, $begin, $end);
173
        return new static($it);
174
    }
175
176
    /**
177
     * Concat to the end of this array
178
     *
179
     * @param Traversable,...
180
     * @return ImmutableArray
181
     */
182
    public function concat(): self
183
    {
184
        $args = func_get_args();
185
        array_unshift($args, $this);
186
187
        // Concat this iterator, and variadic args
188
        $class = new ReflectionClass('Mbh\Iterator\ConcatIterator');
189
        $concatIt = $class->newInstanceArgs($args);
190
191
        // Create as new immutable's iterator
192
        return new static($concatIt);
193
    }
194
195
    /**
196
     * Find a single element
197
     *
198
     * @param callable $callback The test to run on each element
199
     * @return mixed The element we found
200
     */
201
    public function find(callable $callback)
202
    {
203
        foreach ($this as $i => $elem) {
204
            if ($callback($elem, $i, $this)) {
205
                return $elem;
206
            }
207
        }
208
    }
209
210
    /**
211
     * Return a new sorted ImmutableArray
212
     *
213
     * @param callable $callback The sort callback
214
     * @return ImmutableArray
215
     */
216
    public function sort(callable $callback = null)
217
    {
218
        if ($callback) {
219
            return $this->mergeSort($callback);
220
        }
221
222
        return $this->arraySort();
223
    }
224
225
    /**
226
     * Sort a new ImmutableArray by filtering through a heap.
227
     * Tends to run much faster than array or merge sorts, since you're only
228
     * sorting the pointers, and the sort function is running in a highly
229
     * optimized space.
230
     *
231
     * @param SplHeap $heap The heap to run for sorting
232
     * @return ImmutableArray
233
     */
234
    public function heapSort(SplHeap $heap): self
235
    {
236
        foreach ($this as $item) {
237
            $heap->insert($item);
238
        }
239
        return static::fromItems($heap);
240
    }
241
242
    /**
243
     * Factory for building ImmutableArrays from any traversable
244
     *
245
     * @return ImmutableArray
246
     */
247
    public static function fromItems(Traversable $array): self
248
    {
249
        // We can only do it this way if we can count it
250
        if ($array instanceof Countable) {
251
            $sfa = new SplFixedArray(count($array));
252
253
            foreach ($array as $i => $elem) {
254
                $sfa[$i] = $elem;
255
            }
256
257
            return new static($sfa);
258
        }
259
260
        // If we can't count it, it's simplest to iterate into an array first
261
        return static::fromArray(iterator_to_array($array));
262
    }
263
264
    /**
265
     * Build from an array
266
     *
267
     * @return ImmutableArray
268
     */
269
    public static function fromArray(array $array): self
270
    {
271
        return new static(SplFixedArray::fromArray($array));
272
    }
273
274
    public function toArray(): array
275
    {
276
        return $this->sfa->toArray();
0 ignored issues
show
Bug introduced by
The method toArray() does not exist on Traversable. It seems like you code against a sub-type of Traversable such as MongoDB\Driver\Cursor or Guzzle\Http\Message\Header\HeaderCollection or Guzzle\Http\Message\Header\HeaderInterface or http\QueryString or Guzzle\Common\Event or Guzzle\Common\Collection or Mbh\Collection\Interfaces\Collection or SplFixedArray or Guzzle\Service\Resource\ResourceIteratorInterface or Guzzle\Iterator\MethodProxyIterator or Mbh\Iterator\ConcatIterator or Mbh\Iterator\SliceIterator or Mbh\Collection\CallbackHeap. ( Ignorable by Annotation )

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

276
        return $this->sfa->/** @scrutinizer ignore-call */ toArray();
Loading history...
277
    }
278
279
    /**
280
     * Countable
281
     */
282
    public function count(): int
283
    {
284
        return count($this->sfa);
0 ignored issues
show
Bug introduced by
$this->sfa of type Traversable is incompatible with the type Countable|array expected by parameter $var of count(). ( Ignorable by Annotation )

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

284
        return count(/** @scrutinizer ignore-type */ $this->sfa);
Loading history...
285
    }
286
287
    /**
288
     * Iterator
289
     */
290
    public function current()
291
    {
292
        return $this->sfa->current();
0 ignored issues
show
Bug introduced by
The method current() does not exist on Traversable. It seems like you code against a sub-type of Traversable such as IntlCodePointBreakIterator or IntlRuleBasedBreakIterator or Iterator or IntlBreakIterator or MongoGridFSCursor or SimpleXMLIterator. ( Ignorable by Annotation )

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

292
        return $this->sfa->/** @scrutinizer ignore-call */ current();
Loading history...
293
    }
294
295
    public function key(): int
296
    {
297
        return $this->sfa->key();
0 ignored issues
show
Bug introduced by
The method key() does not exist on Traversable. It seems like you code against a sub-type of Traversable such as Iterator or MongoGridFSCursor or SimpleXMLIterator. ( Ignorable by Annotation )

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

297
        return $this->sfa->/** @scrutinizer ignore-call */ key();
Loading history...
298
    }
299
300
    public function next()
301
    {
302
        return $this->sfa->next();
0 ignored issues
show
Bug introduced by
The method next() does not exist on Traversable. It seems like you code against a sub-type of Traversable such as IntlCodePointBreakIterator or IntlRuleBasedBreakIterator or Iterator or IntlBreakIterator or MongoGridFSCursor or SimpleXMLIterator. ( Ignorable by Annotation )

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

302
        return $this->sfa->/** @scrutinizer ignore-call */ next();
Loading history...
303
    }
304
305
    public function rewind()
306
    {
307
        return $this->sfa->rewind();
0 ignored issues
show
Bug introduced by
The method rewind() does not exist on Traversable. It seems like you code against a sub-type of Traversable such as Iterator or MongoGridFSCursor or SimpleXMLIterator. ( Ignorable by Annotation )

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

307
        return $this->sfa->/** @scrutinizer ignore-call */ rewind();
Loading history...
308
    }
309
310
    public function valid()
311
    {
312
        return $this->sfa->valid();
0 ignored issues
show
Bug introduced by
The method valid() does not exist on Traversable. It seems like you code against a sub-type of Traversable such as Iterator or MongoGridFSCursor or SimpleXMLIterator. ( Ignorable by Annotation )

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

312
        return $this->sfa->/** @scrutinizer ignore-call */ valid();
Loading history...
313
    }
314
315
    /**
316
     * ArrayAccess
317
     */
318
    public function offsetExists($offset): bool
319
    {
320
        return $this->sfa->offsetExists($offset);
0 ignored issues
show
Bug introduced by
The method offsetExists() does not exist on Traversable. It seems like you code against a sub-type of Traversable such as Threaded or SimpleXMLElement or Thread or Worker or Stackable or Guzzle\Http\Message\Header\HeaderCollection or http\QueryString or Symfony\Component\EventDispatcher\GenericEvent or Guzzle\Common\Event or Guzzle\Common\Collection or ArrayObject or SplDoublyLinkedList or SplFixedArray or SplObjectStorage or Mbh\Collection\Interfaces\Sequenceable or CachingIterator or Guzzle\Iterator\MethodProxyIterator or Mbh\Iterator\ConcatIterator or Mbh\Iterator\SliceIterator or Phar or ArrayIterator or Phar or Phar or RecursiveCachingIterator or RecursiveArrayIterator or SimpleXMLIterator or Phar. ( Ignorable by Annotation )

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

320
        return $this->sfa->/** @scrutinizer ignore-call */ offsetExists($offset);
Loading history...
321
    }
322
323
    public function offsetGet($offset)
324
    {
325
        return $this->sfa->offsetGet($offset);
0 ignored issues
show
Bug introduced by
The method offsetGet() does not exist on Traversable. It seems like you code against a sub-type of Traversable such as Threaded or SimpleXMLElement or Thread or Worker or Stackable or Guzzle\Http\Message\Header\HeaderCollection or http\QueryString or Symfony\Component\EventDispatcher\GenericEvent or Guzzle\Common\Event or Guzzle\Common\Collection or ArrayObject or SplDoublyLinkedList or SplFixedArray or SplObjectStorage or Mbh\Collection\Interfaces\Sequenceable or CachingIterator or Guzzle\Iterator\MethodProxyIterator or Mbh\Iterator\ConcatIterator or Mbh\Iterator\SliceIterator or Phar or ArrayIterator or Phar or Phar or RecursiveCachingIterator or RecursiveArrayIterator or SimpleXMLIterator or Phar. ( Ignorable by Annotation )

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

325
        return $this->sfa->/** @scrutinizer ignore-call */ offsetGet($offset);
Loading history...
326
    }
327
328
    public function offsetSet($offset, $value)
329
    {
330
        throw new RuntimeException('Attempt to mutate immutable ' . __CLASS__ . ' object.');
331
    }
332
333
    public function offsetUnset($offset)
334
    {
335
        throw new RuntimeException('Attempt to mutate immutable ' . __CLASS__ . ' object.');
336
    }
337
338
    public function clear()
339
    {
340
        throw new RuntimeException('Attempt to mutate immutable ' . __CLASS__ . ' object.');
341
    }
342
}
343