ifcanduela/router

A routing component wrapping FastRoute

1.1.0 2021-09-15 13:10 UTC

This package is auto-updated.

Last update: 2024-04-15 18:45:27 UTC


README

PHP router wrapping nikic/fast-route, with support for nested route groups, default parameters and a few other extra features.

Installation

Use Composer:

composer require ifcanduela/router

Usage

Create an ifcanduela\router\Router instance and define routes:

$router = new ifcanduela\router\Router();
$router->get("/")->to("my_controller");

Loading routes from a file

Loading routes from a file is the preferred method to initialise a router. Create a file like this, for example routes.php:

<?php

/** @var ifcanduela\router\Router $r */

$r->get("/")->to("home");

$r->group("/admin", function (ifcanduela\router\Group $g) {
    $g->get("/dashboard")->to("admin@dashboard");
});

// `$this` can also be used

$this->post("/save")->to("admin@save");

And load the file like this:

$router = new ifcanduela\router\Router();
$router->loadFile("routes.php", "r");

The second argument to loadRoute() defines the name of the router in the loaded file; the default is simply router. Additionally, the $this metavariable is always available in the loaded file and refers to the calling Group or Router.

Files can be loaded from nested groups also:

$router->group("/admin", function (ifcanduela\router\Group $g) {
    $g->loadFile("admin-routes.php");

    $g->group("/settings", function (ifcanduela\router\Group $g) {
        $g->loadFile("admin-settings-routes.php");
    });
});

Route definitions

Routes are mostly created using Router or Group methods:

$group = new Group();
$group->from("/home")->to("home_controller");
$group->get("/login")->to("login_form");
$group->post("/login")->to("login_submit");

Behind the scenes, those methods call the static constructors in the Route class:

$group = new Group();
$group->routes([
    Route::from("/home")->to("home_controller"),
    Route::get("/login")->to("login_form"),
    Route::post("/login")->to("login_submit"),
]);

The get, post, put and delete methods create routes that only match requests with the same HTTP method. The from method allows GET and POST by default, but the Group::from method accepts more string arguments with the HTTP methods to allow.

Additionally, it's possible to call the methods method on a route to override its allowed methods:

Route::from("/update-user/{id}", "get", "post")->to("api@updateUser");
Route::from("/create-user")->to("api@createUser")->methods(["PATCH"]);

Routes follow the syntax defined by nikic/fast-route, so you can add parameters using curly braces and wrap segments in square brackets to make them optional. It's also possible to add default values for optional parameters:

Route::from("/projects/{id}[/{version}]")
    ->to("project_dashboard")
    ->default("version", "latest");

Route groups

When more than one route share a prefix or metadata, you can put them together inside a group. There are several ways to define groups, but all of them have the same result. First, the generic way, in which the group is treated as a router:

$adminGroup = $router->group();
$adminGroup->prefix("/admin");
$adminGroup->get("/dashboard")->to([AdminController::class, "dashboard"]);
$adminGroup->get("/users")->to([UsersController::class, "index"]);

A more clear way is to use a callback to define the grouped routes:

$router->group("/admin", function ($adminGroup) {
    $adminGroup->prefix("/admin");
    $adminGroup->get("/dashboard")->to([AdminController::class, "dashboard"]);
    $adminGroup->get("/users")->to([UsersController::class, "index"]);
});

Resolving the route

The router will need a path and a HTTP method to resolve a route. If no matching route is found, an exception is thrown.

$request = \Symfony\Component\HttpFoundation\Request::createFromGlobals();
$pathInfo = $request->getPathInfo();
$method = $request->getMethod();

try {
    $route = $router->resolve($pathInfo, $method);
} catch (\ifcanduela\router\InvalidHttpMethod $e) {

} catch (\ifcanduela\router\RouteNotFound $e) {

}

Route metadata

It's possible to attach extra information to routes to better identify them and facilitate their use in the request/response cycle.

Route names

Routes can be identified by names, which can help later when creating URLs to them:

$router->get("/example/view/{id}")->to("example@view")->name("example.view");

$url = $router->createUrlFromRoute("example.view", [123]);
//=> "/example/view/123"

You can check if a path and method combination matches a route name:

$pathInfo = $request->getPathInfo();
$method = $request->getMethod();

$router->isRoute("example.view", $pathInfo, $method);
//=> true/false

Tagging routes (before/after)

Tagging routes is useful to run middleware before or after matching them.

Route::from("/path")->to("handler")
    ->before(MyMiddleware::class, "some_other_thing");

A different way of tagging routes is by using namespaces:

Route::from("/admin/index")->to("admin_index")
    ->namespace("admin");

If you are using tags for middleware, you can use global middleware (applied to all routes) by attaching it to the router itself:

$router->before(StartSession::class);
$router->after(ConvertToResponse::class, SendResponse::class);

Once the route is resolved, all applicable tags can be accessed using $route->getBefore() and $route->getAfter().

License

MIT.