Skip to content

Routing

Routing configuration is defined in application modules in the file "etc/routing.php", located under the module's root directory. This file must return a class that implements Amarant\Framework\Contract\Routing\RoutingConfiguratorInterface.

Important

In production mode, routing configuration is cached under "var/cache" in the application root directory. If changes are made, this cache has to be deleted for the new configuration to take effect.

Create a route


Vendor/ModuleName/etc/routing.php
<?php

declare(strict_types=1);

use Amarant\Framework\Contract\Application\AreaInterface;
use Amarant\Framework\Contract\Application\ContextInterface;
use Amarant\Framework\Contract\Routing\RouteCollectionInterface;
use Amarant\Framework\Contract\Routing\RoutingConfiguratorInterface;
use Amarant\Framework\Routing\Route;
use Symfony\Component\HttpFoundation\Request;
use Vendor\ModuleName\Controller\RouteController;

return new class () implements RoutingConfiguratorInterface {
    #[Override] public function configure(
        ContextInterface $context,
        RouteCollectionInterface $routeCollection
    ): void {
        $routeCollection
            ->addRoute(
                Route::create(
                    path: '/some-route',
                    controller: RouteController::class,
                    name: 'some_route',
                    area: AreaInterface::AREA_FRONTEND,
                    methods: [Request::METHOD_GET],
                )
            );
    }
};

Path

Specifies the route's path.

Controller

The controller class. By default, the "__invoke" method will be called to execute the controller.
If a different method should be called, specify it by adding "::" and the method name.

For example:

controller: RouteController::class . '::theMethodNameToExecute',

Name

A unique name for the route.

Important

Use "_" , "." and alphanumeric characters only as route names. This is very important for page layouts whose filenames are determined using the route name. The layout builder converts all "_" to "." and expects the layout files
to have a very specific naming convention.

Area

Frontend, backend or global. Specifying a route to be global makes it available in both frontend and backend area.

Methods

A list of HTTP methods the route should support.

Route parameters


To specify route parameters, add them in the path wrapped in curly brackets, like so:

path: '/some-route/{param-a}/{param-b}',

To add parameter requirements, use a regular expression. For example, to require the parameters to be digits only:

path: '/some-route/{param-a:\d+}/{param-b:\d+}',

To add an optional part of the path, wrap it in square brackets:

path: '/some-route/{param-a:\d+}/{param-b:\d+}[/{param-c}]',

Warning

The optional part can only be the last part of the path.

Parameter resolving and injection

All route parameters will be resolved after the route is matched and passed onto the controller's executing method. The controller method should expect these parameters to all be in camelcase.

This means route parameters "param-a", "param-b" will be passed onto the method as "paramA", "paramB", which also means the method's signature should look something like this:

public function __invoke(string $paramA, string $paramB)

Or something like this if the parameters are supposed to be only digits:

public function __invoke(int $paramA, int $paramB)

The method can also declare additional parameters to be injected, for example this will inject the current request context:

public function __invoke(int $paramA, int $paramB, RequestContextInterface $requestContext)

The order of additional parameters in the method signature does not matter.

Additional parameter requirements


Route parameters can be additionally validated using regex or closures specified in route's "paramRequirements".

Vendor/ModuleName/etc/routing.php
paramRequirements: [
    // closure
    'param-a' => fn ($param) => \is_string($param) && \str_contains($param, 'x'),
    // regex
    'param-b' => '/[^0-9]/',
]

Access and data scopes


To require the user to have specific access or data scopes to be able to access the route, add the following to route's "metadata":

Vendor/ModuleName/etc/routing.php
metadata: [
    // require the user to have these access scopes
    Route::METADATA_REQUIRED_ACCESS_SCOPES => ['access_scope_a', 'access_scope_b'],
    // require the user to have access to the default data scope
    Route::METADATA_REQUIRED_DATA_SCOPES => [
        \Amarant\Framework\Config\DefaultScopeContextProvider::createScopeValue(
            \Amarant\Framework\Contract\Config\ScopeInterface::DEFAULT_SCOPE,
            \Amarant\Framework\Contract\Config\ScopeInterface::DEFAULT_SCOPE
        )
    ]
]

Default status code


To set a default status code a response should have when this route is matched, add the following to route's "metadata":

Vendor/ModuleName/etc/routing.php
metadata: [
    Route::METADATA_DEFAULT_STATUS_CODE => 202,
]

Note

This status code is applied only to layout results returned from controllers.

Hidden routes


Sometimes you may want a route to be able to forward to, but don't want the users to be able to visit it.

To make the router skip a route, add the following to route's "metadata":

Vendor/ModuleName/etc/routing.php
metadata: [
    Route::METADATA_NO_ROUTER => true,
]

Grouping


Routes can be added to groups.

Vendor/ModuleName/etc/routing.php
<?php

declare(strict_types=1);

use Amarant\Framework\Contract\Application\AreaInterface;
use Amarant\Framework\Contract\Application\ContextInterface;
use Amarant\Framework\Contract\Routing\RouteCollectionInterface;
use Amarant\Framework\Contract\Routing\RoutingConfiguratorInterface;
use Amarant\Framework\Routing\Route;
use Amarant\Framework\Routing\RouteGroup;
use Symfony\Component\HttpFoundation\Request;
use Vendor\ModuleName\Controller\RouteController;

return new class () implements RoutingConfiguratorInterface {
    #[Override] public function configure(
        ContextInterface $context,
        RouteCollectionInterface $routeCollection
    ): void {
        $myRouteGroup = $routeCollection->addGroup(
            new RouteGroup(
                'my-group', // (1)
                '/my-group-prefix' // (2)
            )
        );

        $myRouteGroup
            ->addRoute(
                Route::create(
                    path: '/some-route',
                    controller: RouteController::class,
                    name: 'some_route',
                    area: AreaInterface::AREA_FRONTEND,
                    methods: [Request::METHOD_GET],
                )
            );
    }
};
  1. Unique group name. If the group already exists, it won't be added to the collection. You can check and fetch the existing group using hasGroup and getGroup on the route collection.
  2. Optional path prefix. The paths of all routes in a group will be prefixed by this value.

Notice how we now add the route to the group and not to the collection. If we should add it to the collection, it would also become a part of a group, but the default one, which has no prefix.

Note

A route is matchable no matter if their path ends with a slash (/) or not. When adding a base route to a group with a prefix, you could declare the group's prefix ending without a slash, and then setting the route's path to just "/".


To add menu entry for a route:

Vendor/ModuleName/etc/routing.php
<?php

declare(strict_types=1);

use Amarant\Framework\Contract\Application\AreaInterface;
use Amarant\Framework\Contract\Application\ContextInterface;
use Amarant\Framework\Contract\Routing\RouteCollectionInterface;
use Amarant\Framework\Contract\Routing\RoutingConfiguratorInterface;
use Amarant\Framework\Routing\Menu\RouteMenu;
use Amarant\Framework\Routing\Menu\RouteMenuGroup;
use Amarant\Framework\Routing\Route;
use Symfony\Component\HttpFoundation\Request;
use Vendor\ModuleName\Controller\RouteController;

return new class () implements RoutingConfiguratorInterface {
    #[Override] public function configure(
        ContextInterface $context,
        RouteCollectionInterface $routeCollection
    ): void {
        $routeCollection->addMenuGroup(
            new RouteMenuGroup(
                groupId: 'my-group',        // (1)
                title: 'My group',          // (2)
                sortOrder: 10,              // (3)
                iconClass: 'bx bx-package'  // (4)
            )
        );

        $routeCollection
            ->addRoute(
                Route::create(
                    path: '/some-route',
                    controller: RouteController::class,
                    name: 'some_route',
                    area: AreaInterface::AREA_BACKEND,  // (5)
                    methods: [Request::METHOD_GET],
                )
                ->setMenu(
                    new RouteMenu(
                        groupId: 'my-group',    // (6)
                        title: 'My route',      // (7)
                        sortOrder: 10           // (8)
                    )
                )
            );
    }
};
  1. Unique menu group id. If a group exists, it will be overwritten. Check if a group exists using getMenuGroup first.
  2. The group's title, usually rendered as a dropdown menu title.
  3. Sort order used when rendering menu groups.
  4. Optional icon class, usually added to the group's title as an "<i>" element.
  5. Route menus, by default, are meant to be used in the backend (back office).
  6. The menu group id under which the menu entry will be rendered, usually as a link within a dropdown menu item.
  7. The title of the entry, usually the title of the link.
  8. Sort order used when rendering menus inside a menu group.

Note

The menu is rendered only in the backend (back office) theme Hadron. It's possible to use this functionality in the frontend, but you have to render it yourself and set the area parameter of such menu items to frontend.