π«π· Read in French | π¬π§ Read in English
If this bundle is useful to you, consider becoming a sponsor to support the development and maintenance of this open source project.
A modern and complete PHP router for managing your application routes with support for dynamic routes, middlewares, and all essential features.
- Installation
- Quick Start
- Route Definition
- Dynamic Routes
- Route Groups
- URL Generation
- Request
- Response
- Middlewares
- Error Handling
- API Reference
- Complete Examples
Use Composer to install the package:
composer require julienlinard/php-routerRequirements: PHP 8.0 or higher
<?php
require_once __DIR__ . '/vendor/autoload.php';
use JulienLinard\Router\Router;
use JulienLinard\Router\Request;
use JulienLinard\Router\Response;
use JulienLinard\Router\Attributes\Route;
// Create a router instance
$router = new Router();
// Define a controller with routes
class HomeController
{
#[Route(path: '/', methods: ['GET'], name: 'home')]
public function index(): Response
{
return new Response(200, 'Welcome!');
}
}
// Register routes
$router->registerRoutes(HomeController::class);
// Handle the request
$request = new Request();
$response = $router->handle($request);
// Send the response
$response->send();Routes are defined in your controllers using the Route attribute (PHP 8).
<?php
namespace App\Controller;
use JulienLinard\Router\Attributes\Route;
use JulienLinard\Router\Request;
use JulienLinard\Router\Response;
class HomeController
{
#[Route(path: '/', methods: ['GET'], name: 'home')]
public function index(): Response
{
return new Response(200, 'Homepage');
}
}class ApiController
{
#[Route(path: '/api/users', methods: ['GET'], name: 'api.users.index')]
public function index(): Response
{
return Response::json(['users' => []]);
}
#[Route(path: '/api/users', methods: ['POST'], name: 'api.users.store')]
public function store(Request $request): Response
{
$data = $request->getBody();
// Process data...
return Response::json(['message' => 'User created'], 201);
}
}$router = new Router();
$router->registerRoutes(HomeController::class);
$router->registerRoutes(ApiController::class);Route groups allow you to organize your routes with a common prefix and shared middlewares.
use JulienLinard\Router\Middlewares\AuthMiddleware;
// Group with prefix only
$router->group('/api', [], function($router) {
$router->registerRoutes(ApiController::class);
// All routes will have the /api prefix
});
// Group with prefix and middlewares
$router->group('/admin', [AuthMiddleware::class], function($router) {
$router->registerRoutes(AdminController::class);
// All routes will have the /admin prefix AND the AuthMiddleware
});
// Nested groups
$router->group('/api', [], function($router) {
$router->group('/v1', [], function($router) {
$router->registerRoutes(ApiV1Controller::class);
// Routes with /api/v1 prefix
});
$router->group('/v2', [], function($router) {
$router->registerRoutes(ApiV2Controller::class);
// Routes with /api/v2 prefix
});
});Complete Example:
class ApiController
{
// Path defined in controller: '/users'
#[Route(path: '/users', methods: ['GET'], name: 'api.users.index')]
public function index(): Response
{
return Response::json(['users' => []]);
}
}
// Registration with group
$router->group('/api', [], function($router) {
$router->registerRoutes(ApiController::class);
});
// The route will be accessible at: /api/usersThe router supports dynamic routes with parameters automatically extracted from the URL.
class UserController
{
#[Route(path: '/user/{id}', methods: ['GET'], name: 'user.show')]
public function show(Request $request): Response
{
$userId = $request->getRouteParam('id');
return Response::json([
'user_id' => $userId,
'message' => "Displaying user {$userId}"
]);
}
}Example URL: /user/123 β $userId = '123'
class PostController
{
#[Route(path: '/user/{userId}/post/{slug}', methods: ['GET'], name: 'post.show')]
public function show(Request $request): Response
{
$userId = $request->getRouteParam('userId');
$slug = $request->getRouteParam('slug');
return Response::json([
'user_id' => $userId,
'slug' => $slug
]);
}
}Example URL: /user/123/post/my-article β $userId = '123', $slug = 'my-article'
// Get a specific parameter
$id = $request->getRouteParam('id');
$id = $request->getRouteParam('id', 'default'); // with default value
// Get all parameters
$params = $request->getRouteParams(); // ['id' => '123', 'slug' => 'my-article']The Request class provides complete access to HTTP request data.
$request = new Request();
$path = $request->getPath(); // '/user/123'
$method = $request->getMethod(); // 'GET', 'POST', etc.// URL: /search?q=php&page=2
$query = $request->getQueryParam('q'); // 'php'
$page = $request->getQueryParam('page', 1); // '2' or 1 as default
$allParams = $request->getQueryParams(); // ['q' => 'php', 'page' => '2']$contentType = $request->getHeader('content-type');
$allHeaders = $request->getHeaders();
$customHeader = $request->getHeader('x-custom-header', 'default');$token = $request->getCookie('auth_token');
$allCookies = $request->getCookies();// For JSON
$data = $request->getBody(); // ['name' => 'John', 'email' => '...']
$name = $request->getBodyParam('name'); // 'John'
$rawBody = $request->getRawBody(); // Raw string
// For form-urlencoded
$data = $request->getBody(); // ['field1' => 'value1', ...]if ($request->isAjax()) {
// AJAX request
}
if ($request->wantsJson()) {
// Client accepts JSON
}// Create a custom request for tests
$request = new Request('/user/123', 'GET');The Response class allows you to create and send HTTP responses.
$response = new Response(200, 'Response content');
$response->send();$data = ['message' => 'Success', 'data' => []];
$response = Response::json($data, 200);
$response->send();$response = new Response(200, 'Content');
$response->setHeader('X-Custom-Header', 'value');
$response->setHeader('Content-Type', 'application/xml');
$response->send();$statusCode = $response->getStatusCode(); // 200
$content = $response->getContent(); // 'Content'
$headers = $response->getHeaders(); // ['content-type' => 'application/json']The Router now supports dependency injection via a Container. This allows automatic injection of dependencies into controllers and middlewares.
use JulienLinard\Router\Router;
use JulienLinard\Core\Container\Container;
$router = new Router();
$container = new Container();
// Pass the Container to the Router
$router->setContainer($container);
// The Router will automatically use the Container to instantiate controllersuse JulienLinard\Router\Attributes\Route;
use JulienLinard\Router\Response;
use JulienLinard\Doctrine\EntityManager;
use JulienLinard\Auth\AuthManager;
class UserController
{
private EntityManager $em;
private AuthManager $auth;
// Dependencies are automatically injected via the Container
public function __construct(EntityManager $em, AuthManager $auth)
{
$this->em = $em;
$this->auth = $auth;
}
#[Route(path: '/users', methods: ['GET'], name: 'users.index')]
public function index(): Response
{
$users = $this->em->getRepository(User::class)->findAll();
return Response::json($users);
}
}Note: If no Container is set, the Router instantiates controllers directly with new.
Middlewares allow you to execute code before request processing.
Important : The Middleware interface has been improved. The handle() method now returns ?Response instead of void. If a middleware returns a Response, execution stops and that response is returned. If it returns null, execution continues with the next middleware.
use JulienLinard\Router\Middlewares\CorsMiddleware;
use JulienLinard\Router\Middlewares\LoggingMiddleware;
$router = new Router();
// Add a global middleware
$router->addMiddleware(new CorsMiddleware());
$router->addMiddleware(new LoggingMiddleware());use JulienLinard\Router\Middlewares\AuthMiddleware;
use JulienLinard\Router\Middlewares\RoleMiddleware;
class AdminController
{
#[Route(
path: '/admin/dashboard',
methods: ['GET'],
name: 'admin.dashboard',
middleware: [AuthMiddleware::class, RoleMiddleware::class]
)]
public function dashboard(): Response
{
return new Response(200, 'Admin dashboard');
}
}use JulienLinard\Router\Middlewares\CorsMiddleware;
// Default configuration (all origins)
$cors = new CorsMiddleware();
// Custom configuration
$cors = new CorsMiddleware(
allowedOrigins: ['https://example.com', 'https://app.example.com'],
allowedMethods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
allowCredentials: true
);
$router->addMiddleware($cors);use JulienLinard\Router\Middlewares\AuthMiddleware;
class ProtectedController
{
#[Route(
path: '/profile',
methods: ['GET'],
middleware: [AuthMiddleware::class]
)]
public function profile(): Response
{
// User is authenticated
return Response::json(['user' => $_SESSION['user']]);
}
}use JulienLinard\Router\Middlewares\RoleMiddleware;
class AdminController
{
#[Route(
path: '/admin/users',
methods: ['GET'],
middleware: [AuthMiddleware::class, RoleMiddleware::class]
)]
public function users(): Response
{
// User is authenticated AND has admin role
return Response::json(['users' => []]);
}
}
// In your bootstrap
$router->addMiddleware(new RoleMiddleware('admin'));use JulienLinard\Router\Middlewares\LoggingMiddleware;
$router->addMiddleware(new LoggingMiddleware());
// Log all requests to error_log<?php
namespace App\Middlewares;
use JulienLinard\Router\Middleware;
use JulienLinard\Router\Request;
use JulienLinard\Router\Response;
class CustomMiddleware implements Middleware
{
public function handle(Request $request): void
{
// Your logic here
// For example, check a condition
if (/* condition not met */) {
Response::json(['error' => 'Access denied'], 403)->send();
exit;
}
// Otherwise, continue execution
}
}The router automatically handles common errors:
- 404 Not Found: Route not found
- 405 Method Not Allowed: HTTP method not supported for this route
- 500 Internal Server Error: Server error (exceptions)
use JulienLinard\Router\ErrorHandler;
// Errors are automatically handled by the router
// You can use ErrorHandler directly if needed
$response = ErrorHandler::handleNotFound(); // 404
$response = ErrorHandler::handleServerError($e); // 500Registers all routes of a controller.
$router->registerRoutes(HomeController::class);Adds a global middleware.
$router->addMiddleware(new CorsMiddleware());Processes a request and returns the response.
$response = $router->handle($request);Returns all registered routes (debug).
$routes = $router->getRoutes();
// ['static' => [...], 'dynamic' => [...]]Returns a route by its name.
$route = $router->getRouteByName('home');
// ['path' => '/', 'method' => 'GET', 'route' => [...]]Generates a URL from a route name and its parameters.
// Static route
$url = $router->url('home');
// Returns: '/'
// Dynamic route with one parameter
$url = $router->url('user.show', ['id' => '123']);
// Returns: '/user/123'
// Dynamic route with multiple parameters
$url = $router->url('post.show', ['userId' => '123', 'slug' => 'my-article']);
// Returns: '/user/123/post/my-article'
// With query parameters
$url = $router->url('user.show', ['id' => '123'], ['page' => '2', 'sort' => 'name']);
// Returns: '/user/123?page=2&sort=name'
// Returns null if route doesn't exist
$url = $router->url('non-existent');
// Returns: null
// Throws exception if required parameter is missing
try {
$url = $router->url('user.show', []); // Missing 'id' parameter
} catch (\InvalidArgumentException $e) {
// "The parameter 'id' is required for route 'user.show'."
}Creates a route group with a prefix and common middlewares.
$router->group('/api', [AuthMiddleware::class], function($router) {
$router->registerRoutes(ApiController::class);
});getPath(): string- Request pathgetMethod(): string- HTTP methodgetQueryParams(): array- All query parametersgetQueryParam(string $key, $default = null)- A query parametergetHeaders(): array- All headersgetHeader(string $name, ?string $default = null): ?string- A headergetCookies(): array- All cookiesgetCookie(string $name, $default = null)- A cookiegetBody(): ?array- Parsed bodygetBodyParam(string $key, $default = null)- A body parametergetRawBody(): string- Raw bodygetRouteParams(): array- All route parametersgetRouteParam(string $key, $default = null)- A route parameterisAjax(): bool- Checks if it's an AJAX requestwantsJson(): bool- Checks if client accepts JSON
new Response(int $statusCode = 200, string $content = '')Response::json($data, int $statusCode = 200): self- Creates a JSON response
setHeader(string $name, string $value): void- Sets a headersend(): void- Sends the HTTP responsegetStatusCode(): int- Status codegetContent(): string- ContentgetHeaders(): array- All headers
core-php automatically includes php-router. The router is accessible via Application::getRouter().
<?php
use JulienLinard\Core\Application;
use JulienLinard\Router\Attributes\Route;
use JulienLinard\Router\Response;
$app = Application::create(__DIR__);
$router = $app->getRouter();
class HomeController
{
#[Route(path: '/', methods: ['GET'], name: 'home')]
public function index(): Response
{
return new Response(200, '<h1>Home</h1>');
}
}
$router->registerRoutes(HomeController::class);
$app->start();Use authentication middlewares with php-router.
<?php
use JulienLinard\Router\Router;
use JulienLinard\Router\Attributes\Route;
use JulienLinard\Router\Response;
use JulienLinard\Auth\AuthManager;
use JulienLinard\Auth\Middleware\AuthMiddleware;
use JulienLinard\Auth\Middleware\RoleMiddleware;
$router = new Router();
$auth = new AuthManager($authConfig);
class DashboardController
{
#[Route(
path: '/dashboard',
methods: ['GET'],
name: 'dashboard',
middleware: [new AuthMiddleware($auth)]
)]
public function index(): Response
{
return new Response(200, '<h1>Dashboard</h1>');
}
}
class AdminController
{
#[Route(
path: '/admin',
methods: ['GET'],
name: 'admin',
middleware: [
new AuthMiddleware($auth),
new RoleMiddleware('admin', $auth)
]
)]
public function index(): Response
{
return new Response(200, '<h1>Admin</h1>');
}
}
$router->registerRoutes(DashboardController::class);
$router->registerRoutes(AdminController::class);php-router can be used independently of all other packages.
<?php
require_once __DIR__ . '/vendor/autoload.php';
use JulienLinard\Router\Router;
use JulienLinard\Router\Attributes\Route;
use JulienLinard\Router\Request;
use JulienLinard\Router\Response;
$router = new Router();
class HomeController
{
#[Route(path: '/', methods: ['GET'], name: 'home')]
public function index(): Response
{
return new Response(200, 'Hello World');
}
}
$router->registerRoutes(HomeController::class);
$request = new Request();
$response = $router->handle($request);
$response->send();The router allows you to generate URLs from route names, which facilitates maintenance and avoids hardcoded URLs.
// In your views or controllers
$homeUrl = $router->url('home');
// Returns: '/'
$userUrl = $router->url('user.show', ['id' => '123']);
// Returns: '/user/123'$url = $router->url('user.show', ['id' => '123'], ['page' => '2', 'sort' => 'name']);
// Returns: '/user/123?page=2&sort=name'class UserController
{
#[Route(path: '/user/{id}', methods: ['GET'], name: 'user.show')]
public function show(Request $request, Router $router): Response
{
$id = $request->getRouteParam('id');
// Generate next user URL
$nextUserId = (int)$id + 1;
$nextUrl = $router->url('user.show', ['id' => $nextUserId]);
return Response::json([
'user_id' => $id,
'next_user_url' => $nextUrl
]);
}
}Note: To use $router in your controllers, you can inject it via a dependency container or pass it as a parameter.
<?php
require_once __DIR__ . '/vendor/autoload.php';
use JulienLinard\Router\Router;
use JulienLinard\Router\Request;
use JulienLinard\Router\Response;
use JulienLinard\Router\Attributes\Route;
use JulienLinard\Router\Middlewares\CorsMiddleware;
class UserController
{
// Paths are defined without the /api prefix (added by group)
#[Route(path: '/users', methods: ['GET'], name: 'users.index')]
public function index(): Response
{
return Response::json(['users' => []]);
}
#[Route(path: '/users/{id}', methods: ['GET'], name: 'users.show')]
public function show(Request $request): Response
{
$id = $request->getRouteParam('id');
return Response::json(['user' => ['id' => $id]]);
}
#[Route(path: '/users', methods: ['POST'], name: 'users.store')]
public function store(Request $request): Response
{
$data = $request->getBody();
// Create user...
return Response::json(['message' => 'User created'], 201);
}
#[Route(path: '/users/{id}', methods: ['PUT'], name: 'users.update')]
public function update(Request $request): Response
{
$id = $request->getRouteParam('id');
$data = $request->getBody();
// Update user...
return Response::json(['message' => 'User updated']);
}
#[Route(path: '/users/{id}', methods: ['DELETE'], name: 'users.delete')]
public function delete(Request $request): Response
{
$id = $request->getRouteParam('id');
// Delete user...
return Response::json(['message' => 'User deleted'], 204);
}
}
// Configuration with groups
$router = new Router();
$router->addMiddleware(new CorsMiddleware());
// API group with /api prefix
$router->group('/api', [], function($router) {
$router->registerRoutes(UserController::class);
});
// Processing
$request = new Request();
$response = $router->handle($request);
$response->send();
// URL generation
$usersUrl = $router->url('users.index'); // '/api/users'
$userUrl = $router->url('users.show', ['id' => 5]); // '/api/users/5'<?php
require_once __DIR__ . '/vendor/autoload.php';
use JulienLinard\Router\Router;
use JulienLinard\Router\Request;
use JulienLinard\Router\Response;
use JulienLinard\Router\Attributes\Route;
use JulienLinard\Router\Middlewares\AuthMiddleware;
use JulienLinard\Router\Middlewares\RoleMiddleware;
class HomeController
{
#[Route(path: '/', methods: ['GET'], name: 'home')]
public function index(): Response
{
return new Response(200, '<h1>Welcome</h1>');
}
}
class AuthController
{
#[Route(path: '/login', methods: ['GET', 'POST'], name: 'login')]
public function login(Request $request): Response
{
if ($request->getMethod() === 'POST') {
// Process login
$_SESSION['user'] = ['id' => 1, 'role' => 'user'];
return new Response(302, '', ['Location' => '/dashboard']);
}
return new Response(200, '<form>...</form>');
}
}
class DashboardController
{
#[Route(
path: '/dashboard',
methods: ['GET'],
name: 'dashboard',
middleware: [AuthMiddleware::class]
)]
public function index(): Response
{
return new Response(200, '<h1>Dashboard</h1>');
}
}
class AdminController
{
#[Route(
path: '/admin',
methods: ['GET'],
name: 'admin',
middleware: [AuthMiddleware::class, RoleMiddleware::class]
)]
public function index(): Response
{
return new Response(200, '<h1>Admin</h1>');
}
}
// Configuration with groups
$router = new Router();
// Public routes
$router->registerRoutes(HomeController::class);
$router->registerRoutes(AuthController::class);
// Dashboard group with authentication
$router->group('/dashboard', [AuthMiddleware::class], function($router) {
$router->registerRoutes(DashboardController::class);
});
// Admin group with authentication and role
$router->group('/admin', [AuthMiddleware::class, new RoleMiddleware('admin')], function($router) {
$router->registerRoutes(AdminController::class);
});
// Processing
session_start();
$request = new Request();
$response = $router->handle($request);
$response->send();The package includes a complete test suite. To run the tests:
composer test
# or
vendor/bin/phpunit tests/MIT License - See the LICENSE file for more details.
Contributions are welcome! Feel free to open an issue or a pull request.
For any questions or issues, please open an issue on GitHub.
If this bundle is useful to you, consider becoming a sponsor to support the development and maintenance of this open source project.
Developed with β€οΈ by Julien Linard