mycodebox / minirouter
A minimal PHP router
Requires
- php: >=8.3
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.94
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^13.0
README
A minimalist, flexible PHP router for small and medium-sized projects.
Features: Named routes, middleware, groups, reverse routing, dependency injection, flexible response types, complex placeholders, forwarding, and more.
Table of Contents
- Overview & Features
- Installation
- Quick Start
- Routing & Placeholders
- Middleware
- Container
- Groups & API
- Request & Response
- Reverse Routing
- Error Handling
- Complete Demo
- API Overview
- License & Author
Overview & Features
- Routing for all HTTP methods (
GET,POST,PUT,PATCH,DELETE,OPTIONS,HEAD) - Placeholders & complex patterns (
/user/{id:\d+}) - Named routes & reverse routing (
urlFor) - Middleware (global, per route, per group, as class)
- Route groups with prefix and shared middleware
- Dependency injection via MiniContainer
- Automatic body parsing (JSON, form data)
- Flexible response types (text, JSON, HTML, download)
- Server-side forwarding
- Error handling & debug mode
Installation
- Copy the files from
src/into your project. - Install Composer dependencies (if needed).
- Create an entry point, e.g.,
index.php, or use the exampleexample/demo.php.
Quick Start
Tip:
MiniRouter supports middleware and dependency injection via container.
See examples in the next section.
Option 1: With Composer (recommended)
- Install dependencies (only needed once):
composer require mycodebox/minirouter
- Example code:
require_once __DIR__ . '/../vendor/autoload.php'; use MyCodebox\MiniRouter\Core\MiniRouter; $router = new MiniRouter(); $router->get('/hello/{name}', function ($req, $res, $args) { return $res->withHeader('Content-Type', 'text/plain') ->withBody('Hello, ' . $args['name'] . '!'); }); $router->dispatch();
Middleware
MiniRouter supports different types of middleware:
Global Middleware
$router->addMiddleware(function ($req, $res, $next) { // Logging, authentication, etc. return $next($req, $res); });
Route Middleware
$router->get('/secure', $handler) ->addMiddleware(function ($req, $res, $next) { // Route-specific logic return $next($req, $res); });
Group Middleware
$router->group('/api', function ($group) { $group->addMiddleware(function ($req, $res, $next) { // Group-specific logic return $next($req, $res); }); $group->get('/user/{id}', $handler); });
Middleware as a Class
class ApiKeyMiddleware implements MiniMiddlewareInterface { public function process($req, $res, $next) { // Class-based middleware logic return $next($req, $res); } } $router->addMiddleware(new ApiKeyMiddleware());
Container
Use the built-in container for dependency injection:
use MyCodebox\MiniRouter\Core\MiniContainer; $container = new MiniContainer(); $container->set('greetingService', fn() => fn($name) => "Hello, $name!"); $router = new MiniRouter($container); $router->get('/hello/{name}', function ($req, $res, $args) use ($container) { $greeting = $container->get('greetingService'); return $res->withBody($greeting($args['name'])); });
Service Variants with MiniContainer
You can register and use services in the container in various ways:
Variant 1: Anonymous Function (Closure)
$container->set('greetingService', fn($c) => fn($name) => "Hello, $name!"); $greet = $container->get('greetingService'); echo $greet('Max'); // Hello, Max!
Variant 2: Without Container Parameter
$container->set('simpleGreeting', fn() => fn($name) => "Hi, $name!"); $greet = $container->get('simpleGreeting'); echo $greet('Anna'); // Hi, Anna!
Variant 3: Using a Class
class GreetingService { public function greet($name) { return "Hello, $name!"; } } $container->set('greetingService', fn($c) => new GreetingService()); $service = $container->get('greetingService'); echo $service->greet('Tom'); // Hello, Tom!
Variant 4: Using an Array as a Service
$container->set('config', fn() => [ 'greeting' => 'Hello', 'farewell' => 'Goodbye' ]); $config = $container->get('config'); echo $config['greeting'] . ', Max!'; // Hello, Max!
Variant 5: Explicit Service Factory
$container->set('greetingService', function($c) { return function($name) { return "Hello, $name!"; }; }); $greet = $container->get('greetingService'); echo $greet('Lisa'); // Hello, Lisa!
Routing & Placeholders
-
Simple route:
$router->get('/hello/{name}', $handler); -
Complex pattern:
$router->get('/user/{id:\d+}', $handler); // Numbers only -
Named route:
$router->get('/foo', $handler)->setName('foo_route'); -
Reverse routing:
$url = $router->urlFor('foo_route'); -
ANY route (multi-method route):
// Route that responds to multiple methods: $router->any(['GET', 'POST', 'put'], '/any-demo', function ($req, $res) { return $res->withBody(['method' => $req->method]); }); // Methods can be written in any case!
How do placeholders and complex patterns work?
-
Simple placeholders:
Everything in{}is recognized as a variable and passed as a value in$argsto the handler.
Example:/hello/{name}matches/hello/World→$args['name'] === 'World' -
Complex patterns with regex:
After the name, a colon and a regular expression can follow, e.g.,{id:\d+}for numbers only.
Example:/user/{id:\d+}matches/user/42, but not/user/abc. -
Multiple placeholders:
You can use as many placeholders as you like, e.g.,/blog/{year:\d{4}}/{slug}. -
Default behavior:
Without regex, a placeholder accepts everything except/([^/]+). -
Internal:
The router automatically converts the pattern into a regular expression and extracts the values as$args.
Examples:
$router->get('/product/{id:\d+}', function ($req, $res, $args) { // $args['id'] is guaranteed to be a number }); $router->get('/blog/{year:\d{4}}/{slug}', function ($req, $res, $args) { // $args['year'] = "2023", $args['slug'] = "my-article" }); $router->get('/foo/{bar}', function ($req, $res, $args) { // $args['bar'] accepts anything except slash }); // ANY route example: $router->any(['get', 'POST'], '/any', function ($req, $res) { return $res->withBody(['method' => $req->method]); }); // Methods can be written in any case!
Notes
- Case-insensitivity:
When registering HTTP methods (e.g., withany), case does not matter. You can mix'get','GET','Post', etc. - Quality:
The project is checked with PHPUnit tests and PHPStan.
Groups & API
$router->group('/api', function ($group) { $group->addMiddleware(new ApiKeyMiddleware()); $group->get('/user/{id:\d+}', $handler); $group->post('/user', $handler); // ... });
Request & Response
- Request:
$req->method,$req->uri,$req->query,$req->body,$req->headers$req->getHeader('X-API-Key')$req->withAttribute('key', $value),$req->getAttribute('key')
- Response:
$res->withStatus(201)$res->withHeader('Content-Type', 'application/json')$res->withBody(['foo' => 'bar'])$res->send()
Reverse Routing
$router->get('/hello/{name}', $handler)->setName('hello_route'); $url = $router->urlFor('hello_route', ['name' => 'World']); // /hello/World
Error Handling
try { $router->dispatch(); } catch (Throwable $e) { MiniUtils::errorResponse($e, true)->send(); // true = debug mode }
Complete Demo
A complete, practical example can be found in example/demo.php.
You can try the demo directly like this:
php -S localhost:8080 ./example/demo.php
Then open in your browser, e.g., http://localhost:8080
The demo shows:
- Container setup
- Middleware (global, route, group, class)
- Named routes & reverse routing
- API group with auth middleware
- Various response types (text, JSON, HTML)
- Forwarding & error handling
API Overview
MiniRouter
addRoute($method, $pattern, $handler): MiniRouteget($pattern, $handler): MiniRoutepost($pattern, $handler): MiniRouteput($pattern, $handler): MiniRoutepatch($pattern, $handler): MiniRoutedelete($pattern, $handler): MiniRouteoptions($pattern, $handler): MiniRouteany(array $methods, string $pattern, $handler): arraygroup($prefix, $callback): MiniRouteGroupaddMiddleware($middleware): selfurlFor($name, $params = []): ?stringdispatch(): voidforward($path, $method = 'GET', $req = null, $res = null)getCurrentRoute(): ?MiniRoutepublic array $routes
MiniRoute
setName($name): selfgetName(): ?stringaddMiddleware($middleware): selfpublic string $patternpublic string $methodpublic array $middlewarepublic ?MiniRouteGroup $grouppublic $handlerpublic ?string $name
MiniRouteGroup
addMiddleware($middleware): selfaddRoute(MiniRoute $route): selfgetRoutes(): arraygetMiddleware(): arraypublic string $prefixpublic array $middlewarepublic array $routes
MiniRequest
public string $methodpublic string $uripublic array $querypublic mixed $bodypublic array $headerspublic array $attributesgetHeader($name): mixedwithAttribute($key, $value): selfgetAttribute($key): mixed
MiniResponse
withStatus($status): selfwithHeader($name, $value): selfwithBody($body): selfsend(): void
MiniContainer
set($name, $factory): voidget($name): mixedhas($name): bool
License & Author
License: MIT
Author: myCodebox