1<?php
2/** @file multipleiterator.inc
3 * @ingroup SPL
4 * @brief class MultipleIterator
5 * @author  Johannes Schlueter
6 * @author  Marcus Boerger
7 * @date    2008 - 2009
8 *
9 * SPL - Standard PHP Library
10 */
11
12/** @ingroup SPL
13 * @brief   Iterator that iterates over several iterators one after the other
14 * @author  Johannes Schlueter
15 * @author  Marcus Boerger
16 * @version 1.0
17 * @since PHP 5.3
18 */
19class MultipleIterator implements Iterator
20{
21	/** Inner Iterators */
22	private $iterators;
23
24	/** Flags: const MIT_* */
25	private $flags;
26
27	/** do not require all sub iterators to be valid in iteration */
28	const MIT_NEED_ANY = 0;
29
30	/** require all sub iterators to be valid in iteration */
31	const MIT_NEED_ALL  = 1;
32
33	/** keys are created from sub iterators position */
34	const MIT_KEYS_NUMERIC  = 0;
35
36	/** keys are created from sub iterators associated infromation */
37	const MIT_KEYS_ASSOC  = 2;
38
39	/** Construct a new empty MultipleIterator
40	* @param flags MIT_* flags
41	*/
42	public function __construct($flags = self::MIT_NEED_ALL|self::MIT_KEYS_NUMERIC)
43	{
44		$this->iterators = new SplObjectStorage();
45		$this->flags = $flags;
46	}
47
48	/** @return current flags MIT_* */
49	public function getFlags()
50	{
51		return $this->flags;
52	}
53
54	/** @param $flags new flags. */
55	public function setFlags($flags)
56	{
57		$this->flags = $flags;
58	}
59
60	/** @param $iter new Iterator to attach.
61	* @param $inf associative info forIteraotr, must be NULL, integer or string
62	*
63	* @throws IllegalValueException if a inf is none of NULL, integer or string
64	* @throws IllegalValueException if a inf is already an associated info
65	*/
66	public function attachIterator(Iterator $iter, $inf = NULL)
67	{
68
69		if (!is_null($inf))
70		{
71			if (!is_int($inf) && !is_string($inf))
72			{
73				throw new IllegalValueException('Inf must be NULL, integer or string');
74			}
75			foreach($this->iterators as $iter)
76			{
77				if ($inf == $this->iterators->getInfo())
78				{
79					throw new IllegalValueException('Key duplication error');
80				}
81			}
82		}
83		$this->iterators->attach($iter, $inf);
84	}
85
86	/** @param $iter attached Iterator that should be detached. */
87	public function detachIterator(Iterator $iter)
88	{
89		$this->iterators->detach($iter);
90	}
91
92	/** @param $iter Iterator to check
93	* @return whether $iter is attached or not
94	*/
95	public function containsIterator(Iterator $iter)
96	{
97		return $this->iterator->contains($iter);
98	}
99
100	/** @return number of attached Iterator instances. */
101	public function countIterators()
102	{
103		return $this->iterators->count();
104	}
105
106	/** Rewind all attached Iterator instances. */
107	public function rewind()
108	{
109		foreach($this->iterators as $iter)
110		{
111			$iter->rewind();
112		}
113	}
114
115	/**
116	* @return whether all or one sub iterator is valid depending on flags.
117	* In mode MIT_NEED_ALL we expect all sub iterators to be valid and
118	* return flase on the first non valid one. If that flag is not set we
119	* return true on the first valid sub iterator found. If no Iterator
120	* is attached, we always return false.
121	*/
122	public function valid()
123	{
124		if (!sizeof($this->iterators)) {
125			return false;
126		}
127		// The following code is an optimized version that executes as few
128		// valid() calls as necessary and that only checks the flags once.
129		$expect = $this->flags & self::MIT_NEED_ALL ? true : false;
130		foreach($this->iterators as $iter)
131		{
132			if ($expect != $iter->valid())
133			{
134				return !$expect;
135			}
136		}
137		return $expect;
138	}
139
140	/** Move all attached Iterator instances forward. That is invoke
141	* their next() method regardless of their state.
142	*/
143	public function next()
144	{
145		foreach($this->iterators as $iter)
146		{
147			$iter->next();
148		}
149	}
150
151	/** @return false if no sub Iterator is attached and an array of
152	* all registered Iterator instances current() result.
153	* @throws RuntimeException      if mode MIT_NEED_ALL is set and at least one
154	*                               attached Iterator is not valid().
155	* @throws IllegalValueException if a key is NULL and MIT_KEYS_ASSOC is set.
156	*/
157	public function current()
158	{
159		if (!sizeof($this->iterators))
160		{
161			return false;
162		}
163		$retval = array();
164		foreach($this->iterators as $iter)
165		{
166			if ($iter->valid())
167			{
168				if ($this->flags & self::MIT_KEYS_ASSOC)
169				{
170					$key = $this->iterators->getInfo();
171					if (is_null($key))
172					{
173						throw new IllegalValueException('Sub-Iterator is associated with NULL');
174					}
175					$retval[$key] = $iter->current();
176				}
177				else
178				{
179					$retval[] = $iter->current();
180				}
181			}
182			else if ($this->flags & self::MIT_NEED_ALL)
183			{
184				throw new RuntimeException('Called current() with non valid sub iterator');
185			}
186			else
187			{
188				$retval[] = NULL;
189			}
190		}
191		return $retval;
192	}
193
194	/** @return false if no sub Iterator is attached and an array of
195	* all registered Iterator instances key() result.
196	* @throws LogicException if mode MIT_NEED_ALL is set and at least one
197	*         attached Iterator is not valid().
198	*/
199	public function key()
200	{
201		if (!sizeof($this->iterators))
202		{
203			return false;
204		}
205		$retval = array();
206		foreach($this->iterators as $iter)
207		{
208			if ($iter->valid())
209			{
210				$retval[] = $iter->key();
211			}
212			else if ($this->flags & self::MIT_NEED_ALL)
213			{
214				throw new LogicException('Called key() with non valid sub iterator');
215			}
216			else
217			{
218				$retval[] = NULL;
219			}
220		}
221		return $retval;
222	}
223}
224