artyuum/router

Yet another PHP request router.

dev-master 2021-10-20 18:16 UTC

This package is auto-updated.

Last update: 2024-05-21 00:10:25 UTC


README

hello world

Note: This router is not fully functional yet and is under development. A stable version will be released soon.

Features

  • RESTFul router.
  • Shipped with Symfony Http Foundation.
  • Supports named route parameters & placeholders. (WIP)
  • Supports route groups (and nested groups).
  • Supports named routes (with reverse routing). (WIP)
  • Supports before & after route middlewares.
  • Supports before & after global middlewares.
  • Supports routes prefixes.
  • Supports route mapping.
  • Supports custom handler for 404.

Installation

composer require artyuum/router:dev-master

Documentation

Registering a route

The router contains few methods for the most common HTTP methods that will help you to easily register a route:

$router->get(string $uri, $handler);
$router->post(string $uri, $handler);
$router->put(string $uri, $handler);
$router->patch(string $uri, $handler);
$router->delete(string $uri, $handler);
$router->options(string $uri, $handler);

These are just wrappers around the following method:

$router->addRoute(array $methods, string $uri, $handler);

Using the addRoute() method, you can register a route that matches more than one HTTP method:

$router->addRoute(['GET', 'POST'], string $uri, $handler);

The router doesn't limit your application to the most common HTTP methods. Indeed, you can also register a route that matches a custom HTTP method:

$router->addRoute(['BLAH'], string $uri, $handler);

The $handler argument must be either a callable or an array (if it's a class):

// using an anonymous function
$router->get('/', function(\Symfony\Component\HttpFoundation\Request $request, \Symfony\Component\HttpFoundation\Response $response) {
    echo 'hello world';
});

// using a function name
$router->get('/', 'myFunction');

// using an array (if it's a class)
$router->get('/', [HomepageController::class, 'index']);

Once a request matches one of the registered routes, the router will execute the handler and passes two arguments in the following order :

  1. Symfony\Component\HttpFoundation\Request $request

  2. Symfony\Component\HttpFoundation\Response $response

These arguments are part of the Symfony HTTP Foundation component and will help you to get more information about the request and easily build and send a response to the client. Feel free to read the docs to get more information about its usage.

Route parameters

Route parameters can be set using placeholders or pure regexes (PCRE) as follow:

$router->get('/profile/{username}', $handler); // will be internally converted to /profile/?<username>(\w+)

When using pure regex, it is important to give a name to the group (like above). Otherwise, the router won't be able get the parameters from the URL and it also won't be able to properly build an URL to a route using the url() method.

By default, placeholders will be converted to required parameters that will match any word of at least one letter, number or _. You can change this behavior by using the where() method, as follow:

$router->get('/profile/{id}', $handler)->where([
    'id' => '[0-9]+' // can also be written as "\d+"
]); // will match /profile/<one or n digit(s) from 0 to 9>

The where(array $placeholders) method takes an associative array as argument where the $key is the name of the placeholder, and the $value is a regex.

You can also set a placeholders as optional by appending a "?" sign after the placeholder's name, as follow:

$router->get('/profile/{username?}', $handler); // will match /profile OR /profile/<any word of at least one letter, number or _>

Named routes

Naming a route allows you to easily find a registered Route object by name. With the returned object, you will be able to access all methods of this object (e.g. getPath(), getName(), getMiddlewares(), getHandler(), etc...)

Example :

// registers a route named "homepage"
$router->get('/', $handler)->setName('homepage'); 

// registers a route named "user.delete"
$router->get('/users/{id}/delete', $handler)->setName('user.delete'); 

// gets the Route instance of the "homepage" route
$route = $route->getRoute('homepage');

// builds an url to a route
$url = $router->url('homepage'); // /home

// builds an url to a route with parameters
$url = $router->url('user.delete', ['id' => 1]); // /users/1/delete

Route groups

You can group routes using the group() method. This gives you the ability to set a prefixes or middlewares for all the routes inside the group.

Route uri prefixing

Example:

// homepage
$router->get('/', $handler); // will match "/"

// admin
$router->group(function(\Artyum\Router\RouteGroup $group) use ($router)
{
    $group->setPathPrefix('/admin');
    $router->get('/', $handler); // will match "/admin"
});

Route name prefixing

Example:

// homepage
$router->get('/', $handler); // will match "/"

// admin
$router->group(function(\Artyum\Router\RouteGroup $group) use ($router)
{
    $group->setNamePrefix('admin.');
    $router->get(string $uri, $handler)->setName('homepage'); // name will be "admin.homepage" 
});

Route middlewares

Example:

$router->group(function(\Artyum\Router\RouteGroup $group) use ($router)
{
    $group->addMiddlewares([
        'before' => [RateLimitMiddleware::class, AuthMiddleware::class],
        'after' => [LoggingMiddleware::class]
    ]);

    $router->get(string $uri, $handler);
    $router->post(string $uri, $handler);
});

If a request matches one of the registered routes, the router will do the following process in this order:

  1. In first, the handle() method from the RateLimitMiddleware class will be executed.
  2. In second, the handle() method from the AuthMiddleware class will be executed.
  3. In third, the matched route controller will be executed.
  4. And finally, the handle() method from the LoggingMiddleware class will be executed.

The router will automatically executes the handle() method from the middleware class so you don't need to specify it.

You can also add before/after route middlewares to a group as follow:

$router->group(function(\Artyum\Router\RouteGroup $group) use ($router)
{
    $group->setBeforeMiddlewares([RateLimitMiddleware::class, AuthMiddleware::class]);
    $group->setAfterMiddlewares([LoggingMiddleware::class]);
});

These are simply wrappers around the addMiddlewares() method.

Route mapping

You can map a single uri to multiple HTTP methods with differents handler, as follow:

$router->map('/users/{id}')
    ->put($handler)
    ->patch($handler)
    ->delete($handler);

Using the withAttributes() method, you will be able to get access to the Route object in order to set additional attributes to the route:

$router->map('/users/{id}')
    ->put($handler)->withAttributes(function(\Artyum\Router\Route $route) {
        $route
            ->setName('user.replace')
            ->setBeforeMiddlewares([RateLimitMiddleware::class, RolesMiddleware::class]);
     })
    ->patch($handler)->withAttributes(function(\Artyum\Router\Route $route) {
        $route
            ->setName('user.update')
            ->setBeforeMiddlewares([RateLimitMiddleware::class, RolesMiddleware::class]);
    })
    ->delete($handler)->withAttributes(function(\Artyum\Router\Route $route) {
        $route
            ->setName('user.delete')
            ->setBeforeMiddlewares([RateLimitMiddleware::class, RolesMiddleware::class]);
    });

It's also possible to add mapped routes to a group, that way you can set the middlewares for all routes that is inside the group and name prefix too:

$router->group(function(\Artyum\Router\RouteGroup $group) use ($router)
{
    $group->setBeforeMiddlewares([RateLimitMiddleware::class, RolesMiddleware::class]);
    $group->setNamePrefix('user.');

    $router->map('/users/{id}')
        ->put($handler)->withAttributes(function(\Artyum\Router\Route $route) {
            $route->setName('replace');
         })
        ->patch($handler)->withAttributes(function(\Artyum\Router\Route $route) {
            $route->setName('update');
        })
        ->delete($handler)->withAttributes(function(\Artyum\Router\Route $route) {
            $route->setName('delete');
        });
});

Route not found

By default, when the request doesn't match any registered routes, the dispatch() method will throw a "NotFoundException". You can change this behavior by registering a handler. The registered handler will be executed with the same arguments as controllers or middlewares.

setNotFoundHandler($handler);

Example:

// sends a 404 status code if no routes match the current request
$router->setNotFoundHandler(function(\Symfony\Component\HttpFoundation\Request $request, \Symfony\Component\HttpFoundation\Response $response) {
    $response
        ->setStatusCode(404)
        ->send();
});

Contributing

If you'd like to contribute, please fork the repository and make changes as you'd like. Pull requests are warmly welcome.