1: <?php
2: /**
3: * This file contains classes implementing list feature.
4: *
5: * @author Qiang Xue <qiang.xue@gmail.com>
6: * @link http://www.yiiframework.com/
7: * @copyright 2008-2013 Yii Software LLC
8: * @license http://www.yiiframework.com/license/
9: */
10:
11: /**
12: * CList implements an integer-indexed collection class.
13: *
14: * You can access, append, insert, remove an item by using
15: * {@link itemAt}, {@link add}, {@link insertAt}, {@link remove}, and {@link removeAt}.
16: * To get the number of the items in the list, use {@link getCount}.
17: * CList can also be used like a regular array as follows,
18: * <pre>
19: * $list[]=$item; // append at the end
20: * $list[$index]=$item; // $index must be between 0 and $list->Count
21: * unset($list[$index]); // remove the item at $index
22: * if(isset($list[$index])) // if the list has an item at $index
23: * foreach($list as $index=>$item) // traverse each item in the list
24: * $n=count($list); // returns the number of items in the list
25: * </pre>
26: *
27: * To extend CList by doing additional operations with each addition or removal
28: * operation (e.g. performing type check), override {@link insertAt()}, and {@link removeAt()}.
29: *
30: * @property boolean $readOnly Whether this list is read-only or not. Defaults to false.
31: * @property Iterator $iterator An iterator for traversing the items in the list.
32: * @property integer $count The number of items in the list.
33: *
34: * @author Qiang Xue <qiang.xue@gmail.com>
35: * @package system.collections
36: * @since 1.0
37: */
38: class CList extends CComponent implements IteratorAggregate,ArrayAccess,Countable
39: {
40: /**
41: * @var array internal data storage
42: */
43: private $_d=array();
44: /**
45: * @var integer number of items
46: */
47: private $_c=0;
48: /**
49: * @var boolean whether this list is read-only
50: */
51: private $_r=false;
52:
53: /**
54: * Constructor.
55: * Initializes the list with an array or an iterable object.
56: * @param array $data the initial data. Default is null, meaning no initialization.
57: * @param boolean $readOnly whether the list is read-only
58: * @throws CException If data is not null and neither an array nor an iterator.
59: */
60: public function __construct($data=null,$readOnly=false)
61: {
62: if($data!==null)
63: $this->copyFrom($data);
64: $this->setReadOnly($readOnly);
65: }
66:
67: /**
68: * @return boolean whether this list is read-only or not. Defaults to false.
69: */
70: public function getReadOnly()
71: {
72: return $this->_r;
73: }
74:
75: /**
76: * @param boolean $value whether this list is read-only or not
77: */
78: protected function setReadOnly($value)
79: {
80: $this->_r=$value;
81: }
82:
83: /**
84: * Returns an iterator for traversing the items in the list.
85: * This method is required by the interface IteratorAggregate.
86: * @return Iterator an iterator for traversing the items in the list.
87: */
88: public function getIterator()
89: {
90: return new CListIterator($this->_d);
91: }
92:
93: /**
94: * Returns the number of items in the list.
95: * This method is required by Countable interface.
96: * @return integer number of items in the list.
97: */
98: public function count()
99: {
100: return $this->getCount();
101: }
102:
103: /**
104: * Returns the number of items in the list.
105: * @return integer the number of items in the list
106: */
107: public function getCount()
108: {
109: return $this->_c;
110: }
111:
112: /**
113: * Returns the item at the specified offset.
114: * This method is exactly the same as {@link offsetGet}.
115: * @param integer $index the index of the item
116: * @return mixed the item at the index
117: * @throws CException if the index is out of the range
118: */
119: public function itemAt($index)
120: {
121: if(isset($this->_d[$index]))
122: return $this->_d[$index];
123: elseif($index>=0 && $index<$this->_c) // in case the value is null
124: return $this->_d[$index];
125: else
126: throw new CException(Yii::t('yii','List index "{index}" is out of bound.',
127: array('{index}'=>$index)));
128: }
129:
130: /**
131: * Appends an item at the end of the list.
132: * @param mixed $item new item
133: * @return integer the zero-based index at which the item is added
134: */
135: public function add($item)
136: {
137: $this->insertAt($this->_c,$item);
138: return $this->_c-1;
139: }
140:
141: /**
142: * Inserts an item at the specified position.
143: * Original item at the position and the next items
144: * will be moved one step towards the end.
145: * @param integer $index the specified position.
146: * @param mixed $item new item
147: * @throws CException If the index specified exceeds the bound or the list is read-only
148: */
149: public function insertAt($index,$item)
150: {
151: if(!$this->_r)
152: {
153: if($index===$this->_c)
154: $this->_d[$this->_c++]=$item;
155: elseif($index>=0 && $index<$this->_c)
156: {
157: array_splice($this->_d,$index,0,array($item));
158: $this->_c++;
159: }
160: else
161: throw new CException(Yii::t('yii','List index "{index}" is out of bound.',
162: array('{index}'=>$index)));
163: }
164: else
165: throw new CException(Yii::t('yii','The list is read only.'));
166: }
167:
168: /**
169: * Removes an item from the list.
170: * The list will first search for the item.
171: * The first item found will be removed from the list.
172: * @param mixed $item the item to be removed.
173: * @return integer the index at which the item is being removed
174: * @throws CException If the item does not exist
175: */
176: public function remove($item)
177: {
178: if(($index=$this->indexOf($item))>=0)
179: {
180: $this->removeAt($index);
181: return $index;
182: }
183: else
184: return false;
185: }
186:
187: /**
188: * Removes an item at the specified position.
189: * @param integer $index the index of the item to be removed.
190: * @return mixed the removed item.
191: * @throws CException If the index specified exceeds the bound or the list is read-only
192: */
193: public function removeAt($index)
194: {
195: if(!$this->_r)
196: {
197: if($index>=0 && $index<$this->_c)
198: {
199: $this->_c--;
200: if($index===$this->_c)
201: return array_pop($this->_d);
202: else
203: {
204: $item=$this->_d[$index];
205: array_splice($this->_d,$index,1);
206: return $item;
207: }
208: }
209: else
210: throw new CException(Yii::t('yii','List index "{index}" is out of bound.',
211: array('{index}'=>$index)));
212: }
213: else
214: throw new CException(Yii::t('yii','The list is read only.'));
215: }
216:
217: /**
218: * Removes all items in the list.
219: */
220: public function clear()
221: {
222: for($i=$this->_c-1;$i>=0;--$i)
223: $this->removeAt($i);
224: }
225:
226: /**
227: * @param mixed $item the item
228: * @return boolean whether the list contains the item
229: */
230: public function contains($item)
231: {
232: return $this->indexOf($item)>=0;
233: }
234:
235: /**
236: * @param mixed $item the item
237: * @return integer the index of the item in the list (0 based), -1 if not found.
238: */
239: public function indexOf($item)
240: {
241: if(($index=array_search($item,$this->_d,true))!==false)
242: return $index;
243: else
244: return -1;
245: }
246:
247: /**
248: * @return array the list of items in array
249: */
250: public function toArray()
251: {
252: return $this->_d;
253: }
254:
255: /**
256: * Copies iterable data into the list.
257: * Note, existing data in the list will be cleared first.
258: * @param mixed $data the data to be copied from, must be an array or object implementing Traversable
259: * @throws CException If data is neither an array nor a Traversable.
260: */
261: public function copyFrom($data)
262: {
263: if(is_array($data) || ($data instanceof Traversable))
264: {
265: if($this->_c>0)
266: $this->clear();
267: if($data instanceof CList)
268: $data=$data->_d;
269: foreach($data as $item)
270: $this->add($item);
271: }
272: elseif($data!==null)
273: throw new CException(Yii::t('yii','List data must be an array or an object implementing Traversable.'));
274: }
275:
276: /**
277: * Merges iterable data into the map.
278: * New data will be appended to the end of the existing data.
279: * @param mixed $data the data to be merged with, must be an array or object implementing Traversable
280: * @throws CException If data is neither an array nor an iterator.
281: */
282: public function mergeWith($data)
283: {
284: if(is_array($data) || ($data instanceof Traversable))
285: {
286: if($data instanceof CList)
287: $data=$data->_d;
288: foreach($data as $item)
289: $this->add($item);
290: }
291: elseif($data!==null)
292: throw new CException(Yii::t('yii','List data must be an array or an object implementing Traversable.'));
293: }
294:
295: /**
296: * Returns whether there is an item at the specified offset.
297: * This method is required by the interface ArrayAccess.
298: * @param integer $offset the offset to check on
299: * @return boolean
300: */
301: public function offsetExists($offset)
302: {
303: return ($offset>=0 && $offset<$this->_c);
304: }
305:
306: /**
307: * Returns the item at the specified offset.
308: * This method is required by the interface ArrayAccess.
309: * @param integer $offset the offset to retrieve item.
310: * @return mixed the item at the offset
311: * @throws CException if the offset is invalid
312: */
313: public function offsetGet($offset)
314: {
315: return $this->itemAt($offset);
316: }
317:
318: /**
319: * Sets the item at the specified offset.
320: * This method is required by the interface ArrayAccess.
321: * @param integer $offset the offset to set item
322: * @param mixed $item the item value
323: */
324: public function offsetSet($offset,$item)
325: {
326: if($offset===null || $offset===$this->_c)
327: $this->insertAt($this->_c,$item);
328: else
329: {
330: $this->removeAt($offset);
331: $this->insertAt($offset,$item);
332: }
333: }
334:
335: /**
336: * Unsets the item at the specified offset.
337: * This method is required by the interface ArrayAccess.
338: * @param integer $offset the offset to unset item
339: */
340: public function offsetUnset($offset)
341: {
342: $this->removeAt($offset);
343: }
344: }
345:
346: