PageRenderTime 47ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/ORM/Map.php

http://github.com/silverstripe/sapphire
PHP | 446 lines | 222 code | 67 blank | 157 comment | 43 complexity | 0ed6bb8a8bdb1947bf81993b0562c23e MD5 | raw file
Possible License(s): BSD-3-Clause, MIT, CC-BY-3.0, GPL-2.0, AGPL-1.0, LGPL-2.1
  1. <?php
  2. namespace SilverStripe\ORM;
  3. use ArrayAccess;
  4. use Countable;
  5. use IteratorAggregate;
  6. use Iterator;
  7. /**
  8. * Creates a map from an SS_List by defining a key column and a value column.
  9. *
  10. * @package framework
  11. * @subpackage orm
  12. */
  13. class SS_Map implements ArrayAccess, Countable, IteratorAggregate {
  14. protected $list, $keyField, $valueField;
  15. /**
  16. * @see SS_Map::unshift()
  17. *
  18. * @var array $firstItems
  19. */
  20. protected $firstItems = array();
  21. /**
  22. * @see SS_Map::push()
  23. *
  24. * @var array $lastItems
  25. */
  26. protected $lastItems = array();
  27. /**
  28. * Construct a new map around an SS_list.
  29. *
  30. * @param $list The list to build a map from
  31. * @param $keyField The field to use as the key of each map entry
  32. * @param $valueField The field to use as the value of each map entry
  33. */
  34. public function __construct(SS_List $list, $keyField = "ID", $valueField = "Title") {
  35. $this->list = $list;
  36. $this->keyField = $keyField;
  37. $this->valueField = $valueField;
  38. }
  39. /**
  40. * Set the key field for this map.
  41. *
  42. * @var string $keyField
  43. */
  44. public function setKeyField($keyField) {
  45. $this->keyField = $keyField;
  46. }
  47. /**
  48. * Set the value field for this map.
  49. *
  50. * @var string $valueField
  51. */
  52. public function setValueField($valueField) {
  53. $this->valueField = $valueField;
  54. }
  55. /**
  56. * Return an array equivalent to this map.
  57. *
  58. * @return array
  59. */
  60. public function toArray() {
  61. $array = array();
  62. foreach($this as $k => $v) {
  63. $array[$k] = $v;
  64. }
  65. return $array;
  66. }
  67. /**
  68. * Return all the keys of this map.
  69. *
  70. * @return array
  71. */
  72. public function keys() {
  73. return array_keys($this->toArray());
  74. }
  75. /**
  76. * Return all the values of this map.
  77. *
  78. * @return array
  79. */
  80. public function values() {
  81. return array_values($this->toArray());
  82. }
  83. /**
  84. * Unshift an item onto the start of the map.
  85. *
  86. * Stores the value in addition to the {@link DataQuery} for the map.
  87. *
  88. * @var string $key
  89. * @var mixed $value
  90. */
  91. public function unshift($key, $value) {
  92. $oldItems = $this->firstItems;
  93. $this->firstItems = array(
  94. $key => $value
  95. );
  96. if($oldItems) {
  97. $this->firstItems = $this->firstItems + $oldItems;
  98. }
  99. return $this;
  100. }
  101. /**
  102. * Pushes an item onto the end of the map.
  103. *
  104. * @var string $key
  105. * @var mixed $value
  106. */
  107. public function push($key, $value) {
  108. $oldItems = $this->lastItems;
  109. $this->lastItems = array(
  110. $key => $value
  111. );
  112. if($oldItems) {
  113. $this->lastItems = $this->lastItems + $oldItems;
  114. }
  115. return $this;
  116. }
  117. // ArrayAccess
  118. /**
  119. * @var string $key
  120. *
  121. * @return boolean
  122. */
  123. public function offsetExists($key) {
  124. if(isset($this->firstItems[$key])) {
  125. return true;
  126. }
  127. if(isset($this->lastItems[$key])) {
  128. return true;
  129. }
  130. $record = $this->list->find($this->keyField, $key);
  131. return $record != null;
  132. }
  133. /**
  134. * @var string $key
  135. *
  136. * @return mixed
  137. */
  138. public function offsetGet($key) {
  139. if(isset($this->firstItems[$key])) {
  140. return $this->firstItems[$key];
  141. }
  142. if(isset($this->lastItems[$key])) {
  143. return $this->lastItems[$key];
  144. }
  145. $record = $this->list->find($this->keyField, $key);
  146. if($record) {
  147. $col = $this->valueField;
  148. return $record->$col;
  149. }
  150. return null;
  151. }
  152. /**
  153. * Sets a value in the map by a given key that has been set via
  154. * {@link SS_Map::push()} or {@link SS_Map::unshift()}
  155. *
  156. * Keys in the map cannot be set since these values are derived from a
  157. * {@link DataQuery} instance. In this case, use {@link SS_Map::toArray()}
  158. * and manipulate the resulting array.
  159. *
  160. * @var string $key
  161. * @var mixed $value
  162. */
  163. public function offsetSet($key, $value) {
  164. if(isset($this->firstItems[$key])) {
  165. return $this->firstItems[$key] = $value;
  166. }
  167. if(isset($this->lastItems[$key])) {
  168. return $this->lastItems[$key] = $value;
  169. }
  170. user_error(
  171. "SS_Map is read-only. Please use $map->push($key, $value) to append values",
  172. E_USER_ERROR
  173. );
  174. }
  175. /**
  176. * Removes a value in the map by a given key which has been added to the map
  177. * via {@link SS_Map::push()} or {@link SS_Map::unshift()}
  178. *
  179. * Keys in the map cannot be unset since these values are derived from a
  180. * {@link DataQuery} instance. In this case, use {@link SS_Map::toArray()}
  181. * and manipulate the resulting array.
  182. *
  183. * @var string $key
  184. * @var mixed $value
  185. */
  186. public function offsetUnset($key) {
  187. if(isset($this->firstItems[$key])) {
  188. unset($this->firstItems[$key]);
  189. return;
  190. }
  191. if(isset($this->lastItems[$key])) {
  192. unset($this->lastItems[$key]);
  193. return;
  194. }
  195. user_error(
  196. "SS_Map is read-only. Unset cannot be called on keys derived from the DataQuery",
  197. E_USER_ERROR
  198. );
  199. }
  200. /**
  201. * Returns an SS_Map_Iterator instance for iterating over the complete set
  202. * of items in the map.
  203. *
  204. * Satisfies the IteratorAggreagte interface.
  205. *
  206. * @return SS_Map_Iterator
  207. */
  208. public function getIterator() {
  209. return new SS_Map_Iterator(
  210. $this->list->getIterator(),
  211. $this->keyField,
  212. $this->valueField,
  213. $this->firstItems,
  214. $this->lastItems
  215. );
  216. }
  217. /**
  218. * Returns the count of items in the list including the additional items set
  219. * through {@link SS_Map::push()} and {@link SS_Map::unshift}.
  220. *
  221. * @return int
  222. */
  223. public function count() {
  224. return $this->list->count() +
  225. count($this->firstItems) +
  226. count($this->lastItems);
  227. }
  228. }
  229. /**
  230. * Builds a map iterator around an Iterator. Called by SS_Map
  231. *
  232. * @package framework
  233. * @subpackage orm
  234. */
  235. class SS_Map_Iterator implements Iterator {
  236. protected $items;
  237. protected $keyField, $titleField;
  238. protected $firstItemIdx = 0;
  239. protected $endItemIdx;
  240. protected $firstItems = array();
  241. protected $lastItems = array();
  242. protected $excludedItems = array();
  243. /**
  244. * @param Iterator $items The iterator to build this map from
  245. * @param string $keyField The field to use for the keys
  246. * @param string $titleField The field to use for the values
  247. * @param array $firstItems An optional map of items to show first
  248. * @param array $lastItems An optional map of items to show last
  249. */
  250. public function __construct(Iterator $items, $keyField, $titleField, $firstItems = null, $lastItems = null) {
  251. $this->items = $items;
  252. $this->keyField = $keyField;
  253. $this->titleField = $titleField;
  254. $this->endItemIdx = null;
  255. if($firstItems) {
  256. foreach($firstItems as $k => $v) {
  257. $this->firstItems[] = array($k,$v);
  258. $this->excludedItems[] = $k;
  259. }
  260. }
  261. if($lastItems) {
  262. foreach($lastItems as $k => $v) {
  263. $this->lastItems[] = array($k, $v);
  264. $this->excludedItems[] = $k;
  265. }
  266. }
  267. }
  268. /**
  269. * Rewind the Iterator to the first element.
  270. *
  271. * @return mixed
  272. */
  273. public function rewind() {
  274. $this->firstItemIdx = 0;
  275. $this->endItemIdx = null;
  276. $rewoundItem = $this->items->rewind();
  277. if(isset($this->firstItems[$this->firstItemIdx])) {
  278. return $this->firstItems[$this->firstItemIdx][1];
  279. } else {
  280. if($rewoundItem) {
  281. return $this->extractValue($rewoundItem, $this->titleField);
  282. } else if(!$this->items->valid() && $this->lastItems) {
  283. $this->endItemIdx = 0;
  284. return $this->lastItems[0][1];
  285. }
  286. }
  287. }
  288. /**
  289. * Return the current element.
  290. *
  291. * @return mixed
  292. */
  293. public function current() {
  294. if(($this->endItemIdx !== null) && isset($this->lastItems[$this->endItemIdx])) {
  295. return $this->lastItems[$this->endItemIdx][1];
  296. } else if(isset($this->firstItems[$this->firstItemIdx])) {
  297. return $this->firstItems[$this->firstItemIdx][1];
  298. }
  299. return $this->extractValue($this->items->current(), $this->titleField);
  300. }
  301. /**
  302. * Extracts a value from an item in the list, where the item is either an
  303. * object or array.
  304. *
  305. * @param array|object $item
  306. * @param string $key
  307. * @return mixed
  308. */
  309. protected function extractValue($item, $key) {
  310. if (is_object($item)) {
  311. if(method_exists($item, 'hasMethod') && $item->hasMethod($key)) {
  312. return $item->{$key}();
  313. }
  314. return $item->{$key};
  315. } else {
  316. if (array_key_exists($key, $item)) {
  317. return $item[$key];
  318. }
  319. }
  320. }
  321. /**
  322. * Return the key of the current element.
  323. *
  324. * @return string
  325. */
  326. public function key() {
  327. if(($this->endItemIdx !== null) && isset($this->lastItems[$this->endItemIdx])) {
  328. return $this->lastItems[$this->endItemIdx][0];
  329. } else if(isset($this->firstItems[$this->firstItemIdx])) {
  330. return $this->firstItems[$this->firstItemIdx][0];
  331. } else {
  332. return $this->extractValue($this->items->current(), $this->keyField);
  333. }
  334. }
  335. /**
  336. * Move forward to next element.
  337. *
  338. * @return mixed
  339. */
  340. public function next() {
  341. $this->firstItemIdx++;
  342. if(isset($this->firstItems[$this->firstItemIdx])) {
  343. return $this->firstItems[$this->firstItemIdx][1];
  344. } else {
  345. if(!isset($this->firstItems[$this->firstItemIdx-1])) {
  346. $this->items->next();
  347. }
  348. if($this->excludedItems) {
  349. while(($c = $this->items->current()) && in_array($c->{$this->keyField}, $this->excludedItems, true)) {
  350. $this->items->next();
  351. }
  352. }
  353. }
  354. if(!$this->items->valid()) {
  355. // iterator has passed the preface items, off the end of the items
  356. // list. Track through the end items to go through to the next
  357. if($this->endItemIdx === null) {
  358. $this->endItemIdx = -1;
  359. }
  360. $this->endItemIdx++;
  361. if(isset($this->lastItems[$this->endItemIdx])) {
  362. return $this->lastItems[$this->endItemIdx];
  363. }
  364. return false;
  365. }
  366. }
  367. /**
  368. * Checks if current position is valid.
  369. *
  370. * @return boolean
  371. */
  372. public function valid() {
  373. return (
  374. (isset($this->firstItems[$this->firstItemIdx])) ||
  375. (($this->endItemIdx !== null) && isset($this->lastItems[$this->endItemIdx])) ||
  376. $this->items->valid()
  377. );
  378. }
  379. }