1: <?php
2:
3: /**
4: * Jyxo PHP Library
5: *
6: * LICENSE
7: *
8: * This source file is subject to the new BSD license that is bundled
9: * with this package in the file license.txt.
10: * It is also available through the world-wide-web at this URL:
11: * https://github.com/jyxo/php/blob/master/license.txt
12: */
13:
14: namespace Jyxo\Spl;
15:
16: /**
17: * Iterator which applies a callback over results (lazy-loaded calls).
18: * Supports iteration over both \Traversable and array.
19: *
20: * @category Jyxo
21: * @package Jyxo\Spl
22: * @copyright Copyright (c) 2005-2011 Jyxo, s.r.o.
23: * @license https://github.com/jyxo/php/blob/master/license.txt
24: * @author Jakub Tománek
25: */
26: class MapIterator implements \Countable, \Jyxo\Spl\ArrayCopy, \OuterIterator, \SeekableIterator
27: {
28: /**
29: * Source data we iterate over.
30: *
31: * @var \Iterator
32: */
33: private $iterator;
34:
35: /**
36: * Mapping callback applied to each item.
37: *
38: * @var callback|\Closure
39: */
40: private $map;
41:
42: /**
43: * Constructor.
44: *
45: * @param array|\Iterator|\IteratorAggregate $data Source data
46: * @param callback|\Closure $map Applied callback or closure
47: * @throws \InvalidArgumentException Invalid source data or callback is not callable
48: */
49: public function __construct($data, $map)
50: {
51: if (is_array($data)) {
52: $this->iterator = new \ArrayIterator($data);
53: } elseif ($data instanceof \IteratorAggregate) {
54: $this->iterator = $data->getIterator();
55: } elseif ($data instanceof \Iterator) {
56: $this->iterator = $data;
57: } else {
58: throw new \InvalidArgumentException('Supplied data argument is not traversable.');
59: }
60: if (!is_callable($map)) {
61: throw new \InvalidArgumentException('Supplied callback is not callable.');
62: }
63:
64: $this->map = $map;
65: }
66:
67: /**
68: * Returns count of source data.
69: *
70: * @return integer
71: */
72: public function count()
73: {
74: $count = 0;
75: if ($this->iterator instanceof \Countable) {
76: $count = count($this->iterator);
77: } else {
78: $count = iterator_count($this->iterator);
79: }
80: return $count;
81: }
82:
83: /**
84: * Rewinds the iterator to the beginning.
85: */
86: public function rewind()
87: {
88: $this->iterator->rewind();
89: }
90:
91: /**
92: * Advances the internal pointer.
93: */
94: public function next()
95: {
96: $this->iterator->next();
97: }
98:
99: /**
100: * Returns if current pointer position is valid.
101: *
102: * @return boolean
103: */
104: public function valid()
105: {
106: return $this->iterator->valid();
107: }
108:
109: /**
110: * Returns current data.
111: *
112: * @return mixed
113: */
114: public function current()
115: {
116: return $this->map($this->iterator->current());
117: }
118:
119: /**
120: * Returns current key.
121: *
122: * @return integer
123: */
124: public function key()
125: {
126: return $this->iterator->key();
127: }
128:
129: /**
130: * Converts source data to result using a callback function.
131: *
132: * @param mixed $item Source data
133: * @return mixed
134: */
135: private function map($item)
136: {
137: return call_user_func($this->map, $item);
138: }
139:
140: /**
141: * Returns all data as an array.
142: *
143: * @return array
144: */
145: public function toArray()
146: {
147: return array_map($this->map, iterator_to_array($this->iterator));
148: }
149:
150: /**
151: * Returns inner iterator (works even when constructed with array data)
152: *
153: * @return \Iterator
154: */
155: public function getInnerIterator()
156: {
157: return $this->iterator;
158: }
159:
160: /**
161: * Seeks to defined position. Does NOT throw {@link \OutOfBoundsException}.
162: *
163: * @param integer $position New position
164: */
165: public function seek($position)
166: {
167: if ($this->iterator instanceof \SeekableIterator) {
168: try {
169: $this->iterator->seek($position);
170: } catch (\OutOfBoundsException $e) {
171: // Skipped on purpose, I don't think it's necessary
172: // If you'd like to have this exception throw, remove this try-catch and add to 'else' block
173: // If (!$this->valid()) { throw new \OutOfBoundsException('Invalid seek position'); };
174: }
175: } else {
176: $this->rewind();
177: for ($i = 0; $i < $position; $i++) {
178: $this->next();
179: }
180: }
181: }
182: }
183: