PageRenderTime 40ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/library/Zend/Navigation/Container.php

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