Search code examples
cakephpurl-routingcakephp-4.x

Routing for multiple hosts in Cakephp


As cookbook says:

Routes can use the _host option to only match specific hosts. You can use the *. wildcard to match any subdomain.

But what if I would like set same route for multiple hosts at once?

For example:

$routes->connect(
    '/images',        
    ['controller' => 'Images', 'action' => 'index']
)->setHost('images.example.com');

$routes->connect(
    '/images',        
    ['controller' => 'Images', 'action' => 'index']
)->setHost('images.example2.com');

$routes->connect(
    '/images',        
    ['controller' => 'Images', 'action' => 'index']
)->setHost('images.example3.com');

Above is pointless if I have to set several dozen of those routes.

Ideal would be something like this:

$routes->connect(
    '/images',        
    ['controller' => 'Images', 'action' => 'index']
)->setHosts(['images.example.com','images.example2.com','images.example3.com']);

Solution

  • That's not supported, you'll either have to set multiple routes accordingly, which you could simply do in a loop that you feed a list of your hosts:

    foreach (['images.example.com','images.example2.com','images.example3.com'] as $host) {
        $routes
            ->connect(
                '/images',        
                ['controller' => 'Images', 'action' => 'index']
            )
            ->setHost($host);
    }
    

    or create a custom route class that accepts either multiple hosts, or maybe actual regular expressions. The latter would probably be easier, as it wouldn't require reimplementing a lot of stuff for matching, something like:

    src/Routing/Route/RegexHostRoute.php

    namespace App\Routing\Route;
    
    use Cake\Routing\Route\DashedRoute;
    
    class RegexHostRoute extends DashedRoute
    {
        public function match(array $url, array $context = []): ?string
        {
            // avoids trying to match the _host option against itself in parent::match()
            if (!isset($url['_host'])) {
                return null;
            }
    
            return parent::match($url, $context);
        }
    
        public function hostMatches(string $host): bool
        {
            return preg_match('^@' . $this->options['_host'] . '@$', $host) === 1;
        }
    }
    

    That should allow to set a host like images\.example[2-3]?\.com:

    $routes
        ->connect(
            '/images',        
            ['controller' => 'Images', 'action' => 'index'],
            ['routeClass' => \App\Routing\Route\RegexHostRoute::class]
        )
        ->setHost('images\.example[2-3]?\.com');
    

    See also