CMap.php 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. <?php
  2. /**
  3. * This file contains classes implementing Map 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. * CMap implements a collection that takes key-value pairs.
  12. *
  13. * You can access, add or remove an item with a key by using
  14. * {@link itemAt}, {@link add}, and {@link remove}.
  15. * To get the number of the items in the map, use {@link getCount}.
  16. * CMap can also be used like a regular array as follows,
  17. * <pre>
  18. * $map[$key]=$value; // add a key-value pair
  19. * unset($map[$key]); // remove the value with the specified key
  20. * if(isset($map[$key])) // if the map contains the key
  21. * foreach($map as $key=>$value) // traverse the items in the map
  22. * $n=count($map); // returns the number of items in the map
  23. * </pre>
  24. *
  25. * @property boolean $readOnly Whether this map is read-only or not. Defaults to false.
  26. * @property CMapIterator $iterator An iterator for traversing the items in the list.
  27. * @property integer $count The number of items in the map.
  28. * @property array $keys The key list.
  29. *
  30. * @author Qiang Xue <qiang.xue@gmail.com>
  31. * @package system.collections
  32. * @since 1.0
  33. */
  34. class CMap extends CComponent implements IteratorAggregate,ArrayAccess,Countable
  35. {
  36. /**
  37. * @var array internal data storage
  38. */
  39. private $_d=array();
  40. /**
  41. * @var boolean whether this list is read-only
  42. */
  43. private $_r=false;
  44. /**
  45. * Constructor.
  46. * Initializes the list with an array or an iterable object.
  47. * @param array $data the initial data. Default is null, meaning no initialization.
  48. * @param boolean $readOnly whether the list is read-only
  49. * @throws CException If data is not null and neither an array nor an iterator.
  50. */
  51. public function __construct($data=null,$readOnly=false)
  52. {
  53. if($data!==null)
  54. $this->copyFrom($data);
  55. $this->setReadOnly($readOnly);
  56. }
  57. /**
  58. * @return boolean whether this map is read-only or not. Defaults to false.
  59. */
  60. public function getReadOnly()
  61. {
  62. return $this->_r;
  63. }
  64. /**
  65. * @param boolean $value whether this list is read-only or not
  66. */
  67. protected function setReadOnly($value)
  68. {
  69. $this->_r=$value;
  70. }
  71. /**
  72. * Returns an iterator for traversing the items in the list.
  73. * This method is required by the interface IteratorAggregate.
  74. * @return CMapIterator an iterator for traversing the items in the list.
  75. */
  76. public function getIterator()
  77. {
  78. return new CMapIterator($this->_d);
  79. }
  80. /**
  81. * Returns the number of items in the map.
  82. * This method is required by Countable interface.
  83. * @return integer number of items in the map.
  84. */
  85. public function count()
  86. {
  87. return $this->getCount();
  88. }
  89. /**
  90. * Returns the number of items in the map.
  91. * @return integer the number of items in the map
  92. */
  93. public function getCount()
  94. {
  95. return count($this->_d);
  96. }
  97. /**
  98. * @return array the key list
  99. */
  100. public function getKeys()
  101. {
  102. return array_keys($this->_d);
  103. }
  104. /**
  105. * Returns the item with the specified key.
  106. * This method is exactly the same as {@link offsetGet}.
  107. * @param mixed $key the key
  108. * @return mixed the element at the offset, null if no element is found at the offset
  109. */
  110. public function itemAt($key)
  111. {
  112. if(isset($this->_d[$key]))
  113. return $this->_d[$key];
  114. else
  115. return null;
  116. }
  117. /**
  118. * Adds an item into the map.
  119. * Note, if the specified key already exists, the old value will be overwritten.
  120. * @param mixed $key key
  121. * @param mixed $value value
  122. * @throws CException if the map is read-only
  123. */
  124. public function add($key,$value)
  125. {
  126. if(!$this->_r)
  127. {
  128. if($key===null)
  129. $this->_d[]=$value;
  130. else
  131. $this->_d[$key]=$value;
  132. }
  133. else
  134. throw new CException(Yii::t('yii','The map is read only.'));
  135. }
  136. /**
  137. * Removes an item from the map by its key.
  138. * @param mixed $key the key of the item to be removed
  139. * @return mixed the removed value, null if no such key exists.
  140. * @throws CException if the map is read-only
  141. */
  142. public function remove($key)
  143. {
  144. if(!$this->_r)
  145. {
  146. if(isset($this->_d[$key]))
  147. {
  148. $value=$this->_d[$key];
  149. unset($this->_d[$key]);
  150. return $value;
  151. }
  152. else
  153. {
  154. // it is possible the value is null, which is not detected by isset
  155. unset($this->_d[$key]);
  156. return null;
  157. }
  158. }
  159. else
  160. throw new CException(Yii::t('yii','The map is read only.'));
  161. }
  162. /**
  163. * Removes all items in the map.
  164. */
  165. public function clear()
  166. {
  167. foreach(array_keys($this->_d) as $key)
  168. $this->remove($key);
  169. }
  170. /**
  171. * @param mixed $key the key
  172. * @return boolean whether the map contains an item with the specified key
  173. */
  174. public function contains($key)
  175. {
  176. return isset($this->_d[$key]) || array_key_exists($key,$this->_d);
  177. }
  178. /**
  179. * @return array the list of items in array
  180. */
  181. public function toArray()
  182. {
  183. return $this->_d;
  184. }
  185. /**
  186. * Copies iterable data into the map.
  187. * Note, existing data in the map will be cleared first.
  188. * @param mixed $data the data to be copied from, must be an array or object implementing Traversable
  189. * @throws CException If data is neither an array nor an iterator.
  190. */
  191. public function copyFrom($data)
  192. {
  193. if(is_array($data) || $data instanceof Traversable)
  194. {
  195. if($this->getCount()>0)
  196. $this->clear();
  197. if($data instanceof CMap)
  198. $data=$data->_d;
  199. foreach($data as $key=>$value)
  200. $this->add($key,$value);
  201. }
  202. elseif($data!==null)
  203. throw new CException(Yii::t('yii','Map data must be an array or an object implementing Traversable.'));
  204. }
  205. /**
  206. * Merges iterable data into the map.
  207. *
  208. * Existing elements in the map will be overwritten if their keys are the same as those in the source.
  209. * If the merge is recursive, the following algorithm is performed:
  210. * <ul>
  211. * <li>the map data is saved as $a, and the source data is saved as $b;</li>
  212. * <li>if $a and $b both have an array indexed at the same string key, the arrays will be merged using this algorithm;</li>
  213. * <li>any integer-indexed elements in $b will be appended to $a and reindexed accordingly;</li>
  214. * <li>any string-indexed elements in $b will overwrite elements in $a with the same index;</li>
  215. * </ul>
  216. *
  217. * @param mixed $data the data to be merged with, must be an array or object implementing Traversable
  218. * @param boolean $recursive whether the merging should be recursive.
  219. *
  220. * @throws CException If data is neither an array nor an iterator.
  221. */
  222. public function mergeWith($data,$recursive=true)
  223. {
  224. if(is_array($data) || $data instanceof Traversable)
  225. {
  226. if($data instanceof CMap)
  227. $data=$data->_d;
  228. if($recursive)
  229. {
  230. if($data instanceof Traversable)
  231. {
  232. $d=array();
  233. foreach($data as $key=>$value)
  234. $d[$key]=$value;
  235. $this->_d=self::mergeArray($this->_d,$d);
  236. }
  237. else
  238. $this->_d=self::mergeArray($this->_d,$data);
  239. }
  240. else
  241. {
  242. foreach($data as $key=>$value)
  243. $this->add($key,$value);
  244. }
  245. }
  246. elseif($data!==null)
  247. throw new CException(Yii::t('yii','Map data must be an array or an object implementing Traversable.'));
  248. }
  249. /**
  250. * Merges two or more arrays into one recursively.
  251. * If each array has an element with the same string key value, the latter
  252. * will overwrite the former (different from array_merge_recursive).
  253. * Recursive merging will be conducted if both arrays have an element of array
  254. * type and are having the same key.
  255. * For integer-keyed elements, the elements from the latter array will
  256. * be appended to the former array.
  257. * @param array $a array to be merged to
  258. * @param array $b array to be merged from. You can specify additional
  259. * arrays via third argument, fourth argument etc.
  260. * @return array the merged array (the original arrays are not changed.)
  261. * @see mergeWith
  262. */
  263. public static function mergeArray($a,$b)
  264. {
  265. $args=func_get_args();
  266. $res=array_shift($args);
  267. while(!empty($args))
  268. {
  269. $next=array_shift($args);
  270. foreach($next as $k => $v)
  271. {
  272. if(is_integer($k))
  273. isset($res[$k]) ? $res[]=$v : $res[$k]=$v;
  274. elseif(is_array($v) && isset($res[$k]) && is_array($res[$k]))
  275. $res[$k]=self::mergeArray($res[$k],$v);
  276. else
  277. $res[$k]=$v;
  278. }
  279. }
  280. return $res;
  281. }
  282. /**
  283. * Returns whether there is an element at the specified offset.
  284. * This method is required by the interface ArrayAccess.
  285. * @param mixed $offset the offset to check on
  286. * @return boolean
  287. */
  288. public function offsetExists($offset)
  289. {
  290. return $this->contains($offset);
  291. }
  292. /**
  293. * Returns the element at the specified offset.
  294. * This method is required by the interface ArrayAccess.
  295. * @param integer $offset the offset to retrieve element.
  296. * @return mixed the element at the offset, null if no element is found at the offset
  297. */
  298. public function offsetGet($offset)
  299. {
  300. return $this->itemAt($offset);
  301. }
  302. /**
  303. * Sets the element at the specified offset.
  304. * This method is required by the interface ArrayAccess.
  305. * @param integer $offset the offset to set element
  306. * @param mixed $item the element value
  307. */
  308. public function offsetSet($offset,$item)
  309. {
  310. $this->add($offset,$item);
  311. }
  312. /**
  313. * Unsets the element at the specified offset.
  314. * This method is required by the interface ArrayAccess.
  315. * @param mixed $offset the offset to unset element
  316. */
  317. public function offsetUnset($offset)
  318. {
  319. $this->remove($offset);
  320. }
  321. }