Routing

As explained in the Model View Controller chapter, in Flow the dispatcher passes the request to a controller which then calls the respective action. But how to tell, what controller of what package is the right one for the current request? This is were the Routing Framework comes into play.

The Router

The request builder asks the router for the correct package, controller and action. For this it passes the current request to the routers route() method. The router then iterates through all configured routes and invokes their matches() method. The first route that matches, determines which action will be called with what parameters.

The same works for the opposite direction: If a link is generated the routers resolve() method calls the resolve() method of all routes until one route can return the correct URI for the specified arguments.

Note

If no matching route can be found, a NotFoundException is thrown which results in a 404 status code for the HTTP response and an error page being displayed. In Development context that error page contains some more details about the error that occurred.

Routes

A route describes the way from your browser to the controller - and back.

With the uriPattern you can define how a route is represented in the browser’s address bar. By setting defaults you can specify package, controller and action that should apply when a request matches the route. Besides you can set arbitrary default values that will be available in your controller. They are called defaults because you can overwrite them by so called dynamic route parts.

But let’s start with an easy example:

Example: Simple route - Routes.yaml

-
  name: 'Homepage'
  uriPattern: ''
  defaults:
    '@package': 'My.Demo'
    '@controller': 'Standard'
    '@action': 'index'

Note

name is optional, but it’s recommended to set a name for all routes to make debugging easier.

If you insert these lines at the beginning of the file Configurations/Routes.yaml, the indexAction of the StandardController in your My.Demo package will be called when you open up the homepage of your Flow installation (http://localhost/).

URI patterns

The URI pattern defines the appearance of the URI. In a simple setup the pattern only consists of static route parts and is equal to the actual URI (without protocol and host).

In order to reduce the amount of routes that have to be created, you are allowed to insert markers, so called dynamic route parts, that will be replaced by the Routing Framework. You can even mark route parts optional.

But first things first.

Static route parts

A static route part is really simple - it will be mapped one-to-one to the resulting URI without transformation.

Let’s create a route that calls the listAction of the ProductController when browsing to http://localhost/my/demo:

Example: Simple route with static route parts Configuration/Routes.yaml

-
  name: 'Static demo route'
  uriPattern: 'my/demo'
  defaults:
    '@package':    'My.Demo'
    '@controller': 'Product'
    '@action':     'list'

Dynamic route parts

Dynamic route parts are enclosed in curly brackets and define parts of the URI that are not fixed.

Let’s add some dynamics to the previous example:

Example: Simple route with static and dynamic route parts - Configuration/Routes.yaml

-
  name: 'Dynamic demo route'
  uriPattern: 'my/demo/{@action}'
  defaults:
    '@package':    'My.Demo'
    '@controller': 'Product'

Now http://localhost/my/demo/list calls the listAction just like in the previous example.

With http://localhost/my/demo/new you’d invoke the newAction and so on.

Note

It’s not allowed to have successive dynamic route parts in the URI pattern because it wouldn’t be possible to determine the end of the first dynamic route part then.

The @ prefix should reveal that action has a special meaning here. Other predefined keys are @package, @subpackage, @controller and @format. But you can use dynamic route parts to set any kind of arguments:

Example: dynamic parameters - Configuration/Routes.yaml

-
  name: 'Dynamic demo route with parameter'
  uriPattern: 'products/list/{sortOrder}.{@format}'
  defaults:
    '@package':    'My.Demo'
    '@controller': 'Product'
    '@action':     'list'

Browsing to http://localhost/products/list/descending.xml will then call the listAction in your Product controller and the request argument sortOrder has the value of descending.

By default, dynamic route parts match any simple type and convert it to a string that is available through the corresponding request argument. Read on to learn how you can use objects in your routes.

Object Route Parts

If a route part refers to an object, that is known to the Persistence Manager, it will be converted to its technical identifier (usually the UUID) automatically:

Example: object parameters - Configuration/Routes.yaml

-
  name: 'Single product route'
  uriPattern: 'products/{product}'
  defaults:
    '@package':    'My.Demo'
    '@controller': 'Product'
    '@action':     'show'

If you add this route above the previously generated dynamic routes, an URI pointing to the show action of the ProductController will look like http://localhost/products/afb275ed-f4a3-49ab-9f2f-1adff12c674f.

Probably you prefer more human readable URIs and you get them by specifying the object type:

-
  name: 'Single product route'
  uriPattern: 'products/{product}'
  defaults:
    '@package':     'My.Demo'
    '@controller':  'Product'
    '@action':      'show'
  routeParts:
    product:
      objectType: 'My\Demo\Domain\Model\Product'

This will use the identity properties of the specified model to generate the URI representation of the product.

Note

If the model contains no identity, the technical identifier is used!

Try adding the @Flow\Identity annotation to the name property of the product model. The resulting URI will be http://localhost/products/the-product-name

Note

The result will be transliterated, so that it does not contain invalid characters

Alternatively you can override the behavior by specifying an uriPattern for the object route part:

-
  name: 'Single product route'
  uriPattern: 'products/{product}'
  defaults:
    '@package':     'My.Demo'
    '@controller':  'Product'
    '@action':      'show'
  routeParts:
    product:
      objectType: 'My\Demo\Domain\Model\Product'
      uriPattern: '{category.title}/{name}'

This will add the title of the product category to the resulting URI: http://localhost/products/product-category/the-product-name The route part URI pattern can contain all properties of the object or it’s relations.

Note

For properties of type \DateTime you can define the date format by appending a PHP date format string separated by colon: {creationDate:m-Y}. If no format is specified, the default of Y-m-d is used.

Note

If an uriPattern is set or the objectType contains identity properties, mappings from an object to it’s URI representation are stored in the ObjectPathMappingRepository in order to make sure that existing links work even after a property has changed! This mapping is not required if no uriPattern is set because in this case the mapping is ubiquitous.

Internally the above is handled by the so called IdentityRoutePart that gives you a lot of power and flexibility when working with entities. If you have more specialized requirements or want to use routing for objects that are not known to the Persistence Manager, you can create your custom route part handlers, as described below.

Route Part Handlers

Route part handlers are classes that implement Neos\Flow\Mvc\Routing\DynamicRoutePartInterface. But for most cases it will be sufficient to extend Neos\Flow\Mvc\Routing\DynamicRoutePart and overwrite the methods matchValue and resolveValue.

Let’s have a look at a (very simple) route part handler that allows you to match values against configurable regular expressions:

Example: RegexRoutePartHandler.php

class RegexRoutePartHandler extends \Neos\Flow\Mvc\Routing\DynamicRoutePart {

        /**
         * Checks whether the current URI section matches the configured RegEx pattern.
         *
         * @param string $requestPath value to match, the string to be checked
         * @return boolean true if value could be matched successfully, otherwise false.
         */
        protected function matchValue($requestPath) {
                if (!preg_match($this->options['pattern'], $requestPath, $matches)) {
                        return false;
                }
                $this->value = array_shift($matches);
                return true;
        }

        /**
         * Checks whether the route part matches the configured RegEx pattern.
         *
         * @param string $value The route part (must be a string)
         * @return boolean true if value could be resolved successfully, otherwise false.
         */
        protected function resolveValue($value) {
                if (!is_string($value) || !preg_match($this->options['pattern'], $value, $matches)) {
                        return false;
                }
                $this->value = array_shift($matches);
                return true;
        }

}

The corresponding route might look like this:

Example: Route with route part handlers Configuration/Routes.yaml

-
  name: 'RegEx route - only matches index & list actions'
  uriPattern: 'blogs/{blog}/{@action}'
  defaults:
    '@package':    'My.Blog'
    '@controller': 'Blog'
  routeParts:
    '@action':
      handler:   'My\Blog\RoutePartHandlers\RegexRoutePartHandler'
      options:
        pattern: '/index|list/'

The method matchValue() is called when translating from an URL to a request argument, and the method resolveValue() needs to return an URL segment when being passed a value.

Note

For performance reasons the routing is cached. See Caching on how to disable that during development.

Warning

Some examples are missing here, which should explain the API better.

Optional route parts

By putting one or more route parts in round brackets you mark them optional. The following route matches http://localhost/my/demo and http://localhost/my/demo/list.html.

Example: Route with optional route parts - Configuration/Routes.yaml

-
  name: 'Dynamic demo route'
  uriPattern: 'my/demo(/{@action}.html)'
  defaults:
    '@package':    'My.Demo'
    '@controller': 'Product'
    '@action':     'list'

Note

http://localhost/my/demo/list won’t match here, because either all optional parts have to match - or none.

Note

You have to define default values for all optional dynamic route parts.

Case Sensitivity

By Default URIs are lower-cased. The following example with a username of “Kasper” will result in http://localhost/users/kasper

Example: Route with default case handling

-
  uriPattern: 'Users/{username}'
  defaults:
    '@package':    'My.Demo'
    '@controller': 'Product'
    '@action':     'show'

You can change this behavior for routes and/or dynamic route parts:

Example: Route with customised case handling

-
  uriPattern: 'Users/{username}'
  defaults:
    '@package':    'My.Demo'
    '@controller': 'Product'
    '@action':     'show'
  toLowerCase: false
  routeParts:
    username:
      toLowerCase: true

The option toLowerCase will change the default behavior for this route and reset it for the username route part. Given the same username of “Kasper” the resulting URI will now be http://localhost/Users/kasper (note the lower case “k” in “kasper”).

Note

The predefined route parts @package, @subpackage, @controller, @action and @format are an exception, they’re always lower cased!

Matching of incoming URIs to static route parts is always done case sensitive. So “users/kasper” won’t match. For dynamic route parts the case is usually not defined. If you want to handle data coming in through dynamic route parts case-sensitive, you need to handle that in your own code.

Exceeding Arguments

By default arguments that are not part of the configured route values are not appended to the resulting URI as query string.

If you need this behavior, you have to explicitly enable this by setting appendExceedingArguments:

-
  uriPattern: 'foo/{dynamic}'
  defaults:
    '@package':    'Acme.Demo'
    '@controller': 'Standard'
    '@action':     'index'
  appendExceedingArguments: true

Now route values that are neither defined in the uriPattern nor specified in the defaults will be appended to the resulting URI: http://localhost/foo/dynamicValue?someOtherArgument=argumentValue

This setting is mostly useful for fallback routes and it is enabled for the default action route provided with Flow, so that most links will work out of the box.

Note

The setting appendExceedingArguments is only relevant for creating URIs (resolve). While matching an incoming request to a route, this has no effect. Nevertheless, all query parameters will be available in the resulting action request via $actionRequest::getArguments().

Request Methods

Usually the Routing Framework does not care whether it handles a GET or POST request and just looks at the request path. However in some cases it makes sense to restrict a route to certain HTTP methods. This is especially true for REST APIs where you often need the same URI to invoke different actions depending on the HTTP method.

This can be achieved with a setting httpMethods, which accepts an array of HTTP verbs:

-
  uriPattern: 'some/path'
  defaults:
    '@package':    'Acme.Demo'
    '@controller': 'Standard'
    '@action':     'action1'
  httpMethods: ['GET']
-
  uriPattern: 'some/path'
  defaults:
    '@package':    'Acme.Demo'
    '@controller': 'Standard'
    '@action':     'action2'
  httpMethods: ['POST', 'PUT']

Given the above routes a GET request to http://localhost/some/path would invoke the action1Action() while POST and PUT requests to the same URI would call action2Action().

Note

The setting httpMethods is only relevant for matching URIs. While resolving route values to an URI, this setting has no effect.

Subroutes

Flow supports what we call SubRoutes enabling you to provide custom routes with your package and reference them in the global routing setup.

Imagine following routes in the Routes.yaml file inside your demo package:

Example: Demo Subroutes - My.Demo/Configuration/Routes.yaml

-
  name: 'Product routes'
  uriPattern: 'products/{@action}'
  defaults:
    '@controller': 'Product'

-
  name: 'Standard routes'
  uriPattern: '{@action}'
  defaults:
    '@controller': 'Standard'

And in your global Routes.yaml:

Example: Referencing SubRoutes - Configuration/Routes.yaml

-
  name: 'Demo SubRoutes'
  uriPattern: 'demo/<DemoSubroutes>(.{@format})'
  defaults:
    '@package': 'My.Demo'
    '@format':  'html'
  subRoutes:
    'DemoSubroutes':
      package: 'My.Demo'

As you can see, you can reference SubRoutes by putting parts of the URI pattern in angle brackets (like <subRoutes>). With the subRoutes setting you specify where to load the SubRoutes from.

Instead of adjusting the global Routes.yaml you can also include sub routes via Settings.yaml - see Subroutes from Settings.

Internally the ConfigurationManager merges together the main route with its SubRoutes, resulting in the following routing configuration:

Example: Merged routing configuration

-
  name: 'Demo SubRoutes :: Product routes'
  uriPattern: 'demo/products/{@action}.{@format}'
  defaults:
    '@package':    'My.Demo'
    '@format':     'html'
    '@controller': 'Product'

-
  name: 'Demo SubRoutes :: Standard routes'
  uriPattern: 'demo/{@action}.{@format}'
  defaults:
    '@package':    'My.Demo'
    '@format':     'html'
    '@controller': 'Standard'

You can even reference multiple SubRoutes from one route - that will create one route for all possible combinations.

Nested Subroutes

By default a SubRoute is loaded from the Routes.yaml file of the referred package but it is possible to load SubRoutes from a different file by specifying a suffix:

-
  name: 'Demo SubRoutes'
  uriPattern: 'demo/<DemoSubroutes>'
  subRoutes:
    'DemoSubroutes':
      package: 'My.Demo'
      suffix:  'Foo'

This will load the SubRoutes from a file Routes.Foo.yaml in the My.Demo package. With that feature you can include multiple Routes with your package (for example providing different URI styles). Furthermore you can nest routes in order to minimize duplication in your configuration. You nest SubRoutes by including different SubRoutes from within a SubRoute, using the same syntax as before. Additionally you can specify a set of variables that will be replaced in name, uriPattern, defaults and handler options of merged routes:

Imagine the following setup:

global Routes.yaml (Configuration/Routes.yaml):

-
  name: 'My Package'
  uriPattern: '<MyPackageSubroutes>'
  subRoutes:
    'MyPackageSubroutes':
      package: 'My.Package'

default package Routes.yaml (My.Package/Configuration/Routes.yaml):

-
  name: 'Product'
  uriPattern: 'products/<EntitySubroutes>'
  defaults:
    '@package':    'My.Package'
    '@controller': 'Product'
  subRoutes:
    'EntitySubroutes':
      package: 'My.Package'
      suffix:  'Entity'
      variables:
        'entityName': 'product'

-
  name: 'Category'
  uriPattern: 'categories/<EntitySubroutes>'
  defaults:
    '@package':    'My.Package'
    '@controller': 'Category'
  subRoutes:
    'EntitySubroutes':
      package: 'My.Package'
      suffix:  'Entity'
      variables:
        'entityName': 'category'

And in ``My.Package/Configuration/Routes.Entity.yaml``:

-
  name: '<entityName> list view'
  uriPattern: ''
  defaults:
    '@action': 'index'

-
  name: '<entityName> detail view'
  uriPattern: '{<entityName>}'
  defaults:
    '@action': 'show'

-
  name: '<entityName> edit view'
  uriPattern: '{<entityName>}/edit'
  defaults:
    '@action': 'edit'

This will result in a merged configuration like this:

-
  name: 'My Package :: Product :: product list view'
  uriPattern: 'products'
  defaults:
    '@package':    'My.Package'
    '@controller': 'Product'
    '@action':     'index'

-
  name: 'My Package :: Product :: product detail view'
  uriPattern: 'products/{product}'
  defaults:
    '@package':    'My.Package'
    '@controller': 'Product'
    '@action':     'show'

-
  name: 'My Package :: Product :: product edit view'
  uriPattern: 'products/{product}/edit'
  defaults:
    '@package':    'My.Package'
    '@controller': 'Product'
    '@action':     'edit'

-
  name: 'My Package :: Category :: category list view'
  uriPattern: 'categories'
  defaults:
    '@package':    'My.Package'
    '@controller': 'Category'
    '@action':     'index'

-
  name: 'My Package :: Category :: category detail view'
  uriPattern: 'categories/{category}'
  defaults:
    '@package':    'My.Package'
    '@controller': 'Category'
    '@action':     'show'

-
  name: 'My Package :: Category :: category edit view'
  uriPattern: 'categories/{category}/edit'
  defaults:
    '@package':    'My.Package'
    '@controller': 'Category'
    '@action':     'edit'

Subroutes from Settings

Having to adjust the main Routes.yaml whenever you want to include SubRoutes can be cumbersome and error prone, especially when working with 3rd party packages that come with their own routes. Therefore Flow allows you to include SubRoutes via settings, too:

Settings.yaml (Configuration/Settings.yaml):

Neos:
  Flow:
    mvc:
      routes:
        'Some.Package': true

This will include all routes from the main Routes.yaml file of the Some.Package (and all its nested SubRoutes if it defines any).

You can also adjust the position of the included SubRoutes:

Neos:
  Flow:
    mvc:
      routes:
        'Some.Package':
          position: 'start'

Internally Flow uses the PositionalArraySorter to resolve the order of SubRoutes loaded from Settings. Following values are supported for the position option:

  • start (<weight>)

  • end (<weight>)

  • before <key> (<weight>)

  • after <key> (<weight>)

  • <numerical-order>

<weight> defines the priority in case of conflicting configurations. <key> refers to another package key allowing you to set order depending on other SubRoutes.

Note

SubRoutes that are loaded via Settings will always be appended after Routes loaded via Routes.yaml Therefore you should consider getting rid of the main Routes.yaml and only use settings to include routes for greater flexibility.

It’s not possible to adjust route defaults or the UriPattern when including SubRoutes via Settings, but there are two more options you can use:

Neos:
  Flow:
    mvc:
      routes:
        'Some.Package':
          suffix: 'Backend'
          variables:
            'variable1': 'some value'
            'variable2': 'some other value'

With suffix you can specify a custom filename suffix for the SubRoute. The variables option allows you to specify placeholders in the SubRoutes (see Nested Subroutes).

Tip

You can use the flow:routing:list command to list all routes which are currently active, see CLI

Route Loading Order and the Flow Application Context

  • routes inside more specific contexts are loaded first

  • and after that, global ones, so you can specify context-specific routes

Caching

For performance reasons the routing is cached by default. During development of route part handlers it can be useful to disable the routing cache temporarily. You can do so by using the following configuration in your Caches.yaml:

Flow_Mvc_Routing_Route:
  backend: Neos\Cache\Backend\NullBackend
Flow_Mvc_Routing_Resolve:
  backend: Neos\Cache\Backend\NullBackend

Also it can be handy to be able to flush caches for certain routes programmatically so that they can be regenerated. This is useful for example to update all related routes when an entity was renamed. The RouterCachingService allows flushing of all route caches via the flushCaches() method. Individual routes can be removed from the cache with the flushCachesByTag() method.

Tagging

Any UUID string (see UuidValidator::PATTERN_MATCH_UUID) in the route values (when resolving URIs) and the match values (when matching incoming requests) will be added to the cache entries automatically as well as an md5 hash of all URI path segments for matched and resolved routes.

Custom route part handlers can register additional tags to be associated with a route by returning an instance of MatchResult / ResolveResult instead of true/false:

Example before: SomePartHandler.php

use Neos\Flow\Mvc\Routing\DynamicRoutePart;

class SomePartHandler extends DynamicRoutePart {

        protected function matchValue($requestPath) {
                // custom logic, returning false if the $requestPath doesn't match
                $this->value = $matchedValue;
                return true;
        }

        protected function resolveValue($value) {
                // custom logic, returning false if the $value doesn't resolve
                $this->value = $resolvedPathSegment;
                return true;
        }

}

Example now: SomePartHandler.php

use Neos\Flow\Mvc\Routing\Dto\MatchResult;
use Neos\Flow\Mvc\Routing\Dto\ResolveResult;
use Neos\Flow\Mvc\Routing\Dto\RouteTags;
use Neos\Flow\Mvc\Routing\DynamicRoutePart;

class SomePartHandler extends DynamicRoutePart {

        protected function matchValue($requestPath) {
                // custom logic, returning false if the $requestPath doesn't match, as before
                return new MatchResult($matchedValue, RouteTags::createFromTag('some-tag'));
        }

        protected function resolveValue($value) {
                // custom logic, returning false if the $value doesn't resolve, as before
                return new ResolveResult($resolvedPathSegment, null, RouteTags::createFromTag('some-tag'));
        }

}

All cache entries for routes using the above route part handler will be tagged with some-tag and could be flushed with $routerCachingService->flushCachesByTag('some-tag');.

URI Constraints

Most route parts only affect the path when resolving URIs. Sometimes it can be useful for route parts to affect other parts of the resolved URI. For example there could be routes enforcing https URIs, a specific HTTP port or global domain and path pre/suffixes.

In the last code example above the ResolveResult was constructed with the second argument being null. This argument allows route part handlers to specify UriConstraints that can pre-set the following attributes of the resulting URI:

  • Scheme (for example “https”)

  • Host (for example “www.somedomain.tld”)

  • Host prefix (for example “en.”)

  • Host suffix (for example “co.uk”)

  • Port (for example 443)

  • Path (for example “some/path”)

  • Path prefix (for example “en/”)

  • Path suffix (for example “.html”)

Let’s have a look at another simple route part handler that allows you to enforce https URLs:

Example: HttpsRoutePart.php

use Neos\Flow\Mvc\Routing\Dto\ResolveResult;
use Neos\Flow\Mvc\Routing\Dto\UriConstraints;
use Neos\Flow\Mvc\Routing\DynamicRoutePart;

class HttpsRoutePart extends DynamicRoutePart
{
    protected function resolveValue($value)
    {
        return new ResolveResult('', UriConstraints::create()->withScheme('https'));
    }

}

If a corresponding route is configured, like:

Example: Routes.yaml

-
  name: 'Secure route'
  uriPattern: '{https}'
  defaults:
    '@package':    'My.Demo'
    '@controller': 'Product'
    '@action':     'secure'
  routeParts:
    'https':
      handler: 'My\Demo\HttpsRoutePart'

All URIs pointing to the respective action will be forced to be https:// URIs.

As you can see, in this example the route part handler doesn’t affect the URI path at all, so with the configured route this will always point to the homepage. But of course route parts can specify a path (segment) and UriConstraints at the same time. They can also be used to resolve URIs across domains.

Routing Parameters

The last example only care about URI resolving. What if a route should react to conditions that are not extractable from the request URI path? For example the counter-part to the example above, matching only https:// URIs?

Warning

One could be tempted to access the current request from within the route part handler using Dependency Injection. But remember that routes are cached and that route part handlers won’t be invoked again once a corresponding cache entry exists.

For route part handlers to safely access values that are not encoded in the URI path, those values have to be registered as Routing Parameters, usually via a HTTP middleware (see respective chapter about HTTP Foundation).

A HTTP middleware that registers the current request scheme as Routing Parameter could look like this:

Example: SchemeRoutingParameterMiddleware.php

use Neos\Flow\Mvc\Routing\Dto\RouteParameters;
use Neos\Flow\Http\ServerRequestAttributes;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

class SchemeRoutingParameterMiddleware implements MiddlewareInterface
{

    public function process(ServerRequestInterface $request, RequestHandlerInterface $next): ResponseInterface
    {
        $existingParameters = $request->getAttribute(ServerRequestAttributes::ROUTING_PARAMETERS);
        if ($existingParameters === null) {
            $existingParameters = RouteParameters::createEmpty();
        }
        $parameters = $existingParameters->withParameter('scheme', $request->getUri()->getScheme());
        $request = $request->withAttribute(ServerRequestAttributes::ROUTING_PARAMETERS, $parameters);
        return $next->handle($request);
    }
}

Now we can extend the HttpRoutePart to only match https:// requests:

Example: HttpsRoutePart.php

use Neos\Flow\Mvc\Routing\Dto\ResolveResult;
use Neos\Flow\Mvc\Routing\Dto\UriConstraints;
use Neos\Flow\Mvc\Routing\DynamicRoutePart;

class HttpsRoutePart extends DynamicRoutePart
{
    protected function matchValue($value)
    {
        if ($this->parameters->getValue('scheme') !== 'https') {
      return false;
  }
  return true;
    }

    protected function resolveValue($value)
    {
        return new ResolveResult('', UriConstraints::create()->withScheme('https'));
    }

}

Note

For route part handlers to be able to access the Routing Parameters they have to implement the ParameterAwareRoutePartInterface and its matchWithParameters() method. The DynamicRoutePart already implements the interface and makes parameters available in the parameters field.

CLI

Flow provides the following four commands that allow you to test and debug the routing setup. Run ./flow help <command> to get more information about a command and its options.

routing:list

To list all active routes in the order they will be evaluated:

$ ./flow routing:list

Currently registered routes:
+----+---------------------------------------------------------------------------+----------------+-----------------------------------+
| #  | Uri Pattern                                                               | HTTP Method(s) | Name                              |
+----+---------------------------------------------------------------------------+----------------+-----------------------------------+
| 1  | some/route(/{@action}).{@format}                                          | GET, POST      | Some.Package :: Some route        |
| 2  | some/other/{route}                                                        | POST           | Some.Package :: Other route       |
| 3  | fallback                                                                  | any            | Some.Package :: Fallback          |
+----+---------------------------------------------------------------------------+----------------+-----------------------------------+

Run ./flow routing:show <index> to show details for a route

routing:show

To display details for a specific route:

$ ./flow routing:show 1

Information for route #1:

Name: Some.Package :: Some route
URI Pattern: some/route(/{@action}).{@format}
HTTP method(s): GET, Post
Defaults:
  @package: Some.Package
  @action: show
  @controller: SomeController

  Exceeding arguments will be appended as query string

routing:resolve

To build URLs for the given route values:

$ ./flow routing:resolve Neos.Welcome --controller Standard

Resolving:
  Values:
    @package: Neos.Welcome
    @controller: Standard
    @action: index
    @format: html
  Base URI: http://localhost
  Force absolute URI: no

  => Controller: Neos\Welcome\Controller\StandardController

Route resolved!
Name: Neos.Welcome :: Welcome screen
Pattern: flow/welcome

Resolved URI: /flow/welcome


Run ./flow routing:show 1 to show details about this route

Apart from route values, this command allows you to specify route parameters, for example in order to test URLs for the Neos frontend:

$ ./flow routing:resolve Neos.Neos --controller Frontend\\Node --action show --additional-arguments="{\"node\": \"/sites/neosdemo/the-book@live;language=en_US\"}" --parameters="{\"requestUriHost\": \"localhost\"}"

Resolving:
  Values:
    @package: Neos.Neos
    @controller: Frontend\Node
    @action: show
    @format: html
    node: /sites/neosdemo/the-book@live;language=en_US
  Base URI: http://localhost
  Force absolute URI: no
  Parameters:
    requestUriHost: localhost

  => Controller: Neos\Neos\Controller\Frontend\NodeController

Route resolved!
Name: Neos.Neos :: Frontend :: Default Frontend
Pattern: {node}

Resolved URI: /en/the-book/i-down-the-rabbit-hole.html

routing:match

To test the routing for incoming URLs:

$ ./flow routing:match /flow/welcome

Matching:
  URI: /flow/welcome
  Path: flow/welcome
  HTTP Method: GET

Route matched!
Name: Neos.Welcome :: Welcome screen
Pattern: flow/welcome

Results:
  @package: Neos.Welcome
  @controller: Standard
  @action: index
  @format: html

Matched Controller: Neos\Welcome\Controller\StandardController


Run ./flow routing:show 1 to show details about this route

Like routing:resolve this command allows you to specify route parameters too, for example to test routing for the Neos frontend:

$ ./flow routing:match /en/the-book/i-down-the-rabbit-hole.html --parameters="{\"requestUriHost\": \"localhost\"}"

Matching:
  URI: /en/the-book/i-down-the-rabbit-hole.html
  Path: en/the-book/i-down-the-rabbit-hole.html
  HTTP Method: GET
  Parameters:
    requestUriHost: localhost

Route matched!
Name: Neos.Neos :: Frontend :: Default Frontend
Pattern: {node}

Results:
  @package: Neos.Neos
  @controller: Frontend\Node
  @action: show
  @format: html
  node: /sites/neosdemo/the-book/i-down-the-rabbit-hole@live;language=en_US

Matched Controller: Neos\Neos\Controller\Frontend\NodeController


Run ./flow routing:show 75 to show details about this route