3

i want to match in a route a 'random' URL with a random sub path, fox example:

  • /example/1/a/b/c
  • /example/c3/s
  • ...

i implement this route in my .routing.yml file

modulo.validator:
  path: '/example/{path}'
  defaults:
    _controller: '\Drupal\modulo\Controller\Controller::check'
  requirements:
    _permission: 'TRUE'
    path: '.+'

I write it following Symfony2 standard but it doesn't works, seems Drupal override requirements in path.

Take look to system.routing.yml

system.private_file_download:
  path: '/system/files/{filepath}'
  defaults:
    _controller: 'Drupal\system\FileDownloadController::download'
  requirements:
    # Permissive regex to allow slashes in filepath see
    # http://symfony.com/doc/current/cookbook/routing/slash_in_parameter.html
    filepath: .+
    _access: 'TRUE'

why this doesn't work for me?

Any suggestion?

  • Have a look at \Drupal\image\PathProcessor\PathProcessorImageStyles and image.services.yml and image.routing.yml – user21641 Jan 10 '17 at 11:24
  • your example has _permission: 'TRUE' whereas it should be _access: 'TRUE'. if that is not a typo, it will explain why your route doesn't work. _permission takes an actual permission string – zerolab Jan 10 '17 at 11:51
6

The reason that the route in system.routing.yml works is that it gets help by an inbound path processor service, which stores the filepath to a query parameter in the request:

PathProcessorFiles.php

<?php

namespace Drupal\system\PathProcessor;

use Drupal\Core\PathProcessor\InboundPathProcessorInterface;
use Symfony\Component\HttpFoundation\Request;

/**
 * Defines a path processor to rewrite file URLs.
 *
 * As the route system does not allow arbitrary amount of parameters convert
 * the file path to a query parameter on the request.
 */
class PathProcessorFiles implements InboundPathProcessorInterface {

  /**
   * {@inheritdoc}
   */
  public function processInbound($path, Request $request) {
    if (strpos($path, '/system/files/') === 0 && !$request->query->has('file')) {
      $file_path = preg_replace('|^\/system\/files\/|', '', $path);
      $request->query->set('file', $file_path);
      return '/system/files';
    }
    return $path;
  }

}

This service is registered in system.services.yml:

system.services.yml

  services:

    path_processor.files:
      class: Drupal\system\PathProcessor\PathProcessorFiles
      tags:
        - { name: path_processor_inbound, priority: 200 }

And the controller gets the file path from the query parameter added by the processor:

FileDownloadController.php

  public function download(Request $request, $scheme = 'private') {
    $target = $request->query->get('file');
    ...

You can implement the same approach for your route to match everything after /example/.

0

The Drupal 8 example modules has a few examples of this functionality.

For example in the page_example module you'll see a page_example.routing.yml file with the following

page_example_arguments:
   path: 'examples/page-example/arguments/{first}/{second}'
   defaults:
       _controller: '\Drupal\page_example\Controller\PageExampleController::arguments'
   requirements:
       _permission: 'access arguments page'

And in the controller you have the arguments function that is called:

public function arguments($first, $second) {
    // Make sure you don't trust the URL to be safe! Always check for exploits.
    if (!is_numeric($first) || !is_numeric($second)) {
       // We will just show a standard "access denied" page in this case.
       throw new AccessDeniedHttpException();
    }

    $list[] = $this->t("First number was @number.", array('@number' => $first));
    $list[] = $this->t("Second number was @number.", array('@number' => $second));
    $list[] = $this->t('The total was @number.', array('@number' => $first + $second));

    $render_array['page_example_arguments'] = array(
       // The theme function to apply to the #items.
       '#theme' => 'item_list',
       // The list itself.
       '#items' => $list,
       '#title' => $this->t('Argument Information'),
     );

     return $render_array;
}
  • 1
    this is not a response, i know how routing work but i have to match everythings the / character too. – Cronos Jan 10 '17 at 11:31
  • Oké, it's not 100% clear what you want to achieve here then. I thought you had to troubles with the routing and then reading out the parameters to do something with them. – Fons Vandamme Jan 10 '17 at 11:33
  • If you want to alter an existing route you may want to check this page: drupal.org/docs/8/api/routing-system/… – Fons Vandamme Jan 10 '17 at 11:34
0

The accepted answer may be appropriate for the intended use case of the OP, however, the title of this question is "Routing match everything".

As this is the closest question I've found from searching the interwebs, I'll just share my discovery here on how to accomplish this for SPAs where all paths that start with "/app" need to be redirected to a single route.

In ./my_module/my_module.routing.yml:

my_module.app:
  path: '/app'
  defaults:
    _controller: '\Drupal\my_module\Controller\App::content'
  requirements:
    _access: 'TRUE' # Access is handled by app.

In ./my_module/my_module.services.yml:

  my_module.request_subscriber:
    class: Drupal\my_module\EventSubscriber\RequestSubscriber
    arguments: ['@router.route_provider', '@controller_resolver', '@http_kernel.controller.argument_resolver']
    tags:
      - { name: event_subscriber }

In ./my_module/src/EventSubscriber/RequestSubscriber.php:

<?php

namespace Drupal\my_module\EventSubscriber;

use Drupal\Core\Controller\ControllerResolverInterface;
use Drupal\Core\Routing\RouteProviderInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;

/**
 * Class RequestSubscriber.
 */
class RequestSubscriber implements EventSubscriberInterface  {

  /**
   * The Argument Resolver service.
   *
   * @var \Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface
   */
  protected $argumentResolver;

  /**
   * The Controller Resolver service.
   *
   * @var \Drupal\Core\Controller\ControllerResolverInterface
   */
  protected $controllerResolver;

  /**
   * The Route Provider service.
   *
   * @var \Drupal\Core\Routing\RouteProviderInterface
   */
  protected $routeProvider;

  /**
   * RequestSubscriber constructor.
   *
   * @param \Drupal\Core\Routing\RouteProviderInterface $routeProvider
   * @param \Drupal\Core\Controller\ControllerResolverInterface $controllerResolver
   * @param \Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface $argumentResolver
   */
  public function __construct(RouteProviderInterface $routeProvider, ControllerResolverInterface $controllerResolver, ArgumentResolverInterface $argumentResolver) {
    $this->routeProvider = $routeProvider;
    $this->controllerResolver = $controllerResolver;
    $this->argumentResolver = $argumentResolver;
  }

  /**
   * The request event handler.
   *
   * @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
   *   The event.
   */
  public function checkAppRequest(GetResponseEvent $event) {
    $request = $event->getRequest();
    $path = $request->getPathInfo();

    // Redirect all requests that start with "/app" requests to a single
    // route. Note: this is necessary because core doesn't have any other
    // way to really handle "wildcard/catch all" routes.
    if (strpos($path, '/app') === 0
      // Ignore CSS/JS (if it reaches here, it means the file doesn't exist).
      && !in_array(substr($path, -3), ['.css', '.js'])
      && !in_array(substr($path, -4), ['.map'])
    ) {
      $route = $this->routeProvider->getRouteByName('my_module.app');
      $definition = $route->getDefault('_controller');
      $controller = $this->controllerResolver->getControllerFromDefinition($definition, $path);
      $arguments = $this->argumentResolver->getArguments($request, $controller);
      $response = \call_user_func_array($controller, $arguments);
      if ($response instanceof Response) {
        // Set the response, necessary so the kernel knows it got something
        // which will also prevent any other event handler from running.
        $event->setResponse($response);
        return;
      }
    }
  }

  /**
   * {@inheritdoc}
   */
  public static function getSubscribedEvents() {
    // Check for /app requests.
    $events[KernelEvents::REQUEST][] = ['checkAppRequest', 1000];
    return $events;
  }

}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.