zero-to-prod / http-router
High-performance HTTP router for PHP with three-level indexing, PSR-15 middleware support, and route caching
Fund package maintenance!
Github
Installs: 401
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/zero-to-prod/http-router
Requires
- php: >=7.2
- nyholm/psr7: ^1.0
- nyholm/psr7-server: ^1.0
- psr/http-message: ^2.0
- psr/http-server-handler: ^1.0
- psr/http-server-middleware: ^1.0
Requires (Dev)
- phpunit/phpunit: <=11.0.0
README
High-performance HTTP router for PHP 7.2+ with three-level indexing, PSR-15 middleware support, and route caching.
Features
- High Performance: Three-level route indexing for O(1) static route lookup and optimized dynamic route matching
- PSR-15 Middleware: Full support for PSR-15 middleware alongside legacy variadic middleware
- Route Caching: Production-optimized serialization with automatic cache management
- RESTful Resources: Automatic generation of resourceful routes
- Route Groups: Organize routes with shared prefixes and middleware
- Named Routes: Generate URLs from route names with parameter substitution
- Parameter Constraints: Inline (
{id:\d+}) and fluent (where()) constraint syntax - PHP 7.1+ Compatible: Broad multi-version support
Installation
composer require zero-to-prod/http-router
Quick Start
use Zerotoprod\HttpRouter\HttpRouter; $router = HttpRouter::for('GET', '/users/123') ->get('/users/{id}', [UserController::class, 'show']) ->where('id', '\d+') ->name('user.show') ->middleware(AuthMiddleware::class); $router->dispatch();
Basic Usage
Defining Routes
use Zerotoprod\HttpRouter\HttpRouter; $router = HttpRouter::create(); // Static routes $router->get('/', function() { echo 'Home'; }); // Dynamic routes with parameters $router->get('/users/{id}', function(array $params) { echo "User ID: " . $params['id']; }); // Routes with inline constraints $router->get('/posts/{id:\d+}', [PostController::class, 'show']); // Routes with fluent constraints $router->get('/users/{id}', [UserController::class, 'show']) ->where('id', '\d+');
HTTP Methods
$router->get('/users', [UserController::class, 'index']); $router->post('/users', [UserController::class, 'store']); $router->put('/users/{id}', [UserController::class, 'update']); $router->patch('/users/{id}', [UserController::class, 'patch']); $router->delete('/users/{id}', [UserController::class, 'destroy']); $router->options('/users', [UserController::class, 'options']); $router->head('/users', [UserController::class, 'head']); // Multiple methods $router->any('/users/{id}', [UserController::class, 'handler'], ['GET', 'POST']);
RESTful Resources
// Generates all 7 RESTful routes $router->resource('users', UserController::class); // With filters $router->resource('posts', PostController::class, ['only' => ['index', 'show']]); $router->resource('comments', CommentController::class, ['except' => ['destroy']]);
Generated routes:
GET /users→index()GET /users/create→create()POST /users→store()GET /users/{id}→show()GET /users/{id}/edit→edit()PUT /users/{id}→update()DELETE /users/{id}→destroy()
Route Groups
// Prefix groups $router->prefix('api/v1')->group(function($router) { $router->get('/users', [UserController::class, 'index']); // /api/v1/users }); // Middleware groups $router->middleware([AuthMiddleware::class])->group(function($router) { $router->get('/dashboard', [DashboardController::class, 'index']); }); // Combined $router->prefix('admin') ->middleware([AuthMiddleware::class, AdminMiddleware::class]) ->group(function($router) { $router->get('/users', [AdminController::class, 'users']); });
Named Routes
// Define named route $router->get('/users/{id}', [UserController::class, 'show']) ->name('user.show'); // Generate URL $url = $router->route('user.show', ['id' => 123]); // /users/123
Middleware
PSR-15 Middleware
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; class AuthMiddleware implements MiddlewareInterface { public function process( ServerRequestInterface $request, RequestHandlerInterface $handler ): ResponseInterface { // Check authentication return $handler->handle($request); } } $router->get('/dashboard', [DashboardController::class, 'index']) ->middleware(AuthMiddleware::class);
Variadic Middleware
class LogMiddleware { public function __invoke(callable $next, ...$context) { // Before request error_log('Request started'); $next(); // After request error_log('Request completed'); } } $router->get('/users', [UserController::class, 'index']) ->middleware(LogMiddleware::class);
Global Middleware
$router->globalMiddleware([ CorsMiddleware::class, SecurityHeadersMiddleware::class ]);
Route Caching
// Enable automatic caching in production $router->autoCache( cache_path: __DIR__ . '/cache/routes.php', env_var: 'APP_ENV', cache_envs: ['production'] ); // Manual caching if ($router->isCacheable()) { $compiled = $router->compile(); file_put_contents('routes.php', $compiled); } // Load from cache $data = file_get_contents('routes.php'); $router->loadCompiled($data);
Fallback Handler
$router->fallback(function() { http_response_code(404); echo 'Not Found'; });
Advanced Usage
Dispatching with Context
// Pass additional arguments to actions and middleware $router = Router::for('GET', '/users', $container, $request); $router->get('/users', function(array $params, $container, $request) { // Access container and request });
Optional Parameters
$router->get('/users/{id?}', function(array $params) { $id = $params['id'] ?? 'all'; echo "User: $id"; });
Multiple Constraints
$router->get('/posts/{year}/{month}/{slug}', [PostController::class, 'show']) ->where([ 'year' => '\d{4}', 'month' => '\d{2}', 'slug' => '[a-z0-9-]+' ]);
Action Types
The router supports three types of actions:
Controller Array
$router->get('/users', [UserController::class, 'index']);
Invokable Class
$router->get('/users', UserAction::class);
Closure
$router->get('/users', function(array $params) { echo 'Users'; });
Note: Closures cannot be cached due to PHP serialization limitations.
Performance
The router uses a three-level indexing strategy for optimal performance:
- Static Index (O(1)): Hash map
method:path→ Route for exact matches - Prefix Index (O(1) + O(n)): Hash map
method:prefix→ [Routes] for common prefixes - Method Index (O(n)): Hash map
method→ [Routes] as fallback
This approach minimizes regex matching for most common routing patterns.
Testing
composer test
License
MIT License. See LICENSE for details.
Contributing
Contributions are welcome! Please submit pull requests to the GitHub repository.
Support
- Issues: GitHub Issues
- Email: dave0016@gmail.com
- Documentation: https://zero-to-prod.github.io/http-router/
Credits
Created and maintained by David Smith.