nette / routing

Nette Routing: two-ways URL conversion
https://doc.nette.org/routing
Other
231 stars 3 forks source link

Global prefix via route list #1

Closed sitole closed 5 years ago

sitole commented 5 years ago

Description

Recently, I've created many multi-domain sites and routing was painful. I think the route list can be the smartest than a simple array of routes and cache. There's my idea about prefix system.

Before:

$sectionDomain = '//section.example.com/';
$presentationDomain = '//example.com/';

$router = new RouteList;

$router[] = $section = new RouteList('Section');
$router[] = $presentation = new RouteList('Presentation');

// Register presentation home
$presentation[] = new Route($presentationDomain, 'Home:default');

// Register home presenter
$section[] = new Route($sectionDomain, 'Home:default');

// Register login presenter
$section[] = new Route($sectionDomain . 'login', 'Login:fefault');
$section[] = new Route($sectionDomain . 'logout', 'Login:logout');

$section[] = new Route($sectionDomain . 'clanky', 'Articles:default');
$section[] = new Route($sectionDomain . 'novinky', 'News:default');

After:

$router = new RouteList;

$router[] = $section = new RouteList('Section', '//section.example.com/');
$router[] = $presentation = new RouteList('Presentation', '//example.com/');

// Register presentation home
$presentation[] = new Route('', 'Home:default');

// Register home presenter
$section[] = new Route('', 'Home:default');

// Register login presenter
$section[] = new Route('login', 'Login:fefault');
$section[] = new Route('logout', 'Login:logout');

$section[] = new Route('clanky', 'Articles:default');
$section[] = new Route('novinky', 'News:default');
milo commented 5 years ago

Can be solved now at least in two ways. I'm using following router, when no need to setup different DI container:

<?php

declare(strict_types=1);

namespace App\Routing;

use App\Strict;
use Nette;
use Nette\Application\IRouter;
use Nette\Application\Request;

class DomainRouter implements IRouter
{
    use Strict;

    /** @var IRouter[] */
    private $domains = [];

    /** @var IRouter|null */
    private $matched;

    public function addRouter(string $host, IRouter $router): void
    {
        $this->domains[$host] = $router;
    }

    public function match(Nette\Http\IRequest $httpRequest)
    {
        $host = $httpRequest->getUrl()->getHost();
        $this->matched = $this->domains[$host] ?? null;
        return $this->matched ? $this->matched->match($httpRequest) : null;
    }

    public function constructUrl(Request $appRequest, Nette\Http\Url $refUrl)
    {
        return $this->matched
            ? $this->matched->constructUrl($appRequest, $refUrl)
            : null;
    }
}

Usage

$presentation = new RouteList;
$presentation[] = new Route('', 'Home:default');

$section = new RouteList;
$section[] = new Route('', 'Home:default');
$section[] = new Route('login', 'Login:fefault');
$section[] = new Route('logout', 'Login:logout');
$section[] = new Route('clanky', 'Articles:default');
$section[] = new Route('novinky', 'News:default');

$router = new DomainRouter;
$router->addDomain('example.com', $presentation);
$router->addDomain('section.example.com', $section);

And when you need different DI container, setup web server for a different document root with separated index.php and setup DI container with different router factories.

sitole commented 5 years ago

It looks like a possible solution, but the meaning is different. The list of routers may be a group with specific settings.

Something like:

$params = [
    RouteList::PREFIX => '//section.example.com',
    RouteList::POSTFIX=> '/',
];

$section = new RouteList('Section', $params);

But I do not know if it's for wide use or just for me. In that case, it does not make sense to add it as new feature.

dg commented 5 years ago

I want to add to RouteList syntactic sugar similar to forms, ie. instead of

$router = new RouteList();
$router[] = new Route(...);
$router[] = new Route(...);
$router[] = new MyRouter(...);

to allow something like

$router = new RouteList();
$router->addRoute(...);
$router->addRoute(...);
$router->add(new MyRouter...);

Because man don't need to deal with class names so much. And classes can be confusing after merging nette/application#208.

In relation to this issue your code can be written this way:

$router = new RouteList;
$router->withDomain('section.example.com')->withModule('Section')
    ->addRoute('', 'Home:default')

    // Register login presenter
    ->addRoute('login', 'Login:login')
    ->addRoute('logout', 'Login:logout')

    ->addRoute('clanky', 'Articles:default')
    ->addRoute('novinky', 'News:default');

$router->withDomain('example.com')->withModule('Presentation')
    ->addRoute('', 'Home:default');

What do you think?

sitole commented 5 years ago

It looks perfect. This features are comming in Nette 3/4 or later?

In my opinion setDomain and setModule is better than with*.

mabar commented 5 years ago

It's not a typical setter. In that case with and add methods return new instances. Also, second call to set* overrides previous call, it's not that case.

sitole commented 5 years ago

@mabar My bad. I thought the addRoute method is called on the $router.

milo commented 5 years ago

:+1: The withDomain() will work with patterns, like <foo>.example.com?

dg commented 5 years ago

Variables like %tld% are important, parameters like <foo> would be useful, but I'm afraid it would complicate the implementation.

mabar commented 5 years ago

Could I still use only RouteList and Route? I have own router builder which prefixes all routes with base part and sort them. Requirement to use domain and module routers would complicate that approach.

In route builder configuration could be base uri defined e.g. like

RouteList define just these 3 base parts and nette module, without knowledge about how will base url look.