Passed
Push — develop ( 9a09e8...d94677 )
by Vasil
08:01 queued 04:40
created

ResponseCollection::count()   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 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * ~~summary~~
5
 *
6
 * ~~description~~
7
 *
8
 * PHP version 5
9
 *
10
 * @category  Net
11
 * @package   PEAR2_Net_RouterOS
12
 * @author    Vasil Rangelov <[email protected]>
13
 * @copyright 2011 Vasil Rangelov
14
 * @license   http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
15
 * @version   GIT: $Id$
16
 * @link      http://pear2.php.net/PEAR2_Net_RouterOS
17
 */
18
/**
19
 * The namespace declaration.
20
 */
21
namespace PEAR2\Net\RouterOS;
22
23
/**
24
 * Implemented by this class.
25
 */
26
use ArrayAccess;
27
28
/**
29
 * Implemented by this class.
30
 */
31
use Countable;
32
33
/**
34
 * Implemented by this class.
35
 */
36
use SeekableIterator;
37
38
/**
39
 * Represents a collection of RouterOS responses.
40
 *
41
 * @category Net
42
 * @package  PEAR2_Net_RouterOS
43
 * @author   Vasil Rangelov <[email protected]>
44
 * @license  http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
45
 * @link     http://pear2.php.net/PEAR2_Net_RouterOS
46
 *
47
 * @method string getType()
48
 *     Calls {@link Response::getType()}
49
 *     on the response pointed by the pointer.
50
 * @method string[] getUnrecognizedWords()
51
 *     Calls {@link Response::getUnrecognizedWords()}
52
 *     on the response pointed by the pointer.
53
 * @method string|resource|null getProperty(string $name)
54
 *     Calls {@link Response::getProperty()}
55
 *     on the response pointed by the pointer.
56
 * @method string getTag()
57
 *     Calls {@link Response::getTag()}
58
 *     on the response pointed by the pointer.
59
 */
60
class ResponseCollection implements ArrayAccess, SeekableIterator, Countable
61
{
62
63
    /**
64
     * An array with all {@link Response} objects.
65
     *
66
     * An array with all Response objects.
67
     *
68
     * @var Response[]
69
     */
70
    protected $responses = array();
71
72
    /**
73
     * An array with each Response object's type.
74
     *
75
     * An array with each {@link Response} object's type.
76
     *
77
     * @var string[]
78
     */
79
    protected $responseTypes = array();
80
81
    /**
82
     * An array with each Response object's tag.
83
     *
84
     * An array with each {@link Response} object's tag.
85
     *
86
     * @var string[]
87
     */
88
    protected $responseTags = array();
89
90
    /**
91
     * An array with positions of responses, based on an property name.
92
     *
93
     * The name of each property is the array key, and the array value
94
     * is another array where the key is the value for that property, and
95
     * the value is the position of the response. For performance reasons,
96
     * each key is built only when {@link static::setIndex()} is called with
97
     * that property, and remains available for the lifetime of this collection.
98
     *
99
     * @var array<string,array<string,int>>
100
     */
101
    protected $responsesIndex = array();
102
103
    /**
104
     * An array with all distinct properties.
105
     *
106
     * An array with all distinct properties across all {@link Response}
107
     * objects. Created at the first call of {@link static::getPropertyMap()}.
108
     *
109
     * @var array<string,int[]>
110
     */
111
    protected $propertyMap = null;
112
113
    /**
114
     * A pointer, as required by SeekableIterator.
115
     *
116
     * @var int
117
     */
118
    protected $position = 0;
119
120
    /**
121
     * Name of property to use as index
122
     *
123
     * NULL when disabled.
124
     *
125
     * @var string|null
126
     */
127
    protected $index = null;
128
129
    /**
130
     * Compare criteria.
131
     *
132
     * Used by {@link static::compare()} to determine the order between
133
     * two responses. See {@link static::orderBy()} for a detailed description
134
     * of this array's format.
135
     *
136
     * @var string[]|array<string,null|int|array<int|callable>>
137
     */
138
    protected $compareBy = array();
139
140
    /**
141
     * Creates a new collection.
142
     *
143
     * @param Response[] $responses An array of responses, in network order.
144
     */
145
    public function __construct(array $responses)
146
    {
147
        $pos = 0;
148
        foreach ($responses as $response) {
149
            if ($response instanceof Response) {
150
                $this->responseTypes[$pos] = $response->getType();
151
                $this->responseTags[$pos] = $response->getTag();
152
                $this->responses[$pos++] = $response;
153
            }
154
        }
155
    }
156
157
    /**
158
     * A shorthand gateway.
159
     *
160
     * This is a magic PHP method that allows you to call the object as a
161
     * function. Depending on the argument given, one of the other functions in
162
     * the class is invoked and its returned value is returned by this function.
163
     *
164
     * @param int|string|null $offset The offset of the response to seek to.
165
     *     If the offset is negative, seek to that relative to the end.
166
     *     If the collection is indexed, you can also supply a value to seek to.
167
     *     Setting NULL will get the current response's iterator.
168
     *
169
     * @return Response|ArrayObject The {@link Response} at the specified
170
     *     offset, the current response's iterator (which is an ArrayObject)
171
     *     when NULL is given, or FALSE if the offset is invalid
172
     *     or the collection is empty.
173
     */
174
    public function __invoke($offset = null)
175
    {
176
        return null === $offset
177
            ? $this->current()->getIterator()
178
            : $this->seek($offset);
179
    }
180
181
    /**
182
     * Sets a property to be usable as a key in the collection.
183
     *
184
     * @param string|null $name The name of the property to use. Future calls
185
     *     that accept a position will then also be able to search values of
186
     *     that property for a matching value.
187
     *     Specifying NULL will disable such lookups (as is by default).
188
     *     Note that in case this value occurs multiple times within the
189
     *     collection, only the last matching response will be accessible by
190
     *     that value.
191
     *
192
     * @return $this The object itself.
193
     */
194
    public function setIndex($name)
195
    {
196
        if (null !== $name) {
197
            $name = (string)$name;
198
            if (!isset($this->responsesIndex[$name])) {
199
                $this->responsesIndex[$name] = array();
200
                foreach ($this->responses as $pos => $response) {
201
                    $val = $response->getProperty($name);
202
                    if (null !== $val) {
203
                        $this->responsesIndex[$name][$val] = $pos;
204
                    }
205
                }
206
            }
207
        }
208
        $this->index = $name;
209
        return $this;
210
    }
211
212
    /**
213
     * Gets the name of the property used as an index.
214
     *
215
     * @return string|null Name of property used as index. NULL when disabled.
216
     */
217
    public function getIndex()
218
    {
219
        return $this->index;
220
    }
221
222
    /**
223
     * Gets the whole collection as an array.
224
     *
225
     * @param bool $useIndex Whether to use the index values as keys for the
226
     *     resulting array.
227
     *
228
     * @return Response[] An array with all responses, in network order.
229
     */
230
    public function toArray($useIndex = false)
231
    {
232
        if ($useIndex) {
233
            $positions = $this->responsesIndex[$this->index];
234
            asort($positions, SORT_NUMERIC);
235
            $positions = array_flip($positions);
236
            return array_combine(
237
                $positions,
238
                array_intersect_key($this->responses, $positions)
239
            );
240
        }
241
        return $this->responses;
242
    }
243
244
    /**
245
     * Counts the responses in the collection.
246
     *
247
     * @return int The number of responses in the collection.
248
     */
249
    public function count()
250
    {
251
        return count($this->responses);
252
    }
253
254
    /**
255
     * Checks if an offset exists.
256
     *
257
     * @param int|string $offset The offset to check. If the
258
     *     collection is indexed, you can also supply a value to check.
259
     *     Note that negative numeric offsets are NOT accepted.
260
     *
261
     * @return bool TRUE if the offset exists, FALSE otherwise.
262
     */
263
    public function offsetExists($offset)
264
    {
265
        return is_int($offset)
266
            ? array_key_exists($offset, $this->responses)
267
            : array_key_exists($offset, $this->responsesIndex[$this->index]);
268
    }
269
270
    /**
271
     * Gets a {@link Response} from a specified offset.
272
     *
273
     * @param int|string $offset The offset of the desired response. If the
274
     *     collection is indexed, you can also supply the value to search for.
275
     *
276
     * @return Response The response at the specified offset.
277
     */
278
    public function offsetGet($offset)
279
    {
280
        return is_int($offset)
281
            ? $this->responses[$offset >= 0
282
            ? $offset
283
            : count($this->responses) + $offset]
284
            : $this->responses[$this->responsesIndex[$this->index][$offset]];
285
    }
286
287
    /**
288
     * N/A
289
     *
290
     * This method exists only because it is required for ArrayAccess. The
291
     * collection is read only.
292
     *
293
     * @param int|string $offset N/A
294
     * @param Response   $value  N/A
295
     *
296
     * @return void
297
     *
298
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
299
     */
300
    public function offsetSet($offset, $value)
301
    {
302
303
    }
304
305
    /**
306
     * N/A
307
     *
308
     * This method exists only because it is required for ArrayAccess. The
309
     * collection is read only.
310
     *
311
     * @param int|string $offset N/A
312
     *
313
     * @return void
314
     *
315
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
316
     */
317
    public function offsetUnset($offset)
318
    {
319
320
    }
321
322
    /**
323
     * Resets the pointer to 0, and returns the first response.
324
     *
325
     * @return Response|false The first response in the collection,
326
     *     or FALSE if the collection is empty.
327
     */
328
    public function rewind()
329
    {
330
        return $this->seek(0);
331
    }
332
333
    /**
334
     * Moves the position pointer to a specified position.
335
     *
336
     * @param int|string $position The position to move to. If the collection is
337
     *     indexed, you can also supply a value to move the pointer to.
338
     *     A non-existent index will move the pointer to "-1".
339
     *
340
     * @return Response|false The {@link Response} at the specified position,
341
     *     or FALSE if the specified position is not valid.
342
     */
343
    public function seek($position)
344
    {
345
        $this->position = is_int($position)
346
            ? ($position >= 0
347
            ? $position
348
            : count($this->responses) + $position)
349
            : ($this->offsetExists($position)
350
            ? $this->responsesIndex[$this->index][$position]
351
            : -1);
352
        return $this->current();
353
    }
354
355
    /**
356
     * Moves the pointer forward by 1, and gets the next response.
357
     *
358
     * @return Response|false The next {@link Response} object,
359
     *     or FALSE if the position is not valid.
360
     */
361
    public function next()
362
    {
363
        ++$this->position;
364
        return $this->current();
365
    }
366
367
    /**
368
     * Gets the response at the current pointer position.
369
     *
370
     * @return Response|false The response at the current pointer position,
371
     *     or FALSE if the position is not valid.
372
     */
373
    public function current()
374
    {
375
        return $this->valid() ? $this->responses[$this->position] : false;
376
    }
377
378
    /**
379
     * Moves the pointer backwards by 1, and gets the previous response.
380
     *
381
     * @return Response|false The next {@link Response} object,
382
     *     or FALSE if the position is not valid.
383
     */
384
    public function prev()
385
    {
386
        --$this->position;
387
        return $this->current();
388
    }
389
390
    /**
391
     * Moves the pointer to the last valid position, and returns the last
392
     * response.
393
     *
394
     * @return Response|false The last response in the collection,
395
     *     or FALSE if the collection is empty.
396
     */
397
    public function end()
398
    {
399
        $this->position = count($this->responses) - 1;
400
        return $this->current();
401
    }
402
403
    /**
404
     * Gets the key at the current pointer position.
405
     *
406
     * @return int|false The key at the current pointer position,
407
     *     i.e. the pointer position itself, or FALSE if the position
408
     *     is not valid.
409
     */
410
    public function key()
411
    {
412
        return $this->valid() ? $this->position : false;
413
    }
414
415
    /**
416
     * Checks if the pointer is still pointing to an existing offset.
417
     *
418
     * @return bool TRUE if the pointer is valid, FALSE otherwise.
419
     */
420
    public function valid()
421
    {
422
        return $this->offsetExists($this->position);
423
    }
424
425
    /**
426
     * Gets all distinct property names.
427
     *
428
     * Gets all distinct property names across all responses.
429
     *
430
     * @return array<string,int[]> An array with
431
     *     all distinct property names as keys, and
432
     *     the indexes at which they occur as values.
433
     */
434
    public function getPropertyMap()
435
    {
436
        if (null === $this->propertyMap) {
437
            $properties = array();
438
            foreach ($this->responses as $index => $response) {
439
                $names = array_keys($response->getIterator()->getArrayCopy());
440
                foreach ($names as $name) {
441
                    if (!isset($properties[$name])) {
442
                        $properties[$name] = array();
443
                    }
444
                    $properties[$name][] = $index;
445
                }
446
            }
447
            $this->propertyMap = $properties;
448
        }
449
        return $this->propertyMap;
450
    }
451
452
    /**
453
     * Gets all responses of a specified type.
454
     *
455
     * @param string $type The response type to filter by. Valid values are the
456
     *     Response::TYPE_* constants.
457
     *
458
     * @return static A new collection with responses of the
459
     *     specified type.
460
     */
461
    public function getAllOfType($type)
462
    {
463
        $result = array();
464
        foreach (array_keys($this->responseTypes, $type, true) as $index) {
465
            $result[] = $this->responses[$index];
466
        }
467
        return new static($result);
468
    }
469
470
    /**
471
     * Gets all responses with a specified tag.
472
     *
473
     * @param string $tag The tag to filter by.
474
     *
475
     * @return static A new collection with responses having the
476
     *     specified tag.
477
     */
478
    public function getAllTagged($tag)
479
    {
480
        $result = array();
481
        foreach (array_keys($this->responseTags, $tag, true) as $index) {
482
            $result[] = $this->responses[$index];
483
        }
484
        return new static($result);
485
    }
486
487
    /**
488
     * Order resones by criteria.
489
     *
490
     * @param string[]|array<string,null|int|array<int|callable>> $criteria The
491
     *     criteria to order responses by. It takes the
492
     *     form of an array where each key is the name of the property to use
493
     *     as (N+1)th sorting key. The value of each member can be either NULL
494
     *     (for that property, sort normally in ascending order), a single sort
495
     *     order constant (SORT_ASC or SORT_DESC) to sort normally in the
496
     *     specified order, an array where the first member is an order
497
     *     constant, and the second one is sorting flags (same as built in PHP
498
     *     array functions) or a callback.
499
     *     If a callback is provided, it must accept two arguments
500
     *     (the two values to be compared), and return -1, 0 or 1 if the first
501
     *     value is respectively less than, equal to or greater than the second
502
     *     one.
503
     *     Each key of $criteria can also be numeric, in which case the
504
     *     value is the name of the property, and sorting is done normally in
505
     *     ascending order.
506
     *
507
     * @return static A new collection with the responses sorted in the
508
     *     specified order.
509
     */
510
    public function orderBy(array $criteria)
511
    {
512
        $this->compareBy = $criteria;
513
        $sortedResponses = $this->responses;
514
        usort($sortedResponses, array($this, 'compare'));
515
        return new static($sortedResponses);
516
    }
517
518
    /**
519
     * Calls a method of the response pointed by the pointer.
520
     *
521
     * Calls a method of the response pointed by the pointer. This is a magic
522
     * PHP method, thanks to which any function you call on the collection that
523
     * is not defined will be redirected to the response.
524
     *
525
     * @param string $method The name of the method to call.
526
     * @param array  $args   The arguments to pass to the method.
527
     *
528
     * @return mixed Whatever the called function returns.
529
     */
530
    public function __call($method, array $args)
531
    {
532
        return call_user_func_array(
533
            array($this->current(), $method),
534
            $args
535
        );
536
    }
537
538
    /**
539
     * Compares two responses.
540
     *
541
     * Compares two responses, based on criteria defined in
542
     * {@link static::$compareBy}.
543
     *
544
     * @param Response $itemA The response to compare.
545
     * @param Response $itemB The response to compare $a against.
546
     *
547
     * @return int Returns 0 if the two responses are equal according to every
548
     *     criteria specified, -1 if $a should be placed before $b, and 1 if $b
549
     *     should be placed before $a.
550
     */
551
    protected function compare(Response $itemA, Response $itemB)
552
    {
553
        foreach ($this->compareBy as $name => $spec) {
554
            if (!is_string($name)) {
555
                $name = $spec;
556
                $spec = null;
557
            }
558
559
            $members = array(
560
                0 => $itemA->getProperty($name),
561
                1 => $itemB->getProperty($name)
562
            );
563
564
            if (is_callable($spec)) {
565
                uasort($members, $spec);
566
            } elseif ($members[0] === $members[1]) {
567
                continue;
568
            } else {
569
                $flags = SORT_REGULAR;
570
                $order = SORT_ASC;
571
                if (is_array($spec)) {
572
                    list($order, $flags) = $spec;
573
                } elseif (null !== $spec) {
574
                    $order = $spec;
575
                }
576
577
                if (SORT_ASC === $order) {
578
                    asort($members, $flags);
579
                } else {
580
                    arsort($members, $flags);
581
                }
582
            }
583
            return (key($members) === 0) ? -1 : 1;
584
        }
585
586
        return 0;
587
    }
588
}
589