mithridatem / router
Lightweight PHP routing library with configurable authorization and controller resolution.
Requires
- php: ^8.1
Requires (Dev)
- phpunit/phpunit: ^10.5
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-16 17:31:40 UTC
README
Mithridatem Router est une bibliotheque PHP oriente PSR-4 qui fournit un mecanisme de routage HTTP minimaliste, testable et independant de tout framework.
Caracteristiques clefs
- API fluide pour declarer des routes GET, POST ou multi-methodes avec segments dynamiques
{id}et wildcards/*. - Resolution de controlleurs parametres via references (
Route::controller) compatible avec un conteneur de dependances. - Controle d'acces branchable grace a l'interface
GrantCheckerInterface(implementationsAllowAllGrantChecker,ArrayGrantChecker; le routeur utiliseArrayGrantChecker([])par defaut). - Contexte de requete abstrait (
RequestContextInterface) pour isoler les superglobales PHP et faciliter les tests. - Base path configurable (
Router::setBasePath) pour les applications hebergees derriere un prefixe. - Exceptions explicites (
RouteNotFoundException,UnauthorizedException,RouterException) pour une gestion d'erreur claire.
Installation
composer require mithridatem/router
En developpement local sur ce depot, pensez a executer composer dump-autoload apres toute modification de namespace.
Premiers pas
<?php require __DIR__ . '/vendor/autoload.php'; use Mithridatem\Routing\Route; use Mithridatem\Routing\Router; $router = new Router(); // Route simple $router->map(Route::get('/', fn () => 'Hello world')); // Route avec parametre dynamique $router->map(Route::get('/books/{id}', function (string $id) { return "Livre #$id"; })); echo $router->dispatch();
Par defaut dispatch() detecte la requete courante via NativeRequestContext::fromGlobals(). Pour des tests unitaires, injectez votre propre contexte :
use Mithridatem\Routing\Context\RequestContextInterface; final class FakeContext implements RequestContextInterface { public function __construct(private string $path, private string $method) {} public function getPath(): string { return $this->path; } public function getMethod(): string { return $this->method; } public function getBearerToken(): ?string { return null; } } $router->dispatch(new FakeContext('/books/42', 'GET'));
Controlleurs et resolution
Lorsque vos handlers resident dans des classes, utilisez Route::controller() et laissez le routeur resoudre la methode correspondante :
$router->mapController( 'GET', '/admin/dashboard', App\Controller\AdminController::class, 'dashboard' );
Par defaut, DefaultControllerResolver instancie la classe sans dependances. Pour deleguer la resolution a un conteneur, fournissez votre propre implementation de ControllerResolverInterface :
$router->setControllerResolver(new ContainerAwareResolver($container));
Routage par Attributes et decouverte automatique
Depuis PHP 8.1, vous pouvez declarer les routes directement sur les methodes
des controleurs avec l'Attribute #[Route]. La decouverte automatique permet
de charger tous les controleurs du repertoire src/Controller/ (y compris
ses sous-repertoires) sans les enregistrer individuellement.
1. Configurer l'autoload PSR-4 de votre application
Dans le composer.json du projet utilisant la librairie :
{
"autoload": {
"psr-4": {
"App\\": "src/"
}
}
}
Apres une modification de l'autoload :
composer dump-autoload
Arborescence d'exemple :
mon-projet/
├── composer.json
├── public/
│ └── index.php
└── src/
└── Controller/
├── HomeController.php
└── Admin/
└── DashboardController.php
2. Declarer les routes dans les controleurs
Fichier src/Controller/HomeController.php :
<?php namespace App\Controller; use Mithridatem\Routing\Attribute\Route; class HomeController { #[Route(path: '/', method: 'GET')] public function index(): string { return 'Accueil'; } #[Route(path: '/books/{id}', method: 'GET')] public function show(string $id): string { return "Livre #$id"; } }
Fichier src/Controller/Admin/DashboardController.php :
<?php namespace App\Controller\Admin; use Mithridatem\Routing\Attribute\Route; class DashboardController { #[Route(path: '/admin/dashboard', method: 'GET', grants: ['ROLE_ADMIN'])] public function index(): string { return 'Tableau de bord'; } }
L'Attribute accepte aussi plusieurs methodes HTTP :
#[Route(path: '/endpoint', method: ['GET', 'POST'])].
3. Charger automatiquement les routes
Dans public/index.php :
<?php require __DIR__ . '/../vendor/autoload.php'; use Mithridatem\Routing\Loader\AttributeRouteLoader; use Mithridatem\Routing\Router; $router = new Router(); (new AttributeRouteLoader())->loadDirectory( router: $router, directory: __DIR__ . '/../src/Controller', namespace: 'App\\Controller' ); echo $router->dispatch();
Le dossier doit correspondre au namespace PSR-4. Par exemple,
src/Controller/Admin/DashboardController.php doit declarer
namespace App\Controller\Admin;.
Le chargement utilise l'autoload Composer et n'inclut pas directement les fichiers trouves. Les classes non autoloadables sont ignorees.
4. Autorisations et compatibilite
Les routes contenant grants sont refusees par defaut en l'absence de
droits configures. Pour cet exemple :
use Mithridatem\Routing\Auth\ArrayGrantChecker; $router->setGrantChecker(new ArrayGrantChecker(['ROLE_ADMIN']));
Vous pouvez toujours melanger decouverte automatique et declaration manuelle :
use Mithridatem\Routing\Route; $router->map(Route::get('/health', fn () => 'OK'));
5. Injection de dependances dans un controleur
La decouverte automatique enregistre les routes, mais elle ne construit
pas les controleurs avec leurs dependances. Le resolver par defaut appelle
new Controller() ; pour injecter des services, il faut fournir un
ControllerResolverInterface personnalise.
Cet exemple utilise un registre d'instances, sans dependre d'un framework ou d'un conteneur externe.
Service : src/Service/ProductService.php
<?php namespace App\Service; final class ProductService { public function findName(string $id): string { return 'Produit #' . $id; } }
Controleur : src/Controller/ProductController.php
<?php namespace App\Controller; use App\Service\ProductService; use Mithridatem\Routing\Attribute\Route; final class ProductController { public function __construct(private ProductService $products) { } #[Route(path: '/products/{id}', method: 'GET')] public function show(string $id): string { return $this->products->findName($id); } }
Resolver : src/Routing/InstanceControllerResolver.php
<?php namespace App\Routing; use Mithridatem\Routing\Controller\ControllerResolverInterface; use Mithridatem\Routing\Handler\ControllerReference; use Mithridatem\Routing\Exception\RouterException; final class InstanceControllerResolver implements ControllerResolverInterface { /** @param array<class-string, object> $controllers */ public function __construct(private array $controllers) { } public function resolve(ControllerReference $controller): callable { $class = $controller->getClassName(); $method = $controller->getMethodName(); $instance = $this->controllers[$class] ?? null; if ($instance === null) { throw new RouterException("Controleur non enregistre : $class"); } if (!is_callable([$instance, $method])) { throw new RouterException("Methode non accessible : $class::$method"); } return [$instance, $method]; } }
Point d'entree : public/index.php
<?php require __DIR__ . '/../vendor/autoload.php'; use App\Controller\ProductController; use App\Routing\InstanceControllerResolver; use App\Service\ProductService; use Mithridatem\Routing\Loader\AttributeRouteLoader; use Mithridatem\Routing\Router; $products = new ProductService(); $productController = new ProductController($products); $router = new Router(); $router->setControllerResolver(new InstanceControllerResolver([ ProductController::class => $productController, ])); (new AttributeRouteLoader())->loadDirectory( router: $router, directory: __DIR__ . '/../src/Controller', namespace: 'App\\Controller' ); echo $router->dispatch();
La route GET /products/42 renvoie Produit #42. La declaration
#[Route] est toujours decouverte automatiquement ; seule la creation
de l'instance du controleur est explicite. Les autres controleurs decouverts
doivent aussi etre enregistres dans ce resolver, sous peine d'erreur lors
du dispatch. Pour une application plus importante, remplacez le registre
par un resolver deleguant a un conteneur PSR-11.
Consultez egalement le guide detaille : Decouverte automatique des controleurs.
Gestion des autorisations
Associez des droits a une route en fournissant un tableau de "grants" :
use Mithridatem\Routing\Auth\ArrayGrantChecker; $router->setGrantChecker(new ArrayGrantChecker(['ROLE_ADMIN'])); $router->map(Route::get('/admin', fn () => 'Admin panel', ['ROLE_ADMIN']));
Si l'utilisateur courant ne possede aucun des droits requis, une UnauthorizedException est levee. Branchez votre propre verificateur via GrantCheckerInterface pour relier vos mecanismes d'authentification.
Base path et sous-repertoires
Hebergez votre application derriere un prefixe sans re-ecrire vos routes :
$router->setBasePath('/mon-app');
Toutes les correspondances s'effectuent alors relativement a ce prefixe.
Gestion des erreurs
- Route introuvable :
RouteNotFoundException. - Acces refuse :
UnauthorizedException. - Erreur de resolution :
RouterException.
Capturez ces exceptions et adaptez la reponse HTTP de votre application.
Tests et exemples
composer install
composer test
Un exemple minimal est disponible dans examples/basic.php. Des doubles de tests se trouvent dans tests/Support pour simuler le contexte HTTP et les controlleurs.
Aller plus loin
- Documentation d'architecture :
docs/architecture.md. - Guide de migration :
docs/migration.md. - Processus de contribution :
CONTRIBUTING.mdetSECURITY.md.