Skip to content

Instantly share code, notes, and snippets.

@alexander-schranz
Last active July 11, 2022 17:02
Show Gist options
  • Select an option

  • Save alexander-schranz/97eaa507d711cad727988b9cd29c5fa6 to your computer and use it in GitHub Desktop.

Select an option

Save alexander-schranz/97eaa507d711cad727988b9cd29c5fa6 to your computer and use it in GitHub Desktop.
Navigation, Breadcrumb and Tab Groups via Static Symfony Route Options
extranet_dashboard:
path: /extranet
controller: Symfony\Bundle\FrameworkBundle\Controller\TemplateController
defaults:
template: 'base.html.twig'
options:
title: app.extranet.dashboard
icon: 'outline/home'
navigationContext: 'main'
extranet_other:
path: /extranet/other
controller: Symfony\Bundle\FrameworkBundle\Controller\TemplateController
defaults:
template: 'base.html.twig'
options:
title: app.extranet.other
icon: 'outline/document-report'
navigationContext: 'main'
breadcrumbParent: 'extranet_dashboard'
extranet_other_more:
path: /extranet/other/{id}
controller: Symfony\Bundle\FrameworkBundle\Controller\TemplateController
defaults:
template: 'base.html.twig'
options:
title: app.extranet.other_more
breadcrumbParent: 'extranet_other'
icon: 'outline/user'
tabGroup: 'group'
extranet_other_more_detail:
path: /extranet/other/{id}/detail
controller: Symfony\Bundle\FrameworkBundle\Controller\TemplateController
defaults:
template: 'base.html.twig'
options:
title: app.extranet.other_more_detail
breadcrumbParent: 'extranet_other_more'
icon: 'outline/qrcode'
tabGroup: 'group'
extranet_other_more_detail_subview:
path: /extranet/other/{id}/detail/subview
controller: Symfony\Bundle\FrameworkBundle\Controller\TemplateController
defaults:
template: 'base.html.twig'
options:
title: app.extranet.other_more_detail
breadcrumbParent: 'extranet_other_more_detail'
<?php
// all work the same way:
$routeCollection = $this->router->getRouteCollection();
$items = [];
foreach ($routeCollection as $routeName => $route) {
if ($someOption !== $route->getOption('someOption')) {
continue;
}
// get something from routes
$items[] = [/* push to the route */];
}
return $items;
<?php
declare(strict_types=1);
namespace App\Extranet\Infrastructure\Twig;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
use Webmozart\Assert\Assert;
class BreadcrumbTwigExtension extends AbstractExtension
{
public function __construct(
private RouterInterface $router,
private TranslatorInterface $translator,
private RequestStack $requestStack,
) {
}
public function getFunctions(): array
{
return [
new TwigFunction('get_static_breadcrumb', fn (): array => $this->getBreadcrumb()),
];
}
/**
* @return array<array{
* title: string,
* url: string,
* }>|array<mixed[]>
*/
public function getBreadcrumb(): array
{
$currentRequest = $this->requestStack->getCurrentRequest();
if (!$currentRequest instanceof Request) {
return [];
}
$routeName = $currentRequest->attributes->get('_route');
Assert::string($routeName);
/** @var array<string, mixed> $routeAttributes */
$routeAttributes = $currentRequest->attributes->get('_route_params', []);
foreach ($routeAttributes as $key => $value) {
$this->router->getContext()->setParameter($key, $value);
}
$routes = $this->getRoutes($routeName);
// never show a single item as breadcrumb
if (\count($routes) <= 1) {
return [];
}
return $routes;
}
/**
* @param array<array{
* route: string,
* title: string,
* url: string,
* }>|array<mixed[]> $routes
*
* @return array<array{
* route: string,
* title: string,
* url: string,
* }>|array<mixed[]>
*/
public function getRoutes(string $routeName, array $routes = []): array
{
$route = $this->router->getRouteCollection()->get($routeName);
Assert::notNull($route);
$options = $route->getOptions();
unset($options['utf8']);
unset($options['compiler_class']);
unset($options['navigationContext']);
unset($options['breadcrumbParent']);
unset($options['tabGroup']);
$itemTitle = $route->getOption('title');
Assert::nullOrString($itemTitle);
$itemTitle = $itemTitle ? $this->translator->trans($itemTitle) : '';
$itemUrl = $this->router->generate($routeName);
$options['route'] = $routeName;
$options['title'] = $itemTitle;
$options['url'] = $itemUrl;
\array_unshift($routes, $options);
if ($route->hasOption('breadcrumbParent')) {
$parentRouteName = $route->getOption('breadcrumbParent');
Assert::string($parentRouteName);
$routes = $this->getRoutes($parentRouteName, $routes);
}
return $routes;
}
}
<?php
declare(strict_types=1);
namespace App\Extranet\Infrastructure\Twig;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
use Webmozart\Assert\Assert;
class NavigationTwigExtension extends AbstractExtension
{
public function __construct(
private RouterInterface $router,
private TranslatorInterface $translator,
private RequestStack $requestStack,
) {
}
public function getFunctions(): array
{
return [
new TwigFunction('get_static_navigation', fn (string $navigationContext): array => $this->getNavigation($navigationContext)),
];
}
/**
* @return array<string, array{
* title: string,
* url: string,
* activeRoute: bool,
* }>|array<string, mixed[]>
*/
public function getNavigation(string $navigationContext): array
{
$currentRequest = $this->requestStack->getCurrentRequest();
$currentUrl = $currentRequest?->getPathInfo();
$routeCollection = $this->router->getRouteCollection();
$items = [];
$activeRoute = null;
foreach ($routeCollection as $routeName => $route) {
if ($navigationContext !== $route->getOption('navigationContext')) {
continue;
}
$options = $route->getOptions();
unset($options['utf8']);
unset($options['compiler_class']);
unset($options['navigationContext']);
unset($options['tabGroup']);
unset($options['breadcrumbParent']);
$itemTitle = $route->getOption('title');
Assert::string($itemTitle);
$itemTitle = $this->translator->trans($itemTitle);
$itemUrl = $this->router->generate($routeName);
$options['title'] = $itemTitle;
$options['url'] = $itemUrl;
$options['activeRoute'] = false;
if (null !== $currentUrl && \str_starts_with($currentUrl, $itemUrl)) {
$options['activeRoute'] = true;
// make sure there is only one active route in the navigation item (e.g. dashboard should not be active)
if ($activeRoute) {
$items[$activeRoute]['activeRoute'] = false;
}
$activeRoute = $routeName;
}
$items[$routeName] = $options;
}
return $items;
}
}
<?php
declare(strict_types=1);
namespace App\Extranet\Infrastructure\Twig;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
use Webmozart\Assert\Assert;
class TabTwigExtension extends AbstractExtension
{
public function __construct(
private RouterInterface $router,
private TranslatorInterface $translator,
private RequestStack $requestStack,
) {
}
public function getFunctions(): array
{
return [
new TwigFunction('get_static_tabs', fn (): array => $this->getTabs()),
];
}
/**
* @return array<string, array{
* title: string,
* url: string,
* activeRoute: bool,
* }>|array<string, mixed[]>
*/
public function getTabs(): array
{
$currentRequest = $this->requestStack->getCurrentRequest();
/** @var string|null $currentRouteName */
$currentRouteName = $currentRequest?->attributes->get('_route');
if (!$currentRouteName || !$currentRequest) {
return [];
}
$tabGroup = $this->getTabGroup($currentRouteName);
if (!$tabGroup) {
return [];
}
/** @var array<string, mixed> $routeAttributes */
$routeAttributes = $currentRequest->attributes->get('_route_params', []);
foreach ($routeAttributes as $key => $value) {
$this->router->getContext()->setParameter($key, $value);
}
$routeCollection = $this->router->getRouteCollection();
$currentUrl = $currentRequest->getPathInfo();
$items = [];
$activeRoute = null;
foreach ($routeCollection as $routeName => $route) {
if ($tabGroup !== $route->getOption('tabGroup')) {
continue;
}
$options = $route->getOptions();
unset($options['utf8']);
unset($options['compiler_class']);
unset($options['navigationContext']);
unset($options['breadcrumbParent']);
unset($options['tabGroup']);
$itemTitle = $route->getOption('title');
Assert::string($itemTitle);
$itemTitle = $this->translator->trans($itemTitle);
$itemUrl = $this->router->generate($routeName);
$options['title'] = $itemTitle;
$options['url'] = $itemUrl;
$options['activeRoute'] = false;
if (null !== $currentUrl && \str_starts_with($currentUrl, $itemUrl)) {
$options['activeRoute'] = true;
// make sure there is only one active route in the navigation item (e.g. dashboard should not be active)
if ($activeRoute) {
$items[$activeRoute]['activeRoute'] = false;
}
$activeRoute = $routeName;
}
$items[$routeName] = $options;
}
return $items;
}
private function getTabGroup(string $currentRouteName): ?string
{
$route = $this->router->getRouteCollection()->get($currentRouteName);
Assert::notNull($route, \sprintf('Configured breadcrumb route "%s" should be available.', $currentRouteName));
if ($route->hasOption('tabGroup')) {
/** @var string */
return $route->getOption('tabGroup');
}
if ($route->hasOption('breadcrumbParent')) {
/** @var string $parentRoute */
$parentRoute = $route->getOption('breadcrumbParent');
return $this->getTabGroup($parentRoute);
}
return null;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment