Home > Back-end >  How can I support multiple dynamic hosts within Symfony Routing?
How can I support multiple dynamic hosts within Symfony Routing?

Time:11-25

In my Symfony app (Symfony 5.3) I have to support the following scenario with multiple hosts/domains that belong to multiple app contexts (i.e. own firewall and own controllers, sometimes also shared controllers):

main-domain.tld -> main_context
main-domain2.tld -> main_context
service.main-domain.tld -> service_context
service.main-domain2.tld -> service_context
service.maybe-several-other-brand-domains.tld -> service_context
admin.main-domain.tld -> admin_context
admin.main-domain2.tld -> admin_context
admin.maybe-several-other-brand-domains.tld -> admin_context

How it started

Before we had multiple brands/domains, we had two main app contexts, that are addressed by their own hostnames. So we did something like this to assign the controllers to the context:

#[Route(
    path: '/',
    requirements: ['domain' => '%app.public_hostname_context1%'],
    defaults: ['domain' => '%app.public_hostname_context1%'],
    host: '{domain}',
)]
# where app.public_hostname_context1 is a hostname configured in the .env.local

How it is going

This worked well, until we decided to have more than one valid host for one of the contexts, in fact as much as the branding needs. So I did some research and came across the problem, that I cannot access the current hostname inside the defaults config and thus would have to set the domain explicitly on every url I generate.

Question is

How would you solve that requirement?

CodePudding user response:

I post my first approach of a solution as a direct answer, so please discuss it or shine with a better one. Maybe, I have overseen something and I have a slight feeling that that solution may be not the best one. And for others stumbling upon the same requirement, this whole Question will document at least one solution approach. :)

First, remove the defaults from the route definitions and provide a pattern for several valid domains of a context:

#[Route(
    path: '/',
    requirements: ['domain' => '%app.public_hostnames_context1_pattern%'],
    host: '{domain}',
)]
# app.public_hostname_context1_pattern is a pattern configured in the .env.local
# containing all possible hostnames for that context like
# PUBLIC_HOSTNAME_CONTEXT1_PATTERN=(?:service\.main-domain\.tld|service\.main-domain2\.tld)

To set the current hostname as a default for the domain parameter for all routes, I have a RequestListener inspired by this answer from 2012 that sets it, before the RouterListener does its work.

In my services.yaml:

    # must be called before the RouterListener (with priority 32) to load the domain
    App\EventListener\RequestListener:
        tags:
            - { name: kernel.event_listener, event: kernel.request, priority: 33 }

And the RequestListener:

<?php
declare(strict_types=1);
namespace App\EventListener;

use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\Routing\RouterInterface;

class RequestListener
{
    public function __construct(
        private RouterInterface $router,
    ){}

    public function onKernelRequest(RequestEvent $event)
    {
        if (false === $this->router->getContext()->hasParameter('domain')) {
            $this->router->getContext()->setParameter('domain', $event->getRequest()->getHost());
        }
    }
}

The good part of this is, that I can still override the domain parameter when I create URLs. But a drawback I see is: When I generate a URL for another context and don't set the domain explicitly, an error will be raised, because now the host of the current request is used as the domain for the other context and that is not allowed by the pattern within the requirements. I can live with that. Do you?

  • Related