sulcompany / router
A lightweight, dependency-free PHP router with dynamic routes, middleware, route groups, contexts, and built-in error handling.
Requires
- php: ^8.1
- psr/container: ^2.0
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
A lightweight, dependency-free PHP router with dynamic routes, middleware, route groups, contexts, and built-in error handling.
πΊπΈ English | π΅πΉ PortuguΓͺs
π Overview
SulCompany Router is a mini routing framework for PHP 8.1+. It is not PSR-7, not FastRoute. It is a simple, direct, opinionated alternative for developers who want:
- HTTP routing
- Middleware pipeline
- Route groups with prefix, name, and context
- Configurable error handling
- An integrated PSR-11 container (optional)
- Zero dependencies besides
psr/container
It was built to be predictable, readable, and extensible β without the weight of a full framework.
β¨ Features
- β
REST routes (
GET,POST,PUT,PATCH,DELETE) - β
Dynamic parameters (
/user/{id}) - β
Wildcard parameters (
/files/{path*}) - β Named routes and URL generation
- β Nested groups (prefix + name + context + middleware)
- β
Contexts (
admin,ecommerce, ...) for differentiated error views - β
before/aftermiddleware pipeline - β PSR-11 container with autowiring
- β
Route cache (via
var_export) - β
Error handling via
ErrorHandlerInterface - β Automatic JSON / AJAX detection
- β No heavy dependencies
π Requirements
| Requirement | Version |
|---|---|
| PHP | ^8.1 |
| Composer | ^2.0 |
| psr/container | ^2.0 (installed automatically) |
π Installation
composer require sulcompany/router
β‘ Quick Start
1. Create public/index.php
<?php declare(strict_types=1); ob_start(); use SulCompany\Router\Dispatcher; use SulCompany\Router\Router; require __DIR__ . '/../vendor/autoload.php'; $router = new Router('https://mysite.com'); $router->get('/', function ($request, $response) { return $response->html('<h1>Hello, world!</h1>'); }, name: 'home'); $router->get('/user/{id}', [UserController::class, 'show'], name: 'user.show'); $dispatcher = new Dispatcher($router); $dispatcher->dispatch(); while (ob_get_level() > 0) { ob_end_flush(); }
2. Test it
GET /β returns HTMLGET /user/42β executesUserController::show('42')
Done. π
π§ The Basics
Routes
$router->get('/about', [SiteController::class, 'about'], name: 'site.about'); $router->get('/user/{id}', [UserController::class, 'show'], name: 'user.show'); $router->get('/files/{path*}', [FileController::class, 'download'], name: 'files.download');
π Full route documentation β
Groups
$router->group('/admin', name: 'admin', context: 'admin', middleware: [AuthAdmin::class], callback: function ($r) { $r->get('/users', [UserController::class, 'index'], name: 'users.index'); });
π Full group documentation β
Middleware
class AuthMiddleware { public function before(Request $request, Response $response): bool { if (!auth()->check()) { $response->redirect('/login'); return false; } return true; } }
π Full middleware documentation β
Controllers
$router->get('/user/{id}', [UserController::class, 'show'], name: 'user.show');
π Full controller documentation β
Request
$request->method(); $request->path(); $request->header('accept'); $request->get('page'); $request->post('name'); $request->json('field'); $request->expectsJson();
π Full request documentation β
Response
return $response->html('<h1>Hello</h1>'); return $response->json(['ok' => true]); return $response->redirect('/login');
π Full response documentation β
Error Handling
final class AppErrorHandler implements ErrorHandlerInterface { public function handle(int $status, Request $request, Response $response): Response { if ($request->expectsJson()) { return $response->json(['error' => true], $status); } return $response->html("<h1>Error {$status}</h1>", $status); } }
π Full error handling documentation β
Container (DI)
$container = new Container(); $container->singleton(PDO::class, fn() => new PDO('mysql:...')); $container->bind(UserRepository::class, fn($c) => new UserRepository($c->get(PDO::class)));
π Full container documentation β
Route Cache
$router->enableCache(__DIR__ . '/cache/routes.php'); if (!$router->loadCache()) { $router->get('/', ...); $router->compile(); $router->cacheCompiledRoutes(); }
π Full cache documentation β
URL Generation
$router->urlFor('user.show', ['id' => 42]); // β https://mysite.com/user/42
π Full URL generation documentation β
π Documentation
| Document | Description |
|---|---|
| Routes | HTTP methods, dynamic params, wildcards |
| Groups | Prefix, name, context, middleware, nesting |
| Middleware | Before/after, types, order |
| Controllers | Closures, classes, DI |
| Request | All request methods |
| Response | All response builders |
| Error Handling | Contract, default, custom |
| Container | PSR-11, bind, singleton, autowiring |
| Cache | Route compilation and caching |
| URL Generation | Named routes and urlFor() |
π π΅πΉ DocumentaΓ§Γ£o em PortuguΓͺs β
β οΈ Important Notes
Output Buffering
ob_start(); // at the top // ... your code ... while (ob_get_level() > 0) { ob_end_flush(); // at the end }
Without this, your pages may render blank with content-length: 0.
Exceptions
The Router does not catch exceptions. Use try/catch in your index.php.
Not PSR-7
Request and Response are mutable and minimalist.
PSR-11 Container
The Container class implements Psr\Container\ContainerInterface.
π§© Compatibility
| PHP | Supported |
|---|---|
| 8.1 | β |
| 8.2 | β |
| 8.3 | β |
| 8.4 | β (not yet tested) |
| 7.x | β |
π€ Contributing
- Fork the repository
- Create a feature branch
- Commit your changes
- Push to the branch
- Open a Pull Request
π Reporting Issues
Open an issue with PHP version, Router version, and a minimal reproduction.
π₯ Credits
- Francisco Dulo β author
- Sul Company Lda β maintainer
- Contributors β everyone who helped
π License
MIT β see LICENSE.
SulCompany Router β made with π€ in Angola.