如何添加需要服务方法的路线要求?

时间:2019-06-06 15:03:37

标签: php symfony symfony-routing

我需要建立一条具有动态条件的路线。

目前,我仅使用requirements来使与静态列表的匹配单词:

/**
 * @Route(
 *     "/{category}/{id}",
 *     requirements={
 *         "category"="^(foo|bar)$"
 *     }
 * )
 *
 * ...
 */

但是现在我需要从服务方法中动态检索这些单词。

在寻找解决方案时,我对condition expression language充满了希望;但是唯一的 这里可以访问的变量是上下文和请求。但是,为了实现我的目标,我 需要对容器服务的完全访问权限。

换句话说,我希望执行以下伪php来测试路由:

if (in_array($category, MyService::getAllCategories())) {
    /* Inform Symfony that the route matches (then use this controller) */
} else {
    /* Inform Symfony that the route does not match and that the routing process
     * has to go on. */
}

请注意,我遇到问题的主要原因是将{category}参数放在 网址,然后可以取消其他路由。那我就不能只在 控制器,如果不需要该条件,则返回404。我肯定可以把这条路线放在 在路由过程中排在最后,但我认为这不是一个好的解决方案。

2 个答案:

答案 0 :(得分:1)

  

...参数放置在url的前面,然后可以取代其他路由。....

尽管以上内容使我感到困惑,但我希望我不要误会,这就是您所需要的。至少那是我所知道的!

  1. 您需要一个自定义注释类。例如namespace App\Annotation:Category
  2. 您上面的类将接受来自您的自定义注释条目的参数。例如@Category
  3. 您的自定义事件侦听器会将它们连接在一起以使其正常工作。例如namespace App\Event\Listener:CategoryAnnotationListener

This is a full example涵盖了方法和类级别的自定义注释。似乎您只需要方法级别,所以这里是您的示例。根据您的需要进行重构。 注意:经过测试,可以正常工作。

用法

declare(strict_types=1);

namespace App\Controller;

use App\Annotation\Category;

/**
 * @Route("/{category}/{id}")
 * @Category
 */
public function index...

类别

namespace App\Annotation;

/**
 * @Annotation
 */
class Category
{
}

监听器

declare(strict_types=1);

namespace App\Event\Listener;

use App\Annotation\Category;
use Doctrine\Common\Annotations\Reader;
use ReflectionClass;
use ReflectionException;
use RuntimeException;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;

class CategoryAnnotationListener
{
    private $annotationReader;

    public function __construct(Reader $annotationReader)
    {
        $this->annotationReader = $annotationReader;
    }

    public function onKernelController(FilterControllerEvent $event): void
    {
        if (!$event->isMasterRequest()) {
            return;
        }

        $controllers = $event->getController();
        if (!is_array($controllers)) {
            return;
        }

        $this->handleAnnotation($controllers, $event->getRequest()->getPathInfo());
    }

    private function handleAnnotation(iterable $controllers, string $path = null): void
    {
        list($controller, $method) = $controllers;

        try {
            $controller = new ReflectionClass($controller);
        } catch (ReflectionException $e) {
            throw new RuntimeException('Failed to read annotation!');
        }

        $method = $controller->getMethod($method);
        $annotation = $this->annotationReader->getMethodAnnotation($method, Category::class);

        if ($annotation instanceof Category) {
            $this->doYourThing($path);
        }
    }

    private function doYourThing(string $path = null): void
    {
        // Explode $path to extract "category" and "id"
        // Run your logic against MyService::getAllCategories()
        // Depending on the outcome either throw exception or just return 404
    }
}

配置

services:
    App\Event\Listener\CategoryAnnotationListener:
        tags:
            - { name: kernel.event_listener, event: kernel.controller, method: onKernelController }

答案 1 :(得分:1)

可以使用自定义路由加载器作为解决方案... http://symfony.com/doc/current/routing/custom_route_loader.html此示例生成的不是动态路由,但效果很好。

仅作为示例,假设CategoryProvider和Category是您的类...

<?php

// src/Routing/CategoryLoader.php
namespace App\Routing;

use Symfony\Component\Config\Loader\Loader;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
use App\CategoryProvider;

class CategoryLoader extends Loader
{
    private $isLoaded = false;

    private $catProvider;

    public function __construct(CategoryProvider $catProvider)
    {
        $this->catProvider = $catProvider;
    }

    public function load($resource, $type = null)
    {
        if (true === $this->isLoaded) {
            throw new \RuntimeException('Do not add the "extra" loader twice');
        }

        $routes = new RouteCollection();

        foreach ($this->catProvider->getAll() as $cat) {

            // prepare a new route
            $path = sprintf('/%s/{id}', $cat->getSlug());
            $defaults = [
                '_controller' => 'App\Controller\ExtraController::extra',
            ];
            $requirements = [
                'parameter' => '\d+',
            ];
            $route = new Route($path, $defaults, $requirements);

            // add the new route to the route collection
            $routeName = 'categoryRoute' . $cat->getSlug();
            $routes->add($routeName, $route);

        }

        $this->isLoaded = true;

        return $routes;
    }

    public function supports($resource, $type = null)
    {
        return 'extra' === $type;
    }
}