Skip to content

Commit

Permalink
Introducing page providers
Browse files Browse the repository at this point in the history
  • Loading branch information
aschempp committed Apr 8, 2020
1 parent a8dcc22 commit 692742d
Show file tree
Hide file tree
Showing 7 changed files with 298 additions and 102 deletions.
3 changes: 3 additions & 0 deletions core-bundle/src/Resources/config/services.yml
Expand Up @@ -462,6 +462,7 @@ services:
arguments:
- '@contao.framework'
- '@database_connection'
- !tagged_locator { tag: contao.page_provider, index_by: type, default_index_method: getPageType }
- '%contao.url_suffix%'
- '%contao.prepend_locale%'

Expand Down Expand Up @@ -573,6 +574,7 @@ services:
class: Contao\CoreBundle\Routing\DelegatingUrlGenerator
arguments:
- !tagged_iterator contao.content_resolver
- !tagged_locator { tag: contao.page_provider, index_by: type, default_index_method: getPageType }
- '@?logger'

contao.routing.route_provider:
Expand All @@ -581,6 +583,7 @@ services:
- '@contao.framework'
- '@database_connection'
- '@contao.routing.candidates'
- !tagged_locator { tag: contao.page_provider, index_by: type, default_index_method: getPageType }
- '%contao.prepend_locale%'

contao.routing.route_404_provider:
Expand Down
35 changes: 19 additions & 16 deletions core-bundle/src/Routing/Candidates/Candidates.php
Expand Up @@ -13,8 +13,11 @@
namespace Contao\CoreBundle\Routing\Candidates;

use Contao\CoreBundle\Framework\ContaoFramework;
use Contao\CoreBundle\Routing\Content\PageProviderInterface;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\FetchMode;
use Symfony\Cmf\Component\Routing\Candidates\CandidatesInterface;
use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\HttpFoundation\Request;

class Candidates implements CandidatesInterface
Expand Down Expand Up @@ -59,11 +62,16 @@ class Candidates implements CandidatesInterface
* @var array
*/
private $urlSuffix;
/**
* @var ServiceLocator
*/
private $pageProviders;

public function __construct(ContaoFramework $framework, Connection $database, string $urlSuffix, bool $prependLocale)
public function __construct(ContaoFramework $framework, Connection $database, ServiceLocator $pageProviders, string $urlSuffix, bool $prependLocale)
{
$this->framework = $framework;
$this->database = $database;
$this->pageProviders = $pageProviders;
$this->prependLocale = $prependLocale;

$this->languagePrefix = [];
Expand Down Expand Up @@ -189,25 +197,20 @@ private function initialize(): void
return;
}

$urlSuffix = [];
$languagePrefix = [];

$pages = $this->database
->query('SELECT urlSuffix, languagePrefix FROM tl_page')
->fetchAll()
$languagePrefix = $this->database
->query("SELECT DISTINCT languagePrefix FROM tl_page WHERE type='root'")
->fetchAll(FetchMode::COLUMN)
;

foreach ($pages as $page) {
if ($page['urlSuffix']) {
$urlSuffix[] = $page['urlSuffix'];
}
$urlSuffix = [];

if ($page['languagePrefix']) {
$languagePrefix[] = $page['languagePrefix'];
}
foreach ($this->pageProviders->getProvidedServices() as $type => $v) {
/** @var PageProviderInterface $provider */
$provider = $this->pageProviders->get($type);
$urlSuffix[] = $provider->getUrlSuffixes();
}

$this->urlSuffix = array_unique($urlSuffix);
$this->languagePrefix = array_unique($languagePrefix);
$this->urlSuffix = array_filter(array_unique(array_merge(...$urlSuffix)));
$this->languagePrefix = array_filter($languagePrefix);
}
}
71 changes: 71 additions & 0 deletions core-bundle/src/Routing/Content/PageProviderInterface.php
@@ -0,0 +1,71 @@
<?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\HttpFoundation\Request;
use Symfony\Component\Routing\Route;

/**
* A page provider provides information about a page type.
* The page provider knows what route for a page of its type looks like, e.g. if
* a particular route has a different URL suffix than configured in the root page.
*/
interface PageProviderInterface
{
public const ATTRIBUTE = '_page_provider';

/**
* While matching URLs, contao generates alias candidates and looks for matching page models.
* Based on these page's "type" property, the respective page provider is asked for the route,
* as only the page provider knows about requirements and defaults.
*
* To generate URLs for a page, the respective page provider needs to return a route based on
* the page configuration. If $content is available, it can be used to enhance route defaults,
* so the route can be generated even if no parameters have been passed to the router generate() method.
*
* When matching URLs, $content is empty and $request is present. When generating URLs,
* $content _might_ be there, but there's never a "current" $request.
*
* @param mixed $content
*/
public function getRouteForPage(PageModel $pageModel, $content = null, Request $request = null): Route;

/**
* The URL suffixes are used to generate alias candidates, which means
* stripping the URL suffix from a request path to find pages with
* the remaining path as an alias.
*
* Only return a non-empty array if this page does not use the global
* URL suffix as configured in the root page, e.g if this is a "feed" page type,
* it could return ".rss" and ".atom" as the URL suffixes.
*/
public function getUrlSuffixes(): array;

/**
* If the page supports content composition, it's layout is defined by a Contao
* page layout, and it supports articles and content elements.
*
* Most Contao page types do support composition. Pages that do not support composition
* can be structural (e.g. a redirect page) or functional (e.g. an XML sitemap).
*
* The optional $pageModel might tell if a particular page supports composition,
* for example a 404 page that redirects cannot have articles, but a regular 404 does.
*/
public function supportContentComposition(PageModel $pageModel = null): bool;

/**
* Gets the page type of this provider.
*/
public static function getPageType(): string;
}
25 changes: 24 additions & 1 deletion core-bundle/src/Routing/DelegatingUrlGenerator.php
Expand Up @@ -16,24 +16,34 @@
use Psr\Log\LoggerInterface;
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
use Symfony\Cmf\Component\Routing\VersatileGeneratorInterface;
use Symfony\Component\DependencyInjection\ServiceLocator;
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;

use Contao\CoreBundle\Routing\Content\PageProviderInterface;
use Contao\CoreBundle\Routing\Content\PageRoute;

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

public function __construct($resolvers, LoggerInterface $logger = null)
/**
* @var ServiceLocator
*/
private $pageProviders;

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

$this->resolvers = $resolvers;
$this->pageProviders = $pageProviders;
}

/**
Expand Down Expand Up @@ -112,6 +122,19 @@ private function resolveContent($content): Route
}
}

if ($route instanceof PageRoute) {
$page = $route->getPage();

if ($this->pageProviders->has($page->type)) {

/** @var PageProviderInterface $pageProvider */
$pageProvider = $this->pageProviders->get($page->type);

$route = $pageProvider->getRouteForPage($route->getPage(), $route->getContent());
$route->setDefault(PageProviderInterface::ATTRIBUTE, $pageProvider);
}
}

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

0 comments on commit 692742d

Please sign in to comment.