Passed
Push — develop ( ead434...0a8e77 )
by Vasil
03:53
created

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