vendor/knplabs/knp-menu/src/Knp/Menu/MenuItem.php line 8

Open in your IDE?
  1. <?php
  2. namespace Knp\Menu;
  3. /**
  4. * Default implementation of the ItemInterface
  5. */
  6. class MenuItem implements ItemInterface
  7. {
  8. /**
  9. * Name of this menu item (used for id by parent menu)
  10. *
  11. * @var string
  12. */
  13. protected $name;
  14. /**
  15. * Label to output, name is used by default
  16. *
  17. * @var string|null
  18. */
  19. protected $label;
  20. /**
  21. * Attributes for the item link
  22. *
  23. * @var array<string, string|bool|null>
  24. */
  25. protected $linkAttributes = [];
  26. /**
  27. * Attributes for the children list
  28. *
  29. * @var array<string, string|bool|null>
  30. */
  31. protected $childrenAttributes = [];
  32. /**
  33. * Attributes for the item text
  34. *
  35. * @var array<string, string|bool|null>
  36. */
  37. protected $labelAttributes = [];
  38. /**
  39. * Uri to use in the anchor tag
  40. *
  41. * @var string|null
  42. */
  43. protected $uri;
  44. /**
  45. * Attributes for the item
  46. *
  47. * @var array<string, string|bool|null>
  48. */
  49. protected $attributes = [];
  50. /**
  51. * Extra stuff associated to the item
  52. *
  53. * @var array<string, mixed>
  54. */
  55. protected $extras = [];
  56. /**
  57. * Whether the item is displayed
  58. *
  59. * @var bool
  60. */
  61. protected $display = true;
  62. /**
  63. * Whether the children of the item are displayed
  64. *
  65. * @var bool
  66. */
  67. protected $displayChildren = true;
  68. /**
  69. * Child items
  70. *
  71. * @var array<string, ItemInterface>
  72. */
  73. protected $children = [];
  74. /**
  75. * Parent item
  76. *
  77. * @var ItemInterface|null
  78. */
  79. protected $parent;
  80. /**
  81. * whether the item is current. null means unknown
  82. *
  83. * @var bool|null
  84. */
  85. protected $isCurrent;
  86. /**
  87. * @var FactoryInterface
  88. */
  89. protected $factory;
  90. /**
  91. * Class constructor
  92. *
  93. * @param string $name The name of this menu, which is how its parent will
  94. * reference it. Also used as label if label not specified
  95. */
  96. public function __construct(string $name, FactoryInterface $factory)
  97. {
  98. $this->name = $name;
  99. $this->factory = $factory;
  100. }
  101. public function setFactory(FactoryInterface $factory): ItemInterface
  102. {
  103. $this->factory = $factory;
  104. return $this;
  105. }
  106. public function getName(): string
  107. {
  108. return $this->name;
  109. }
  110. public function setName(string $name): ItemInterface
  111. {
  112. if ($this->name === $name) {
  113. return $this;
  114. }
  115. $parent = $this->getParent();
  116. if (null !== $parent && isset($parent[$name])) {
  117. throw new \InvalidArgumentException('Cannot rename item, name is already used by sibling.');
  118. }
  119. $oldName = $this->name;
  120. $this->name = $name;
  121. if (null !== $parent) {
  122. $names = \array_keys($parent->getChildren());
  123. $items = \array_values($parent->getChildren());
  124. $offset = \array_search($oldName, $names);
  125. $names[$offset] = $name;
  126. $children = \array_combine($names, $items);
  127. $parent->setChildren($children);
  128. }
  129. return $this;
  130. }
  131. public function getUri(): ?string
  132. {
  133. return $this->uri;
  134. }
  135. public function setUri(?string $uri): ItemInterface
  136. {
  137. $this->uri = $uri;
  138. return $this;
  139. }
  140. public function getLabel(): string
  141. {
  142. return $this->label ?? $this->name;
  143. }
  144. public function setLabel(?string $label): ItemInterface
  145. {
  146. $this->label = $label;
  147. return $this;
  148. }
  149. public function getAttributes(): array
  150. {
  151. return $this->attributes;
  152. }
  153. public function setAttributes(array $attributes): ItemInterface
  154. {
  155. $this->attributes = $attributes;
  156. return $this;
  157. }
  158. public function getAttribute(string $name, $default = null)
  159. {
  160. return $this->attributes[$name] ?? $default;
  161. }
  162. public function setAttribute(string $name, $value): ItemInterface
  163. {
  164. $this->attributes[$name] = $value;
  165. return $this;
  166. }
  167. public function getLinkAttributes(): array
  168. {
  169. return $this->linkAttributes;
  170. }
  171. public function setLinkAttributes(array $linkAttributes): ItemInterface
  172. {
  173. $this->linkAttributes = $linkAttributes;
  174. return $this;
  175. }
  176. public function getLinkAttribute(string $name, $default = null)
  177. {
  178. return $this->linkAttributes[$name] ?? $default;
  179. }
  180. public function setLinkAttribute(string $name, $value): ItemInterface
  181. {
  182. $this->linkAttributes[$name] = $value;
  183. return $this;
  184. }
  185. public function getChildrenAttributes(): array
  186. {
  187. return $this->childrenAttributes;
  188. }
  189. public function setChildrenAttributes(array $childrenAttributes): ItemInterface
  190. {
  191. $this->childrenAttributes = $childrenAttributes;
  192. return $this;
  193. }
  194. public function getChildrenAttribute(string $name, $default = null)
  195. {
  196. return $this->childrenAttributes[$name] ?? $default;
  197. }
  198. public function setChildrenAttribute(string $name, $value): ItemInterface
  199. {
  200. $this->childrenAttributes[$name] = $value;
  201. return $this;
  202. }
  203. public function getLabelAttributes(): array
  204. {
  205. return $this->labelAttributes;
  206. }
  207. public function setLabelAttributes(array $labelAttributes): ItemInterface
  208. {
  209. $this->labelAttributes = $labelAttributes;
  210. return $this;
  211. }
  212. public function getLabelAttribute(string $name, $default = null)
  213. {
  214. return $this->labelAttributes[$name] ?? $default;
  215. }
  216. public function setLabelAttribute(string $name, $value): ItemInterface
  217. {
  218. $this->labelAttributes[$name] = $value;
  219. return $this;
  220. }
  221. public function getExtras(): array
  222. {
  223. return $this->extras;
  224. }
  225. public function setExtras(array $extras): ItemInterface
  226. {
  227. $this->extras = $extras;
  228. return $this;
  229. }
  230. public function getExtra(string $name, $default = null)
  231. {
  232. return $this->extras[$name] ?? $default;
  233. }
  234. public function setExtra(string $name, $value): ItemInterface
  235. {
  236. $this->extras[$name] = $value;
  237. return $this;
  238. }
  239. public function getDisplayChildren(): bool
  240. {
  241. return $this->displayChildren;
  242. }
  243. public function setDisplayChildren(bool $bool): ItemInterface
  244. {
  245. $this->displayChildren = $bool;
  246. return $this;
  247. }
  248. public function isDisplayed(): bool
  249. {
  250. return $this->display;
  251. }
  252. public function setDisplay(bool $bool): ItemInterface
  253. {
  254. $this->display = $bool;
  255. return $this;
  256. }
  257. public function addChild($child, array $options = []): ItemInterface
  258. {
  259. if (!$child instanceof ItemInterface) {
  260. $child = $this->factory->createItem($child, $options);
  261. } elseif (null !== $child->getParent()) {
  262. throw new \InvalidArgumentException('Cannot add menu item as child, it already belongs to another menu (e.g. has a parent).');
  263. }
  264. $child->setParent($this);
  265. $this->children[$child->getName()] = $child;
  266. return $child;
  267. }
  268. public function getChild(string $name): ?ItemInterface
  269. {
  270. return $this->children[$name] ?? null;
  271. }
  272. public function reorderChildren(array $order): ItemInterface
  273. {
  274. if (\count($order) !== $this->count()) {
  275. throw new \InvalidArgumentException('Cannot reorder children, order does not contain all children.');
  276. }
  277. $newChildren = [];
  278. foreach ($order as $name) {
  279. if (!isset($this->children[$name])) {
  280. throw new \InvalidArgumentException('Cannot find children named '.$name);
  281. }
  282. $child = $this->children[$name];
  283. $newChildren[$name] = $child;
  284. }
  285. $this->setChildren($newChildren);
  286. return $this;
  287. }
  288. public function copy(): ItemInterface
  289. {
  290. $newMenu = clone $this;
  291. $newMenu->setChildren([]);
  292. $newMenu->setParent();
  293. foreach ($this->getChildren() as $child) {
  294. $newMenu->addChild($child->copy());
  295. }
  296. return $newMenu;
  297. }
  298. public function getLevel(): int
  299. {
  300. return $this->parent ? $this->parent->getLevel() + 1 : 0;
  301. }
  302. public function getRoot(): ItemInterface
  303. {
  304. $obj = $this;
  305. do {
  306. $found = $obj;
  307. } while ($obj = $obj->getParent());
  308. return $found;
  309. }
  310. public function isRoot(): bool
  311. {
  312. return null === $this->parent;
  313. }
  314. public function getParent(): ?ItemInterface
  315. {
  316. return $this->parent;
  317. }
  318. public function setParent(?ItemInterface $parent = null): ItemInterface
  319. {
  320. if ($parent === $this) {
  321. throw new \InvalidArgumentException('Item cannot be a child of itself');
  322. }
  323. $this->parent = $parent;
  324. return $this;
  325. }
  326. public function getChildren(): array
  327. {
  328. return $this->children;
  329. }
  330. public function setChildren(array $children): ItemInterface
  331. {
  332. $this->children = $children;
  333. return $this;
  334. }
  335. public function removeChild($name): ItemInterface
  336. {
  337. $name = $name instanceof ItemInterface ? $name->getName() : $name;
  338. if (isset($this->children[$name])) {
  339. // unset the child and reset it so it looks independent
  340. $this->children[$name]->setParent(null);
  341. unset($this->children[$name]);
  342. }
  343. return $this;
  344. }
  345. public function getFirstChild(): ItemInterface
  346. {
  347. if (empty($this->children)) {
  348. throw new \LogicException('Cannot get first child: there are no children.');
  349. }
  350. return \reset($this->children);
  351. }
  352. public function getLastChild(): ItemInterface
  353. {
  354. if (empty($this->children)) {
  355. throw new \LogicException('Cannot get last child: there are no children.');
  356. }
  357. return \end($this->children);
  358. }
  359. public function hasChildren(): bool
  360. {
  361. foreach ($this->children as $child) {
  362. if ($child->isDisplayed()) {
  363. return true;
  364. }
  365. }
  366. return false;
  367. }
  368. public function setCurrent(?bool $bool): ItemInterface
  369. {
  370. $this->isCurrent = $bool;
  371. return $this;
  372. }
  373. public function isCurrent(): ?bool
  374. {
  375. return $this->isCurrent;
  376. }
  377. public function isLast(): bool
  378. {
  379. // if this is root, then return false
  380. if (null === $this->parent) {
  381. return false;
  382. }
  383. return $this->parent->getLastChild() === $this;
  384. }
  385. public function isFirst(): bool
  386. {
  387. // if this is root, then return false
  388. if (null === $this->parent) {
  389. return false;
  390. }
  391. return $this->parent->getFirstChild() === $this;
  392. }
  393. public function actsLikeFirst(): bool
  394. {
  395. // root items are never "marked" as first
  396. if (null === $this->parent) {
  397. return false;
  398. }
  399. // A menu acts like first only if it is displayed
  400. if (!$this->isDisplayed()) {
  401. return false;
  402. }
  403. // if we're first and visible, we're first, period.
  404. if ($this->isFirst()) {
  405. return true;
  406. }
  407. $children = $this->parent->getChildren();
  408. foreach ($children as $child) {
  409. // loop until we find a visible menu. If its this menu, we're first
  410. if ($child->isDisplayed()) {
  411. return $child->getName() === $this->getName();
  412. }
  413. }
  414. return false;
  415. }
  416. public function actsLikeLast(): bool
  417. {
  418. // root items are never "marked" as last
  419. if (null === $this->parent) {
  420. return false;
  421. }
  422. // A menu acts like last only if it is displayed
  423. if (!$this->isDisplayed()) {
  424. return false;
  425. }
  426. // if we're last and visible, we're last, period.
  427. if ($this->isLast()) {
  428. return true;
  429. }
  430. $children = \array_reverse($this->parent->getChildren());
  431. foreach ($children as $child) {
  432. // loop until we find a visible menu. If its this menu, we're first
  433. if ($child->isDisplayed()) {
  434. return $child->getName() === $this->getName();
  435. }
  436. }
  437. return false;
  438. }
  439. /**
  440. * Implements Countable
  441. */
  442. public function count(): int
  443. {
  444. return \count($this->children);
  445. }
  446. /**
  447. * Implements IteratorAggregate
  448. */
  449. public function getIterator(): \Traversable
  450. {
  451. return new \ArrayIterator($this->children);
  452. }
  453. /**
  454. * Implements ArrayAccess
  455. *
  456. * @param string $offset
  457. */
  458. public function offsetExists($offset): bool
  459. {
  460. return isset($this->children[$offset]);
  461. }
  462. /**
  463. * Implements ArrayAccess
  464. *
  465. * @param string $offset
  466. *
  467. * @return ItemInterface|null
  468. */
  469. #[\ReturnTypeWillChange]
  470. public function offsetGet($offset)
  471. {
  472. return $this->getChild($offset);
  473. }
  474. /**
  475. * Implements ArrayAccess
  476. *
  477. * @param string $offset
  478. * @param string|null $value
  479. */
  480. public function offsetSet($offset, $value): void
  481. {
  482. $this->addChild($offset)->setLabel($value);
  483. }
  484. /**
  485. * Implements ArrayAccess
  486. *
  487. * @param string $offset
  488. */
  489. public function offsetUnset($offset): void
  490. {
  491. $this->removeChild($offset);
  492. }
  493. }