Skip to content

Commit

Permalink
Content resolvers convert content to a route object
Browse files Browse the repository at this point in the history
  • Loading branch information
aschempp committed Apr 8, 2020
1 parent 139b19c commit 223182e
Show file tree
Hide file tree
Showing 8 changed files with 282 additions and 16 deletions.
Expand Up @@ -59,6 +59,7 @@ public function load(array $configs, ContainerBuilder $container): void
$loader->load('commands.yml');
$loader->load('listener.yml');
$loader->load('services.yml');
$loader->load('routing.yml');
$loader->load('migrations.yml');

$container->setParameter('contao.web_dir', $config['web_dir']);
Expand Down
11 changes: 11 additions & 0 deletions core-bundle/src/Resources/config/routing.yml
@@ -0,0 +1,11 @@
services:
_defaults:
autoconfigure: true

Contao\CoreBundle\Routing\Content\ArticleResolver:
tags:
- contao.content_resolver

Contao\CoreBundle\Routing\Content\PageResolver:
tags:
- contao.content_resolver
9 changes: 5 additions & 4 deletions core-bundle/src/Resources/config/services.yml
Expand Up @@ -512,10 +512,11 @@ services:

contao.routing.legacy_route_provider:
class: Contao\CoreBundle\Routing\LegacyRouteProvider
decorates: contao.routing.route_provider
arguments:
- '@contao.routing.frontend_loader'
- '@contao.routing.legacy_route_provider.inner'
- '@contao.routing.route_provider'
tags:
- contao.content_resolver

contao.routing.nested_matcher:
class: Symfony\Cmf\Component\Routing\NestedMatcher\NestedMatcher
Expand Down Expand Up @@ -569,9 +570,9 @@ services:
- '@contao.security.token_checker'

contao.routing.route_generator:
class: Symfony\Cmf\Component\Routing\ProviderBasedGenerator
class: Contao\CoreBundle\Routing\DelegatingUrlGenerator
arguments:
- '@contao.routing.route_provider'
- !tagged_iterator contao.content_resolver
- '@?logger'

contao.routing.route_provider:
Expand Down
41 changes: 41 additions & 0 deletions core-bundle/src/Routing/Content/ArticleResolver.php
@@ -0,0 +1,41 @@
<?php

declare(strict_types=1);

/*
* This file is part of Contao.
*
* (c) Leo Feyer
*
* @license LGPL-3.0-or-later
*/

namespace Contao\CoreBundle\Routing\Content;

use Contao\ArticleModel;
use Contao\PageModel;
use Symfony\Component\Routing\Exception\RouteNotFoundException;
use Symfony\Component\Routing\Route;

class ArticleResolver implements ContentResolverInterface
{
/**
* @param ArticleModel $article
*/
public function resolveContent($article): Route
{
/** @var PageModel $page */
$page = $article->getRelated('pid');

if (!$page instanceof PageModel) {
throw new RouteNotFoundException(sprintf('Page ID %s for article ID %s not found', $article->id, $article->pid));
}

return new PageRoute($page, '/articles/'.($article->alias ?: $article->id), $article);
}

public function supportsContent($content): bool
{
return $content instanceof ArticleModel;
}
}
30 changes: 30 additions & 0 deletions core-bundle/src/Routing/Content/ContentResolverInterface.php
@@ -0,0 +1,30 @@
<?php

namespace Contao\CoreBundle\Routing\Content;

use Symfony\Component\Routing\Route;

interface ContentResolverInterface
{
public const ATTRIBUTE = '_content_resolver';

/**
* Returns a Symfony\Component\Routing\Route or another $content to resolve.
*
* @param mixed $content The route "content" which may also be an object or anything
*
* @return Route|mixed
*/
public function resolveContent($content);

/**
* Whether this provider supports the supplied $content.
*
* This check does not need to look if the specific instance can be
* resolved to a route, only whether the router can generate routes from
* objects of this class.
*
* @param mixed $content The route "content" which may also be an object or anything
*/
public function supportsContent($content): bool;
}
32 changes: 32 additions & 0 deletions core-bundle/src/Routing/Content/PageResolver.php
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);

/*
* This file is part of Contao.
*
* (c) Leo Feyer
*
* @license LGPL-3.0-or-later
*/

namespace Contao\CoreBundle\Routing\Content;

use Contao\PageModel;
use Symfony\Component\Routing\Route;

class PageResolver implements ContentResolverInterface
{
/**
* @param PageModel $page
*/
public function resolveContent($page): Route
{
return new PageRoute($page);
}

public function supportsContent($content): bool
{
return $content instanceof PageModel;
}
}
125 changes: 125 additions & 0 deletions core-bundle/src/Routing/DelegatingUrlGenerator.php
@@ -0,0 +1,125 @@
<?php

declare(strict_types=1);

/*
* This file is part of Contao.
*
* (c) Leo Feyer
*
* @license LGPL-3.0-or-later
*/

namespace Contao\CoreBundle\Routing;

use Contao\CoreBundle\Routing\Content\ContentResolverInterface;
use Psr\Log\LoggerInterface;
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
use Symfony\Cmf\Component\Routing\VersatileGeneratorInterface;
use Symfony\Component\Routing\Exception\RouteNotFoundException;
use Symfony\Component\Routing\Generator\UrlGenerator as SymfonyUrlGenerator;
use Symfony\Component\Routing\RequestContext;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;

class DelegatingUrlGenerator extends SymfonyUrlGenerator implements VersatileGeneratorInterface
{
/**
* @var array<ContentResolverInterface>
*/
private $resolvers;

public function __construct($resolvers, LoggerInterface $logger = null)
{
parent::__construct(new RouteCollection(), new RequestContext(), $logger);

$this->resolvers = $resolvers;
}

/**
* @param array $parameters
* @param int $referenceType
*/
public function generate($content, $parameters = [], $referenceType = self::ABSOLUTE_PATH): string
{
$route = $this->resolveContent($content);

// the Route has a cache of its own and is not recompiled as long as it does not get modified
$compiledRoute = $route->compile();
$hostTokens = $compiledRoute->getHostTokens();

$debug_message = $this->getRouteDebugMessage($content);

return $this->doGenerate($compiledRoute->getVariables(), $route->getDefaults(), $route->getRequirements(), $compiledRoute->getTokens(), $parameters, $debug_message, $referenceType, $hostTokens);
}

public function supports($content): bool
{
if ($content instanceof Route) {
return true;
}

foreach ($this->resolvers as $provider) {
if ($provider->supportsContent($content)) {
return true;
}
}

return false;
}

public function getRouteDebugMessage($name, array $parameters = []): string
{
if (is_scalar($name)) {
return $name;
}

if (\is_array($name)) {
return serialize($name);
}

if ($name instanceof RouteObjectInterface) {
return 'key '.$name->getRouteKey();
}

if ($name instanceof Route) {
return 'path '.$name->getPath();
}

if (\is_object($name)) {
return \get_class($name);
}

return 'null route';
}

private function resolveContent($content): Route
{
if ($content instanceof Route) {
return $content;
}

$route = null;

foreach ($this->resolvers as $resolver) {
if ($resolver->supportsContent($content)) {
$route = $resolver->resolveContent($content);

if ($route instanceof Route) {
$route->setDefault(ContentResolverInterface::ATTRIBUTE, $resolver);
}
break;
}
}

if ($route instanceof Route) {
return $route;
}

if (null !== $route && $route !== $content) {
return $this->resolveContent($route);
}

throw new RouteNotFoundException('No route found for '.$this->getRouteDebugMessage($content));
}
}
49 changes: 37 additions & 12 deletions core-bundle/src/Routing/LegacyRouteProvider.php
Expand Up @@ -12,12 +12,17 @@

namespace Contao\CoreBundle\Routing;

use Contao\CoreBundle\Routing\Content\ContentResolverInterface;
use Symfony\Cmf\Component\Routing\RouteProviderInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Exception\RouteNotFoundException;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;

class LegacyRouteProvider implements RouteProviderInterface
/**
* @internal
*/
class LegacyRouteProvider implements ContentResolverInterface, RouteProviderInterface
{
/**
* @var FrontendLoader
Expand All @@ -29,27 +34,26 @@ class LegacyRouteProvider implements RouteProviderInterface
*/
private $routeProvider;

/**
* @internal Do not inherit from this class; decorate the "contao.routing.legacy_route_provider" service instead
*/
public function __construct(FrontendLoader $frontendLoader, RouteProviderInterface $routeProvider)
{
$this->frontendLoader = $frontendLoader;
$this->routeProvider = $routeProvider;
}

public function getRouteCollectionForRequest(Request $request): RouteCollection
public function supportsContent($content): bool
{
return $this->routeProvider->getRouteCollectionForRequest($request);
$routes = ['contao_frontend', 'contao_index', 'contao_root', 'contao_catch_all'];

return \is_string($content) && \in_array($content, $routes);
}

public function getRouteByName($name): Route
public function resolveContent($content): Route
{
if ('contao_frontend' === $name || 'contao_index' === $name) {
return $this->frontendLoader->load('.', 'contao_frontend')->get($name);
if ('contao_frontend' === $content || 'contao_index' === $content) {
return $this->frontendLoader->load('.', 'contao_frontend')->get($content);
}

if ('contao_root' === $name) {
if ('contao_root' === $content) {
return new Route(
'/',
[
Expand All @@ -60,7 +64,7 @@ public function getRouteByName($name): Route
);
}

if ('contao_catch_all' === $name) {
if ('contao_catch_all' === $content) {
return new Route(
'/{_url_fragment}',
[
Expand All @@ -72,11 +76,32 @@ public function getRouteByName($name): Route
);
}

return $this->routeProvider->getRouteByName($name);
throw new RouteNotFoundException('No route for '.$content);
}


public function getRouteCollectionForRequest(Request $request): RouteCollection
{
@trigger_error(__METHOD__.' has been deprecated in Contao 4.10', E_USER_DEPRECATED);

return $this->routeProvider->getRouteCollectionForRequest($request);
}

public function getRouteByName($name): Route
{
@trigger_error(__METHOD__.' has been deprecated in Contao 4.10', E_USER_DEPRECATED);

try {
return $this->resolveContent($name);
} catch (RouteNotFoundException $e) {
return $this->routeProvider->getRouteByName($name);
}
}

public function getRoutesByNames($names): array
{
@trigger_error(__METHOD__.' has been deprecated in Contao 4.10', E_USER_DEPRECATED);

return $this->routeProvider->getRoutesByNames($names);
}
}

0 comments on commit 223182e

Please sign in to comment.