/hack-routing

A PHP rewrite of HackRouter by Facebook

Primary LanguagePHPMIT LicenseMIT

Hack-Routing

Unit tests status Static analysis status Coding standards status Type Coverage Total Downloads Latest Stable Version License

Fast, type-safe request routing, parameter retrieval, and link generation.

It's a port of hack-router By Facebook, Inc.

Components

HTTP Exceptions

Exception classes representing common situations in HTTP applications:

  • HackRouting\HttpException\InternalServerErrorException
  • HackRouting\HttpException\MethodNotAllowedException
  • HackRouting\HttpException\NotFoundException

Router

A simple typed request router. Example:

<?php

use Psl\Str;
use HackRouting\Cache;
use HackRouting\Router;
use HackRouting\HttpMethod;
use HackRouting\HttpException;

$cache = new Cache\ApcuCache();
$router = new Router($cache);

$router->addRoute(HttpMethod::GET, '/', function (): string {
    return 'Hello, World!';
});

$router->addRoute(HttpMethod::GET, '/user/{username:[a-z]+}', function (array $parameters): string {
    return Str\format('Hello, %s!', $parameters['username']);
});

$router->addRoute(HttpMethod::POST, '/', function (): string {
    return 'Hello, POST world';
});

$router->addRoute(HttpMethod::GET, '/{page:about|contact}-us', static function (array $parameters): string {
    if ($parameters['page'] === 'about') {
        return 'Learn about us';
    }

    return 'Contact us';
});


try {
    [$responder, $parameters] = $router->match('GET', '/user/azjezz');
    
    $responder($parameters); // Hello, azjezz!
} catch (HttpException\MethodNotAllowedException $e) {
    $allowed_methods = $e->getAllowedMethods();
    // Handle 405.
} catch (HttpException\NotFoundException) {
    // Handle 404.
} catch (HttpException\InternalServerErrorException) {
    // Handle 500.
}

AbstractRouter

A more low-level router, which allows you to load routes using other means ( e.g. from configuration files ).

<?php

use Psl\Str;
use HackRouting\AbstractRouter;
use HackRouting\HttpMethod;

/**
 * @extends BaseRouter<(function(array<string, string>):string)>
 */
final class Matcher extends AbstractRouter {
  /**
   * @return array<non-empty-string, array<string, (function(array<string, string>):string)>>
   */
  protected function getRoutes(): array {
    return [
      HttpMethod::GET => [
        '/' => static fn(array $parameters): string => 'Hello, World!',
        '/user/{username}/' => static fn(array $parameters): string => Str\format('Hello, %s!', $parameters['username']),
      ],

      HttpMethod::POST => [
        '/' => static fn(array $parameters): string => 'Hello, POST world',
      ],
    ];
  }
}

Simplified for conciseness - see examples/AbstractRouterExample.php for full executable example.

UriPatterns

Generate route fragments, URIs (for linking), and retrieve URI parameters in a consistent and type-safe way:

<?php

use HackRouting\UriPattern\UriPattern;

final class UserPageController extends WebController {
  public static function getUriPattern(): UriPattern {
    return (new UriPattern())
      ->literal('/users/')
      ->string('user_name');
  }

  // ...
}

Parameters can be retrieved, with types checked at runtime both against the values, and the definition:

public function getResponse(): string {
  return 'Hello, '.$this->getUriParameters()->getString('user_name');
}

You can also generate links to controllers:

$link = UserPageController::getUriBuilder()
  ->setString('user_name', 'Mr Hankey')
  ->getPath();

These examples are simplified for conciseness - see examples/UriPatternsExample.php for full executable example.

Caching

HackRouting comes with 4 caching strategies.

  • HackRouting\Cache\ApcuCache
  • HackRouting\Cache\FileCache
  • HackRouting\Cache\MemoryCache
  • HackRouting\Cache\NullCache

By default, the router will use NullCache strategy, however, in production, it's extremely recommended using another strategy that fits your need.

If your application is running behind a traditional web-server ( i.e: fpm/fast-cgi ), we recommend using ApcuCache strategy if possible, falling back to FileCache.

If your application is used with a long-running process server such as Amphp, ReactPHP, RoadRunner ... etc, it's recommended to use MemoryCache to avoid additional I/O operations, and maximize performance.

Contributing

We welcome GitHub issues and pull requests - please see CONTRIBUTING.md for details.

License

hack-routing is MIT-licensed.