3. Routing

Radar for PHP

3. Routing

Before continuing, remove the default Hello route from the web/index.php file. It looks like this:

$adr->get('Hello', '/{name}?', function (array $input) {
        $payload = new \Aura\Payload\Payload();
        return $payload
            ->setStatus($payload::SUCCESS)
            ->setOutput([
                'phrase' => 'Hello ' . $input['name']
            ]);
    })
    ->defaults(['name' => 'world']);

That will prepare your project for real use.

3.1. Adding A Route

The Radar routing system is based on Aura.Router, with some details modified specifically for Radar.

In other frameworks, a route points an incoming request to a particular controller class and action method. In Radar, every action is identical, in the sense that it receives input, invokes a domain element, and passes the domain output to a responder. This means that a Radar route does not point to an action per se, but to a trio of action-related elements: an input handler, a domain element, and a responder.

Let's add an HTTP PATCH route. In web/index.php, call $adr->patch() with a route name and URL path, and a Domain specification.

$adr->patch('Todo\EditItem', '/todo/{id}', 'Domain\Todo\ApplicationService\EditItem');

The route name doubles as a class name prefix for optional Input and Responder classes. We will talk more about that later.

The path is typical for routing systems, using placeholder tokens for route attribute values.

The Domain specification is a string or an array:

  • If a string, Radar will instantiate this class using the internal dependency injection container and call its __invoke() method with the user input from the HTTP request.

  • If an array in the format ['ClassName', 'method'], the dependency injection container will instantiate the specfied class name, and then call the specified method with the user input from the HTTP request.

3.2. Manually Specifying A Custom Input Class

By default, each Radar route uses the Radar\Adr\Input class to collect user input and use it for parameters to the Domain call. Essentially, the input-collection logic looks like this:

    public function __invoke(ServerRequestInterface $request)
    {
        return [
            array_merge(
                (array) $request->getQueryParams(),
                (array) $request->getAttributes(),
                (array) $request->getParsedBody(),
                (array) $request->getUploadedFiles()
            )
        ];
    }

This means the Domain element has to receive a single $input array as its only parameter. The $input is merged from a list of sources, with later sources overriding earlier ones. This is naive but useful as a default case.

For more serious and useful input collection, you can write a callable class that mimics the above __invoke() signature, and have it return the parameters to pass to the Domain element. For example, if your Domain call takes an $id and a $data array as its parameters, you might do this:

use Psr\Http\Message\ServerRequestInterface;

class GenericIdAndDataInput
{
    public function __invoke(ServerRequestInterface $request)
    {
        $id = $request->getAttribute('id');
        $data = $request->getParsedBody();
        return [$id, $data];
    }
}

You can then specify this class as the input callable to be used for your route by calling input() on the route object:

$adr->patch('Todo\EditItem', '/todo/{id}', 'Domain\Todo\ApplicationService\EditItem')
    ->input('GenericIdAndDataInput');

If you want to set your own default input class instead of the Radar one, call $adr->input() directly instead of as part of a route specification:

$adr->input('MyDefaultInputClass');

That class will be used as the default input callable for all route actions.

3.3. Manually Specifying A Custom Responder Class

By default, each Radar route uses the Radar\Adr\Responder\Responder class to build the HTTP response. Because presentation work is so dependent on the domain elements being presented, it is difficult to outline how Responders work in detail; please examine the Responder class to get a good handle on what's going on there.

In the mean time, let it suffice to say that you are almost certain to want to build your own Responder classes, and then specify them as the responder callable for one or more of your routes. To do so, call responder() on the route, and pass the responder class name:

$adr->patch('Todo\EditItem', '/todo/{id}', 'Domain\Todo\ApplicationService\EditItem')
    ->responder('MyTodoResponder');

If you want to set your own default responder class instead of the Radar one, call $adr->responder() directly instead of as part of a route specification:

$adr->responder('MyDefaultResponderClass');

N.b.: As a side note, if your responder class implements Radar\Adr\Responder\ResponderAcceptsInterface, the route will automatically apply an accepts() pre-filter to help with content negotiation. This is not content-negotiation proper, only a filter to make sure the responder can handle at least one of the content types acceptable by the client.

3.4. Automatic Input And Responder Discovery

Earlier, we noted that the route name doubles as a class name prefix for optional Input and Responder classes. This means that if you name your classes according to a Radar convention, the route will automatically pick up on those classes and use them for input() and responder() on the route.

For example, given a route named Todo\EditItem, you could create a Todo\EditItem\Input class and/or a Todo\EditItem\Responder class. If they exist, the route will use them automatically. Of course, you can still manually specify an input() and responder() value, and those will be used instead.

3.5. Other Route Specifications

The $adr object acts as a proxy for the underlying Aura\Router\Map instance, so all the Map methods are also available on the $adr object for defining your routes. Please see the Aura.Router Map documentation for more information.