Как я могу добавить требование маршрута, которое требует метода обслуживания? - PullRequest
0 голосов
/ 06 июня 2019

Мне нужно построить маршрут с динамическим условием.

На данный момент я просто использую requirements, чтобы сопоставить статический список с словами:

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

Но теперь мне нужно, чтобы эти слова динамически извлекались из сервисного метода.

При поиске решения я дал надежду на condition язык выражений ;но единственные переменные, которые доступны здесь, это контекст и запрос.Однако для достижения моей цели мне нужен полный доступ к сервисам контейнеров.

Другими словами, я бы хотел, чтобы для проверки маршрута выполнялся следующий псевдо-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} размещается в начале URL-адреса, а затем может игнорировать другие маршруты.Тогда я не могу просто проверить свое состояние внутри контроллера и вернуть 404, если условие не требуется.Конечно, я мог бы поместить этот маршрут в конец в порядке процесса маршрутизации, но я не думаю, что это хорошее решение.

Ответы [ 2 ]

1 голос
/ 07 июня 2019

Пользовательский маршрутный загрузчик может быть решением ... 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;
    }
}
1 голос
/ 06 июня 2019

... параметр помещается в начале ссылки, а затем может игнорировать другие маршруты .....

Хотя немного выше смущает меня, и я надеюсь, что я не понял неправильновот что тебе нужно.По крайней мере, это то, что я знаю!

  1. Вам нужен пользовательский класс аннотаций.например, namespace App\Annotation:Category
  2. Ваш класс выше примет параметры, поступающие из вашей пользовательской записи аннотации .например, @Category
  3. Ваш пользовательский прослушиватель событий свяжет их обоих вместе, чтобы заставить его работать.например, namespace App\Event\Listener:CategoryAnnotationListener

Это полный пример , который охватывает пользовательские аннотации как на уровне метода, так и на уровне класса.Похоже, вам нужен только уровень метода, так что вот ваш пример.Рефакторинг в соответствии с вашими потребностями. Примечание: Проверено и работает.

Использование

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 }
...