Gaslawork docs

Routing

Gaslawork uses dynamic routing. Here is an example using one single route:

use Gaslawork\Routing\Router;
use Gaslawork\Routing\Route;

$routes = (new Router)
    ->add(
        (new Route("/:controller/:action/:id", "\\Controller\\{+controller}", "{action}Action"))
            ->setDefaults([
                "controller" => "Index",
                "action" => "index",
            ])
    );

In a perfect world this is the only route you will ever add.

The Router object will hold all the routes, and we add a Route object to it.

Deep dive into the internals

I’m now going to explain how the routing internals work. You do not need to know this, but I find it comforting knowing the internals of the framework I’m using, and maybe you do too:

A brief explanation of the controller and the action

Controllers has its own documentation, but to make sense of this document I’ll give you a short explanation.

A “controller” is a PHP class that has one or more methods (functions). These methods are what we call “actions”. Gaslawork will create a new instance of the controller (class), and then execute the correct action (method).

A very simple controller looks like this:

namespace Controller;

class Index extends \Gaslawork\Controller {

    public function indexAction()
    {
        print "Hello, world!";
    }

}

The path to the controller is \Controller\Index (since the namespace is Controller and the class name is Index).

When this controller/action combination is found by the router, an instance of \Controller\Index is created, and the indexAction() is called.

A controller can contain as many methods as you like.

The target and the handler

The Route object takes three variables:

Parameters

In the example above we have put /:controller/:action/:id as the “target”, and \\Controller\\{+controller} as the “handler”. Parts in the “target” that begins with : are considered as “parameters”. A parameter can later be fetched from by the controller, but can also be used in the “handler” to build the controller path. In the example above we are using the parameter controller as {+controller} in the “handler”. The plus sign means that the first character should be converted to upper case. Not using the plus sign ({controller}) uses the parameter as is.

A \Gaslawork\Exception\UndefinedRouteHandlerParameterException is going to be thrown if a parameter is used by the “handler” but the parameter is undefined or empty. Make sure that you either have default values set for the parameters used by the “handler”, or that the parameters are set as required. We are going to learn how to set parameter defaults and required parameters later in this document.

There is one special parameter named “action” which decides which action (method) in the controller is going to be run.

In the example above we are specifying the default values of the parameters by using the method setDefaults(). The defaults are are used a when parameter is not set by the URL.

Here are some examples of URL’s - using the route we defined in the example above:

URL Controller (Class) Action (Method)
/ \Controller\Index indexAction
/foo \Controller\Foo indexAction
/foo/bar \Controller\Foo barAction

You may have noticed in the previous example that the action (method) names are suffixed with Action. This is because of security. For example, calling index() on the Index class is treated as calling the constructor. You are free to change that by writing a different “action handler” string. Simply putting "{action}" in the “action handler” will use the method name as is (without any prefix or suffix). You are free to do however you like.

Fetching parameters

Parameters can be fetched by the controller or via the request object’s getParam() method.

class Index extends \Gaslawork\Controller {

    public function indexAction()
    {
        print $this->getParam("id");
        // Or:
        print \Gaslawork\Request::current()->getParam("id");
    }

}

Default parameters

All parameters can have default values.

$routes = (new Router)->add(
    (new Route("/:controller/:action/:id", "\\Controller\\{+controller}", "{action}Action"))
        ->setDefaults([
            "controller" => "welcome",
            "action" => "hello",
        ])
);

The example above will have the default controller to be welcome and the default action to be hello. That means that the URI / will match to the controller \Controller\Welcome\ and the action helloAction will be called.

You can set defaults for all parameters:

$routes = (new Router)->add(
    new Route("/:controller/:action/:id", "\\Controller\\{+controller}", "{action}Action"))
        ->setDefaults([
            "controller" => "welcome",
            "action" => "hello",
            "id" => "123",
        ])
);

This will make the id parameter to be 123 until the parameter is set in the URI.

Since the setDefaults() method overwrites all preexisting defaults, you can remove the default of controller and action. Be careful though, a route that is missing the controller is treated as invalid. A missing action will call the __invoke() method of the controller. This is of course perfectly valid and some developers prefer it.

You can also set a default for a parameter that is not in the “target”:

$routes = (new Router)->add(
    (new Route("/:action", "\\Controller\\{+controller}", "{action}Action"))
        ->setDefaults([
            "controller" => "hello",
            "action" => "index",
        ])
);

In this example the controller will always be \Controller\Hello. You can use this idea to totally remove actions from your application (or a specific route), and only use controllers:

$routes = (new Router)->add(
    (new Route("/:controller", "\\Controller\\{+controller}", "{action}Action"))
        ->setDefaults([
            "controller" => "index",
        ])
);

This route does not have an action, and can never have, so the __invoke() method of the controller will always be called.

White listing parameters

$routes = (new Router)
    ->add(
        (new Route("/:controller/:action/:id", "\\Controller\\{+controller}", "{action}Action"))
            ->setDefaults([
                "action" => "index",
            ])
            ->setWhitelist([
                "controller" => ["foo"],
            ])
    )
    ->add(new Route("/:controller/:action/:id", "\\Controller\\{+controller}", "{action}Action"));

We are creating a Route object and call setWhitelist() on it. We pass a dictionary to that method where the key is the name of the parameter. The value of the dictionary is an array of allowed values of the parameter. All other values than is specified here will not match the route.

The example above will call the controller \Controller\Special\Foo when the controller parameter is foo.

You can white list all parameters, not just the special onces.

Black listing parameters

$routes = (new Router)
    ->add(
        (new Route("/:controller/:action/:id", "\\Controller\\{+controller}", "{action}Action"))
            ->setDefaults([
                "controller" => "Index",
                "action" => "index",
            ])
            ->setBlacklist([
                "controller" => ["foo"],
            ])
    )
    ->add(
        (new Route("/:controller/:action/:id", "\\Controller\\Special\\{+controller}", "{action}Action"))
            ->setDefaults([
                "controller" => "Index",
                "action" => "index",
            ])
    );

This time we call the setBlacklist() method on the Route object. We pass a dictionary to that method where the key is the name of the parameter and the value is an array of non-allowed parameter values.

In this example above the controller \Controller\Bar will be called if the controller parameter is bar, but the controller \Controller\Special\Foo will be called when the controller parameter is foo.

Setting parameters as required

$routes = (new Router)
    ->add(
        (new Route("/:controller/:action/:id", "\\Controller\\WithId\\{+controller}", "{action}Action"))
            ->setDefaults([
                "controller" => "Index",
                "action" => "index",
            ])
            ->setRequired(["id"])
    )
    ->add(
        (new Route("/:controller/:action/:id", "\Controller\\", "{action}Action"))
            ->setDefaults([
                "controller" => "Index",
                "action" => "index",
            ])
    );

The example above sets the id parameter to be required. So the URI /Hello/World/123 will match the first route and the controller \Controller\WithId\Hello\ will be used.

Advanced routing

This is a fully working route:

/hello/:action/world/:controller/foo/:id

An example URL that matches this route is:

/hello/abc/world/def/foo

The controller will be def and the action will be abc.

Why dynamic routing is used

Many other frameworks choose to map URLs to controllers but Gaslawork’s routes are more performant when having a lot of URLs. PHP is executing the whole code every time, which means that the router’s objects needs to be built from scratch each time, wasting resources on things that are never going to be used. After all, when visiting a page only one route is the correct one, and all the other ones are created but never used. Even when using cache the objects needs to be unserialized to be used, making it slower the more URLs you have.