8

According to the Symfony documentation, a route defined as below should trigger the specified controller for both /hello/bob and /hello/bob/bobby:

_hello:
  path:     /hello/{names}
  defaults: { _controller: \Drupal\mymodule\Controller\Main::Controller }
  requirements:
    _access: 'TRUE'
    names: .+

In the case of a request to /hello/bob/bobby the {names} param would be "bob/bobby" (slash intact) and it would be up to the controller to break that down into multiple variables or to leave it as a single string. The trick to that is the altered regex (".+") used to filter that {names} param.

This stackoverflow post also implies that custom regex can be used to allow slashes in a route param (at least in Symfony 2).

If I try this against Drupal 8.0.0-beta15 it does not work and the specified controller is only triggered for a request to /hello/bob. However, I can confirm that this used to work in previous betas (I think up until ~beta13).

Has something changed in the way Drupal integrates with the Symfony routing component that would explain this? Perhaps there is an alternate way to accomplish the passing of slashes in routing params? I know there is a movement toward Symfony 3.0 in core, but I'm not sure if that could explain things.

I also know that route subscribers are available to manage dynamic route structures. However the case I am working on requires an almost infinite combination/number of dynamic parameters at the end of a base path (but which are trivial to parse in my controller). I'm also trying to avoid query strings (e.g. /hello?names[]=bob&names[]=bobby) for this case.

Mainly I'm just confused as to the disconnect with the Symfony documentation, which seems to state that this should be possible.


Additional Notes

After posting this question I discovered this discussion in the D8 core queues: [Discussion] Drop automated passing of extra argument: Y/N. It seems to conclude that "menu tail" support (which is essentially what I am after) will officially be dropped in D8. That discussion ended 3 years ago and so I can only guess that some of the more generalized implementation specifics were not fully realized until recently (~beta13). This may explain why I've only now noticed this change.

I'm guessing that Drupal (not Symfony) is now generating a 404 response based on the raw slash-delimited request before any of the Symfony-specific routing logic further dissects the route (and it's param-specific regex, etc.). If this is the case it could explain why technique above stopped working. I am still wondering however if there are alternative ways to deal with this need that avoid using query params and custom route subscribers.

  • From what I know about Symfony I would expect /hello/{username} to match on /hello/bob but not on /hello/bob/smith. If you want additional parameters, you have to set defaults for them as in stackoverflow.com/questions/11980175/…. But maybe I don't understand the question. – cilefen Sep 27 '15 at 2:31
  • Configuring defaults works as expected (i.e. it's possible to set {names} as an optional param). So yes, I suppose I could have a route path like /hello/{arg1}/{arg2}/{arg3}/.../{argN}, which could work with 0-N params. However that sets a static limit on the number of params and feels pretty messy. My understanding that it should be possible to do this differently, via slash delimiters in a single param, is based on the Symfony documentation (symfony.com/doc/master/cookbook/routing/slash_in_parameter.html) along with previous experience with older Drupal core betas. – rjacobs Sep 27 '15 at 4:41
  • 1
    It's not clear what path: /hello/{names} and username: .+ have to do with each other. – user49 Sep 27 '15 at 9:31
  • @chx, sorry, I had edited my question right after posting in an effort to make it more generic. In that process I forgot to update the yml snippet. I've fixed that. It should read "names: .+" – rjacobs Sep 27 '15 at 15:41
  • I just noticed that my last comment may give the impression that I solved things. Please note that I only had a typo in my post. The question/issue still stands as it's now phrased. – rjacobs Sep 28 '15 at 14:00
9

You can alter the path by adding a class that implements the InboundPathProcessorInterface

namespace Drupal\mymodule\PathProcessor;

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

class HelloPathProcessor implements InboundPathProcessorInterface {

  public function processInbound($path, Request $request) {
    if (strpos($path, '/hello/') === 0) {
      $names = preg_replace('|^\/hello\/|', '', $path);
      $names = str_replace('/',':', $names);
      return "/hello/$names";
    }
    return $path;
  }

}

This way the router will interpret the path /hello/bob/bobby as /hello/bob:bobby and you will get the parameters separated by : (or any other character that won't conflict with the parameters) in your controller.

You will also need to register the class as a service in mymodule.services.yml (make sure the priority is set higher than 200)

services:
  mymodule.path_processor:
    class: Drupal\mymodule\PathProcessor\HelloPathProcessor
    tags:
      - { name: path_processor_inbound, priority: 250 }
  • Yeah this is a good point, and I suppose this is the same mechanism that's used to do path aliasing? If going this route I assume that the router path would be unchanged (/hello/{names}) and then "names" could still be handled as a single route param. So if a URL using this route was generated programmatically names would still have to be constructed with the custom-delimiters (bob:bobby), but the system would still be compatible with more "friendly" URL input (bob/bobby)? – rjacobs Jan 20 '16 at 16:06
1

@Samuel Moncarey did a good job at describing this.

In addition to his response you can retrieve the parameters in your controller by implementing the following:

Your routing.yml file should look something like this.

mymodule.listings:
  path: /listings/{parms}
  defaults:
    _controller: 'Drupal\mymodule\Controller\MyController::content'
    _title: 'Page title'
  requirements:
    parms: '^[^\?]*$'
    _permission: 'access content'

Your controller should look something like this:

class MyController extends ControllerBase {

    public function content(Request $request) {
        // Get the path parameters
        $params = explode(':', $request->attributes->get('parms'));
    }
}
0

Alternative solution:

Create a route parameter that receives a json array.


mymodule.routing.yml

_hello:
  path:     /hello/{names}
  defaults: { _controller: \Drupal\mymodule\Controller\Main::index }
  requirements:
    _access: 'TRUE'

\Drupal\mymodule\Controller\Main

public function index($names_json) {
  $names = Json::decode($names_json);
  // Do something with $names
}
  • Yes, encoding multiple arguments into one route parameter is certainly an option. I assume you are implying that the "names" param would be a formal php array that's directly encoded/serialized? If so, wouldn't there be more appropriate encoding options for a path component that JSON? Note that this would also make for a much less user-friendly path regardless of the encoding technique (a potential issue for human input, but not necessarily for programmatic path generation). – rjacobs Jan 4 '16 at 22:50
  • Of course you can use any encoding technique you want. I prefer JSON as it is very standardized with an encoder (/decoder) in most languages. This would make paths much less user friendly. If the path is exposed outside then I think passing the names in the query (name[]=Ben&name[]=George) is much more readable. – Eyal Jan 4 '16 at 23:18

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.