简体   繁体   中英

Symfony 2 How To Dynamically Add Routes With Access To The Request Object

Background:

I am trying to conditionally load routes based on the request host. I have a database setup that has hosts in it that map to templates. If a user comes in from the host A and that uses template TA I want to load the routes for that template. If they come in from host B then load the routes for that template ( TB ).

The reason I have to do this is because each template will share many routes. There are however some unique routes for a given template.

It would be fine to restrict each template routes to a given host, except that there are literally 1000's of hosts.

What I Have Tried:

I have tried a custom route loader as described in the documentation here:

http://symfony.com/doc/current/cookbook/routing/custom_route_loader.html

However when i configure the service and try and inject the "@request" the constructor fails because $request is null

services:
    acme_demo.routing_loader:
        class: Acme\DemoBundle\Routing\ExtraLoader
        arguments: ["@request"]
        tags:
            - { name: routing.loader }

Class:

<?php
namespace: Acme\DemoBundle\Routing;
use Symfony\Component\HttpFoundation\Request;
class ExtraLoader
{
    protected $request;

    public function __construct(Request $request)
    {
        $this->request = $request;
    }
    // ...
}

This also doesnt work if I try and switch "@request" for "@service_container" then call

$this->container->get('request');

The closest I got to getting this working was following a guide found here:

http://marcjschmidt.de/blog/2013/11/30/symfony-custom-dynamic-router.html

The problem i have with this on is im trying to use annotation on a controller and i cant seem to get the Symfony\\Component\\Routing\\Loader\\AnnotationFileLoader working.

Ok I have finally figured out a working solution, Its a mix of several of the above mentioned guides.

Im using a kernel request listener:

services:
    website_listener:
        class: NameSpace\Bundle\EventListener\WebsiteListener
        arguments:
            - "@website_service"
            - "@template_service"
            - "@sensio_framework_extra.routing.loader.annot_dir"
            - "@router"
            - "%admin_domain%"
        tags:
            - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest, priority: 33 }

The Listener:

<?php
namespace NameSpace\WebsiteBundle\EventListener;

use NameSpace\TemplateBundle\Service\TemplateService;
use NameSpace\WebsiteBundle\Service\WebsiteService;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Routing\Loader\AnnotationDirectoryLoader;
use Symfony\Component\HttpKernel\EventListener\RouterListener;
use Symfony\Component\Routing\Matcher\UrlMatcher;
use Symfony\Component\Routing\RequestContext;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Bundle\FrameworkBundle\Routing\Router;
use Symfony\Component\PropertyAccess\PropertyAccess;

class WebsiteListener
{

    /**
     * @var WebsiteService
     */
    protected $websiteService;

    /**
     * @var TemplateService
     */
    protected $templateService;

    /**
     * @var AnnotationDirectoryLoader
     */
    protected $loader;

    /**
     * @var Router
     */
    protected $router;

    /**
     * @var string
     */
    protected $adminDomain;

    public function __construct(WebsiteService $websiteService, TemplateService $templateService, AnnotationDirectoryLoader $loader, Router $router, $adminDomain)
    {
        $this->websiteService = $websiteService;
        $this->templateService = $templateService;
        $this->loader = $loader;
        $this->router = $router;
        $this->adminDomain = $adminDomain;
    }

    public function loadRoutes()
    {
        $template = $this->templateService->getTemplateByAlias($this->websiteService->getWebsite()->getTemplate());

        $routes = $this->loader->load($template['routes'],'annotation');
        $allRoutes = $this->router->getRouteCollection();
        $allRoutes->addCollection($routes);
    }

    public function onKernelRequest(GetResponseEvent $event)
    {
        try {
            $this->websiteService->handleRequest($event->getRequest());
            $this->loadRoutes();
        } catch(NotFoundHttpException $e) {
            if($event->getRequest()->getHost() !== $this->adminDomain){
                throw $e;
            }
        }

    }
}

The Key parts of this are:

  • The Loader - I found "@sensio_framework_extra.routing.loader.annot_dir" in the source code. That the annotation directory loader that symfony uses by default so thats the one that I want to use too. But if you want to use a different loader there are others available.
  • The Router - This is what i use to get all of the current routes. NOTE that the $allRoutes->addCollection($routes) call is on a seperate line. Im not sure why it makes a difference but calling it all in 1 like was not working.
  • $template['routes'] is just a namespaces controller reference like you would use to add routing in your routing.yml. Something like: "@NamespaceBundle/Controller"

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM