PageRenderTime 49ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/library/Zend/Navigation/Container.php

https://github.com/rettal/zf2
PHP | 508 lines | 223 code | 55 blank | 230 comment | 25 complexity | 6f0e1af9a83f1c6d1bbc1559101a099b MD5 | raw file
  1. <?php
  2. /**
  3. * Zend Framework
  4. *
  5. * LICENSE
  6. *
  7. * This source file is subject to the new BSD license that is bundled
  8. * with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://framework.zend.com/license/new-bsd
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@zend.com so we can send you a copy immediately.
  14. *
  15. * @category Zend
  16. * @package Zend_Navigation
  17. * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
  18. * @license http://framework.zend.com/license/new-bsd New BSD License
  19. * @version $Id$
  20. */
  21. /**
  22. * @namespace
  23. */
  24. namespace Zend\Navigation;
  25. use Zend\Config;
  26. /**
  27. * Zend_Navigation_Container
  28. *
  29. * Container class for Zend_Navigation_Page classes.
  30. *
  31. * @uses Countable
  32. * @uses RecursiveIterator
  33. * @uses RecursiveIteratorIterator
  34. * @uses \Zend\Navigation\Exception
  35. * @uses \Zend\Navigation\AbstractPage
  36. * @category Zend
  37. * @package Zend_Navigation
  38. * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
  39. * @license http://framework.zend.com/license/new-bsd New BSD License
  40. */
  41. abstract class Container implements \RecursiveIterator, \Countable
  42. {
  43. /**
  44. * Contains sub pages
  45. *
  46. * @var array
  47. */
  48. protected $_pages = array();
  49. /**
  50. * An index that contains the order in which to iterate pages
  51. *
  52. * @var array
  53. */
  54. protected $_index = array();
  55. /**
  56. * Whether index is dirty and needs to be re-arranged
  57. *
  58. * @var bool
  59. */
  60. protected $_dirtyIndex = false;
  61. // Internal methods:
  62. /**
  63. * Sorts the page index according to page order
  64. *
  65. * @return void
  66. */
  67. protected function _sort()
  68. {
  69. if ($this->_dirtyIndex) {
  70. $newIndex = array();
  71. $index = 0;
  72. foreach ($this->_pages as $hash => $page) {
  73. $order = $page->getOrder();
  74. if ($order === null) {
  75. $newIndex[$hash] = $index;
  76. $index++;
  77. } else {
  78. $newIndex[$hash] = $order;
  79. }
  80. }
  81. asort($newIndex);
  82. $this->_index = $newIndex;
  83. $this->_dirtyIndex = false;
  84. }
  85. }
  86. // Public methods:
  87. /**
  88. * Notifies container that the order of pages are updated
  89. *
  90. * @return void
  91. */
  92. public function notifyOrderUpdated()
  93. {
  94. $this->_dirtyIndex = true;
  95. }
  96. /**
  97. * Adds a page to the container
  98. *
  99. * This method will inject the container as the given page's parent by
  100. * calling {@link Zend_Navigation_Page::setParent()}.
  101. *
  102. * @param Zend_Navigation_Page|array|\Zend\Config\Config $page page to add
  103. * @return \Zend\Navigation\Container fluent interface,
  104. * returns self
  105. * @throws \Zend\Navigation\Exception if page is invalid
  106. */
  107. public function addPage($page)
  108. {
  109. if ($page === $this) {
  110. throw new Exception(
  111. 'A page cannot have itself as a parent');
  112. }
  113. if (is_array($page) || $page instanceof Config\Config) {
  114. $page = AbstractPage::factory($page);
  115. } elseif (!$page instanceof AbstractPage) {
  116. throw new Exception(
  117. 'Invalid argument: $page must be an instance of ' .
  118. 'Zend_Navigation_Page or Zend_Config, or an array');
  119. }
  120. $hash = $page->hashCode();
  121. if (array_key_exists($hash, $this->_index)) {
  122. // page is already in container
  123. return $this;
  124. }
  125. // adds page to container and sets dirty flag
  126. $this->_pages[$hash] = $page;
  127. $this->_index[$hash] = $page->getOrder();
  128. $this->_dirtyIndex = true;
  129. // inject self as page parent
  130. $page->setParent($this);
  131. return $this;
  132. }
  133. /**
  134. * Adds several pages at once
  135. *
  136. * @param array|\Zend\Config\Config $pages pages to add
  137. * @return \Zend\Navigation\Container fluent interface, returns self
  138. * @throws Zend_Navigation_Exception if $pages is not array or \Zend\Config\Config
  139. */
  140. public function addPages($pages)
  141. {
  142. if ($pages instanceof Config\Config) {
  143. $pages = $pages->toArray();
  144. }
  145. if (!is_array($pages)) {
  146. throw new Exception(
  147. 'Invalid argument: $pages must be an array or an ' .
  148. 'instance of Zend_Config');
  149. }
  150. foreach ($pages as $page) {
  151. $this->addPage($page);
  152. }
  153. return $this;
  154. }
  155. /**
  156. * Sets pages this container should have, removing existing pages
  157. *
  158. * @param array $pages pages to set
  159. * @return \Zend\Navigation\Container fluent interface, returns self
  160. */
  161. public function setPages(array $pages)
  162. {
  163. $this->removePages();
  164. return $this->addPages($pages);
  165. }
  166. /**
  167. * Returns pages in the container
  168. *
  169. * @return array array of \Zend\Navigation\AbstractPage instances
  170. */
  171. public function getPages()
  172. {
  173. return $this->_pages;
  174. }
  175. /**
  176. * Removes the given page from the container
  177. *
  178. * @param \Zend\Navigation\AbstractPage|int $page page to remove, either a page
  179. * instance or a specific page order
  180. * @return bool whether the removal was
  181. * successful
  182. */
  183. public function removePage($page)
  184. {
  185. if ($page instanceof AbstractPage) {
  186. $hash = $page->hashCode();
  187. } elseif (is_int($page)) {
  188. $this->_sort();
  189. if (!$hash = array_search($page, $this->_index)) {
  190. return false;
  191. }
  192. } else {
  193. return false;
  194. }
  195. if (isset($this->_pages[$hash])) {
  196. unset($this->_pages[$hash]);
  197. unset($this->_index[$hash]);
  198. $this->_dirtyIndex = true;
  199. return true;
  200. }
  201. return false;
  202. }
  203. /**
  204. * Removes all pages in container
  205. *
  206. * @return \Zend\Navigation\Container fluent interface, returns self
  207. */
  208. public function removePages()
  209. {
  210. $this->_pages = array();
  211. $this->_index = array();
  212. return $this;
  213. }
  214. /**
  215. * Checks if the container has the given page
  216. *
  217. * @param \Zend\Navigation\AbstractPage $page page to look for
  218. * @param bool $recursive [optional] whether to search
  219. * recursively. Default is false.
  220. * @return bool whether page is in container
  221. */
  222. public function hasPage(AbstractPage $page, $recursive = false)
  223. {
  224. if (array_key_exists($page->hashCode(), $this->_index)) {
  225. return true;
  226. } elseif ($recursive) {
  227. foreach ($this->_pages as $childPage) {
  228. if ($childPage->hasPage($page, true)) {
  229. return true;
  230. }
  231. }
  232. }
  233. return false;
  234. }
  235. /**
  236. * Returns true if container contains any pages
  237. *
  238. * @return bool whether container has any pages
  239. */
  240. public function hasPages()
  241. {
  242. return count($this->_index) > 0;
  243. }
  244. /**
  245. * Returns a child page matching $property == $value, or null if not found
  246. *
  247. * @param string $property name of property to match against
  248. * @param mixed $value value to match property against
  249. * @return \Zend\Navigation\AbstractPage|null matching page or null
  250. */
  251. public function findOneBy($property, $value)
  252. {
  253. $iterator = new \RecursiveIteratorIterator($this,
  254. \RecursiveIteratorIterator::SELF_FIRST);
  255. foreach ($iterator as $page) {
  256. if ($page->get($property) == $value) {
  257. return $page;
  258. }
  259. }
  260. return null;
  261. }
  262. /**
  263. * Returns all child pages matching $property == $value, or an empty array
  264. * if no pages are found
  265. *
  266. * @param string $property name of property to match against
  267. * @param mixed $value value to match property against
  268. * @return array array containing only \Zend\Navigation\AbstractPage
  269. * instances
  270. */
  271. public function findAllBy($property, $value)
  272. {
  273. $found = array();
  274. $iterator = new \RecursiveIteratorIterator($this,
  275. \RecursiveIteratorIterator::SELF_FIRST);
  276. foreach ($iterator as $page) {
  277. if ($page->get($property) == $value) {
  278. $found[] = $page;
  279. }
  280. }
  281. return $found;
  282. }
  283. /**
  284. * Returns page(s) matching $property == $value
  285. *
  286. * @param string $property name of property to match against
  287. * @param mixed $value value to match property against
  288. * @param bool $all [optional] whether an array of all matching
  289. * pages should be returned, or only the first.
  290. * If true, an array will be returned, even if not
  291. * matching pages are found. If false, null will
  292. * be returned if no matching page is found.
  293. * Default is false.
  294. * @return \Zend\Navigation\AbstractPage|null matching page or null
  295. */
  296. public function findBy($property, $value, $all = false)
  297. {
  298. if ($all) {
  299. return $this->findAllBy($property, $value);
  300. } else {
  301. return $this->findOneBy($property, $value);
  302. }
  303. }
  304. /**
  305. * Magic overload: Proxy calls to finder methods
  306. *
  307. * Examples of finder calls:
  308. * <code>
  309. * // METHOD // SAME AS
  310. * $nav->findByLabel('foo'); // $nav->findOneBy('label', 'foo');
  311. * $nav->findOneByLabel('foo'); // $nav->findOneBy('label', 'foo');
  312. * $nav->findAllByClass('foo'); // $nav->findAllBy('class', 'foo');
  313. * </code>
  314. *
  315. * @param string $method method name
  316. * @param array $arguments method arguments
  317. * @throws \Zend\Navigation\Exception if method does not exist
  318. */
  319. public function __call($method, $arguments)
  320. {
  321. if (@preg_match('/(find(?:One|All)?By)(.+)/', $method, $match)) {
  322. return $this->{$match[1]}($match[2], $arguments[0]);
  323. }
  324. throw new Exception(sprintf(
  325. 'Bad method call: Unknown method %s::%s',
  326. get_class($this),
  327. $method));
  328. }
  329. /**
  330. * Returns an array representation of all pages in container
  331. *
  332. * @return array
  333. */
  334. public function toArray()
  335. {
  336. $pages = array();
  337. $this->_dirtyIndex = true;
  338. $this->_sort();
  339. $indexes = array_keys($this->_index);
  340. foreach ($indexes as $hash) {
  341. $pages[] = $this->_pages[$hash]->toArray();
  342. }
  343. return $pages;
  344. }
  345. // RecursiveIterator interface:
  346. /**
  347. * Returns current page
  348. *
  349. * Implements RecursiveIterator interface.
  350. *
  351. * @return \Zend\Navigation\AbstractPage current page or null
  352. * @throws \Zend\Navigation\Exception if the index is invalid
  353. */
  354. public function current()
  355. {
  356. $this->_sort();
  357. current($this->_index);
  358. $hash = key($this->_index);
  359. if (isset($this->_pages[$hash])) {
  360. return $this->_pages[$hash];
  361. } else {
  362. throw new Exception(
  363. 'Corruption detected in container; ' .
  364. 'invalid key found in internal iterator');
  365. }
  366. }
  367. /**
  368. * Returns hash code of current page
  369. *
  370. * Implements RecursiveIterator interface.
  371. *
  372. * @return string hash code of current page
  373. */
  374. public function key()
  375. {
  376. $this->_sort();
  377. return key($this->_index);
  378. }
  379. /**
  380. * Moves index pointer to next page in the container
  381. *
  382. * Implements RecursiveIterator interface.
  383. *
  384. * @return void
  385. */
  386. public function next()
  387. {
  388. $this->_sort();
  389. next($this->_index);
  390. }
  391. /**
  392. * Sets index pointer to first page in the container
  393. *
  394. * Implements RecursiveIterator interface.
  395. *
  396. * @return void
  397. */
  398. public function rewind()
  399. {
  400. $this->_sort();
  401. reset($this->_index);
  402. }
  403. /**
  404. * Checks if container index is valid
  405. *
  406. * Implements RecursiveIterator interface.
  407. *
  408. * @return bool
  409. */
  410. public function valid()
  411. {
  412. $this->_sort();
  413. return current($this->_index) !== false;
  414. }
  415. /**
  416. * Proxy to hasPages()
  417. *
  418. * Implements RecursiveIterator interface.
  419. *
  420. * @return bool whether container has any pages
  421. */
  422. public function hasChildren()
  423. {
  424. return $this->hasPages();
  425. }
  426. /**
  427. * Returns the child container.
  428. *
  429. * Implements RecursiveIterator interface.
  430. *
  431. * @return \Zend\Navigation\AbstractPage|null
  432. */
  433. public function getChildren()
  434. {
  435. $hash = key($this->_index);
  436. if (isset($this->_pages[$hash])) {
  437. return $this->_pages[$hash];
  438. }
  439. return null;
  440. }
  441. // Countable interface:
  442. /**
  443. * Returns number of pages in container
  444. *
  445. * Implements Countable interface.
  446. *
  447. * @return int number of pages in the container
  448. */
  449. public function count()
  450. {
  451. return count($this->_index);
  452. }
  453. }