Skip to content

Commit

Permalink
implement nested navigation (#7)
Browse files Browse the repository at this point in the history
implement nested navigation

- container for nested navigation instances
- builder for navigation tree
- registry for navigation containers (so you can register multiple nested navigation instances)
  • Loading branch information
martinlutter authored and ivanbarlog committed Mar 6, 2018
1 parent 1fd91b1 commit 9091774
Show file tree
Hide file tree
Showing 8 changed files with 398 additions and 3 deletions.
4 changes: 1 addition & 3 deletions src/Builder/MatcherInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,12 @@

namespace Everlution\Navigation\Builder;

use Everlution\Navigation\Item\ItemInterface;

/**
* Interface MatcherInterface.
*
* @author Ivan Barlog <[email protected]>
*/
interface MatcherInterface
{
public function isCurrent(ItemInterface $item): bool;
public function isCurrent($item): bool;
}
35 changes: 35 additions & 0 deletions src/Nested/AdvancedNavigationInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

declare(strict_types=1);

namespace Everlution\Navigation\Nested;

use Everlution\Navigation\ContainerInterface;
use Everlution\Navigation\Nested\Container\ContainerNotFoundException;
use Everlution\Navigation\Nested\Container\NestableContainerInterface;

/**
* Class AdvancedNavigationInterface.
*
* @author Martin Lutter <[email protected]>
*/
interface AdvancedNavigationInterface
{
/**
* @return NestableContainerInterface[]
*/
public function getNavigationContainers(): array;

/**
* As argument provide FQCN of item class eg. MainNavigation::class.
*
* @param string $name
*
* @return NestableContainerInterface
*
* @throws ContainerNotFoundException
*/
public function get(string $name): NestableContainerInterface;

public function getRoot(): ContainerInterface;
}
154 changes: 154 additions & 0 deletions src/Nested/Builder/NavigationBuilder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
<?php

declare(strict_types=1);

namespace Everlution\Navigation\Nested\Builder;

use Everlution\Navigation\Nested\AdvancedNavigationInterface;
use Everlution\Navigation\Nested\Container\ContainerNotFoundException;
use Everlution\Navigation\Nested\Container\NestableContainerInterface;
use Everlution\Navigation\Builder\MatcherInterface;
use Everlution\Navigation\Builder\NoCurrentItemFoundException;
use Everlution\Navigation\ContainerInterface;

/**
* Class NavigationBuilder.
*
* @author Martin Lutter <[email protected]>
*/
class NavigationBuilder
{
/** @var AdvancedNavigationInterface */
private $navigation;
/** @var ParentNode */
private $root;
/** @var ContainerInterface[] */
private $stack = [];
/** @var ContainerInterface[] */
private $used = [];
/** @var ParentNode */
private $current;
/** @var MatcherInterface */
private $matcher;

public function __construct(AdvancedNavigationInterface $container, MatcherInterface $matcher)
{
$this->navigation = $container;
$this->matcher = $matcher;
$this->build($matcher);
}

/**
* @return ParentNode
*
* @throws NoCurrentItemFoundException
*/
public function getCurrent(): ParentNode
{
if (!$this->current) {
throw new NoCurrentItemFoundException();
}

return $this->current;
}

/**
* @param MatcherInterface $matcher
*/
private function build(MatcherInterface $matcher)
{
$this->root = new ParentNode($this->navigation->getRoot());
$this->setCurrentNode($matcher, $this->root);
$this->addToUsed($this->root);

foreach ($this->navigation->getNavigationContainers() as $container) {
$this->walkToRootContainer($container);
$this->addContainersFromStack();
}
}

/**
* @param NestableContainerInterface $item
*
* @return NestableContainerInterface
*
* @throws ContainerNotFoundException
*/
private function getParent(NestableContainerInterface $item): NestableContainerInterface
{
return $this->navigation->get($item->getParentContainer());
}

/**
* @param NestableContainerInterface $container
*
* @return NestableContainerInterface
*/
private function walkToRootContainer(NestableContainerInterface $container): NestableContainerInterface
{
$this->stack[] = $container;

try {
return $this->walkToRootContainer($this->getParent($container));
} catch (ContainerNotFoundException $e) {
return $container;
}
}

private function addContainersFromStack(): void
{
$root = $this->root;
while ($container = array_pop($this->stack)) {
if (false === $this->isUsed($container)) {
$this->addContainer($root, $container);
}

$root = $root->get(get_class($container));
}
}

/**
* @param ParentNode $root
* @param NestableContainerInterface $container
*/
private function addContainer(ParentNode $root, NestableContainerInterface $container): void
{
$parentNode = new ParentNode($container);

$root->addChild($parentNode);
$this->addToUsed($parentNode);
$this->setCurrentNode($this->matcher, $parentNode);
}

/**
* @param MatcherInterface $matcher
* @param ParentNode $node
*/
private function setCurrentNode(MatcherInterface $matcher, ParentNode $node): void
{
if (!$this->current && $matcher->isCurrent($node->getContainer())) {
$this->current = $node;
}
}

/**
* @param ContainerInterface $container
*
* @return bool
*/
private function isUsed(ContainerInterface $container): bool
{
$name = get_class($container);

return isset($this->used[$name]);
}

/**
* @param ParentNode $node
*/
private function addToUsed(ParentNode $node): void
{
$name = get_class($node->getContainer());
$this->used[$name] = $node;
}
}
77 changes: 77 additions & 0 deletions src/Nested/Builder/ParentNode.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?php

declare(strict_types=1);

namespace Everlution\Navigation\Nested\Builder;

use Everlution\Navigation\ContainerInterface;

/**
* Class RootNode.
*
* @author Martin Lutter <[email protected]>
*/
class ParentNode
{
/** @var ContainerInterface */
protected $container;
/** @var ParentNode[] */
protected $children = [];

public function __construct(ContainerInterface $navigation)
{
$this->container = $navigation;
}

/**
* @return ContainerInterface
*/
public function getContainer(): ContainerInterface
{
return $this->container;
}

/**
* @param ParentNode $item
*/
public function addChild(ParentNode $item): void
{
$this->children[get_class($item->getContainer())] = $item;
}

/**
* @return bool
*/
public function hasChildren(): bool
{
return false === empty($this->children);
}

/**
* @return array
*/
public function getChildren(): array
{
return $this->children;
}

/**
* @param string $name
*
* @return bool
*/
public function has(string $name): bool
{
return array_key_exists($name, $this->children);
}

/**
* @param string $name
*
* @return ParentNode
*/
public function get(string $name): ParentNode
{
return $this->children[$name];
}
}
18 changes: 18 additions & 0 deletions src/Nested/Container/ContainerNotFoundException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace Everlution\Navigation\Nested\Container;

/**
* Class ContainerNotFoundException.
*
* @author Martin Lutter <[email protected]>
*/
class ContainerNotFoundException extends \Exception
{
public function __construct(string $name)
{
parent::__construct("$name container was not found");
}
}
26 changes: 26 additions & 0 deletions src/Nested/Container/NestableContainerInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

declare(strict_types=1);

namespace Everlution\Navigation\Nested\Container;

use Everlution\Navigation\ContainerInterface;
use Everlution\Navigation\Item\ItemInterface;

/**
* Class NestableContainerInterface.
*
* @author Martin Lutter <[email protected]>
*/
interface NestableContainerInterface extends ContainerInterface
{
/**
* @return string
*/
public function getParentContainer(): string;

/**
* @return ItemInterface
*/
public function getParentItem(): ItemInterface;
}
49 changes: 49 additions & 0 deletions src/Nested/NavigationContainer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

declare(strict_types=1);

namespace Everlution\Navigation\Nested;

use Everlution\Navigation\ContainerInterface;
use Everlution\Navigation\Nested\Container\ContainerNotFoundException;
use Everlution\Navigation\Nested\Container\NestableContainerInterface;

/**
* Class NavigationContainer.
*
* @author Martin Lutter <[email protected]>
*/
abstract class NavigationContainer implements AdvancedNavigationInterface
{
/** @var NestableContainerInterface[] */
private $containers = [];

public function add(NestableContainerInterface $container): void
{
$this->containers[get_class($container)] = $container;
}

/**
* @return ContainerInterface[]
*/
public function getNavigationContainers(): array
{
return $this->containers;
}

/**
* @param string $name
*
* @return NestableContainerInterface
*
* @throws ContainerNotFoundException
*/
public function get(string $name): NestableContainerInterface
{
if (false === isset($this->containers[$name])) {
throw new ContainerNotFoundException($name);
}

return $this->containers[$name];
}
}
Loading

0 comments on commit 9091774

Please sign in to comment.