<?php
declare(strict_types=1);
namespace App\Bundle\Routing;
use InvalidArgumentException;
use Psr\Log\LoggerInterface;
use RuntimeException;
use Symfony\Bundle\FrameworkBundle\Routing\Router;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface;
use Symfony\Component\Routing\Exception\MethodNotAllowedException;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
use Symfony\Component\Routing\Exception\RouteNotFoundException;
use Symfony\Component\Routing\Matcher\RequestMatcherInterface;
use Symfony\Component\Routing\RequestContext;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\RouterInterface;
class CompoundRouter implements CompoundRouterInterface, WarmableInterface
{
/**
* Array of routers sorted by priority.
*
* @var RouterInterface[]
*/
protected $sortedRouters = [];
/**
* Array of routers grouped by priority.
*
* @var RouterInterface[]
*/
protected $routers = [];
/**
* @var RouteCollection|null
*/
protected $routeCollection;
/**
* @var RequestContext|null
*/
protected $context;
/**
* @var LoggerInterface|null
*/
protected $logger;
public function __construct(LoggerInterface $logger = null)
{
$this->logger = $logger;
}
/**
* {@inheritDoc}
*/
public function matchRequest(Request $request): array
{
$this->logger->debug("REQUEST PATH:" . $request->getPathInfo());
$exc = null;
foreach ($this->all() as $router) {
try {
$ret = $router->matchRequest($request);
$this->logger->debug(
sprintf('Router %s matched the route', get_class($router))
);
return $ret;
} catch (ResourceNotFoundException $e) {
if ($this->logger) {
$this->logger->debug(
sprintf('Router %s was not able to match, message "%s"', get_class($router), $e->getMessage())
);
}
} catch (MethodNotAllowedException $e) {
if ($this->logger) {
$this->logger->debug(
sprintf('Router %s do not allow this method, message "%s"', get_class($router), $e->getMessage())
);
$exc = $e;
}
}
}
throw $exc ?: new ResourceNotFoundException('None of the routers could matched the request: ' . $request);
}
/**
* {@inheritDoc}
*/
public function match($pathinfo): array
{
throw new RuntimeException('The match method is unsupported, adjust Router to use matchRequest method.');
}
/**
* {@inheritDoc}
*/
public function generate($name, $parameters = [], $referenceType = self::ABSOLUTE_PATH): string
{
foreach ($this->all() as $router) {
try {
return $router->generate($name, $parameters, $referenceType);
} catch (RouteNotFoundException $e) {
if ($this->logger) {
$this->logger->debug(
sprintf(
'Router %s cannot generate route "%s". Reason: "%s"',
get_class($router),
$name,
$e->getMessage()
)
);
}
}
}
throw new RouteNotFoundException("None of the routers were able to generate route \"$name\"");
}
/**
* {@inheritDoc}
*/
public function all(): array
{
if (!$this->sortedRouters) {
$this->sortedRouters = $this->sortRouters();
// Context setting must be done here in order to prevent cache issues
if (null !== $ctx = $this->context) {
foreach ($this->sortedRouters as $router)
$router->setContext($ctx);
}
}
return $this->sortedRouters;
}
public function attach(RouterInterface $router, int $priority = 0): CompoundRouterInterface
{
if (!$router instanceof RequestMatcherInterface)
throw new InvalidArgumentException(
sprintf('%s must implement %s', get_class($router), RequestMatcherInterface::class)
);
if (!isset($this->routers[$priority]))
$this->routers[$priority] = [];
$this->routers[$priority][] = $router;
$this->sortedRouters = [];
return $this;
}
/**
* {@inheritDoc}
*/
public function getRouteCollection(): RouteCollection
{
if (!$this->routeCollection) {
$this->routeCollection = new CompoundRouteCollection();
foreach ($this->all() as $router)
$this->routeCollection->addCollection($router->getRouteCollection());
}
return $this->routeCollection;
}
/**
* {@inheritDoc}
*/
public function setContext(RequestContext $context)
{
$this->context = $context;
foreach ($this->all() as $router)
$router->setContext($context);
return $this;
}
/**
* {@inheritDoc}
*/
public function getContext(): RequestContext
{
return $this->context;
}
/**
* {@inheritDoc}
*/
public function warmUp($cacheDir): array
{
foreach ($this->all() as $router)
if ($router instanceof WarmableInterface)
$router->warmUp($cacheDir);
return [];
}
/**
* @return RouterInterface[]
*/
protected function sortRouters(): array
{
$output = [];
krsort($this->routers);
foreach ($this->routers as $routers)
$output = array_merge($output, $routers);
return $output;
}
}