2
0

Container.php 14 KB

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