marko/routing
Routes live on the methods they handle. Conflicts are caught at boot time with clear error messages. Override vendor routes cleanly via Preferences, or disable them explicitly with #[DisableRoute].
Installation
Section titled “Installation”composer require marko/routingDefining Routes
Section titled “Defining Routes”Add route attributes to controller methods:
use Marko\Routing\Attributes\Get;use Marko\Routing\Attributes\Post;use Marko\Routing\Http\Response;
class ProductController{ #[Get('/products')] public function index(): Response { return new Response('Product list'); }
#[Get('/products/{id}')] public function show( int $id, ): Response { return new Response("Product $id"); }
#[Post('/products')] public function store(): Response { return new Response('Created', 201); }}Route parameters are automatically passed to method arguments.
Available Methods
Section titled “Available Methods”#[Get('/path')]#[Post('/path')]#[Put('/path')]#[Patch('/path')]#[Delete('/path')]Adding Middleware
Section titled “Adding Middleware”use Marko\Routing\Attributes\Middleware;
class AdminController{ #[Get('/admin/dashboard')] #[Middleware(AuthMiddleware::class)] public function dashboard(): Response { return new Response('Admin dashboard'); }}Middleware classes implement MiddlewareInterface:
use Marko\Routing\Http\Request;use Marko\Routing\Http\Response;use Marko\Routing\Middleware\MiddlewareInterface;
class AuthMiddleware implements MiddlewareInterface{ public function handle( Request $request, callable $next, ): Response { if (!$this->isAuthenticated($request)) { return new Response('Unauthorized', 401); }
return $next($request); }}Overriding Vendor Routes
Section titled “Overriding Vendor Routes”Use Preferences to replace a vendor’s controller:
use Marko\Core\Attributes\Preference;use Marko\Routing\Attributes\Get;use Vendor\Blog\PostController;
#[Preference(replaces: PostController::class)]class MyPostController extends PostController{ #[Get('/blog')] // Your route takes over public function index(): Response { return new Response('My custom blog'); }}Disabling Routes
Section titled “Disabling Routes”Explicitly remove an inherited route:
use Marko\Routing\Attributes\DisableRoute;
#[Preference(replaces: PostController::class)]class MyPostController extends PostController{ #[DisableRoute] // Removes /blog/{slug} route public function show( string $slug, ): Response { // Method still exists but has no route }}Route Conflicts
Section titled “Route Conflicts”If two modules define the same route, Marko throws RouteConflictException at boot:
Route conflict detected for GET /products
Defined in: - Vendor\Catalog\ProductController::index() - App\Store\ProductController::list()
Resolution: Use #[Preference] to extend one controller,or use #[DisableRoute] to remove one route.Requires marko/cli for the marko binary.
Listing Routes
Section titled “Listing Routes”See all registered routes:
marko route:listMETHOD PATH ACTION MIDDLEWAREGET / HelloController::indexGET /blog PostController::indexGET /blog/{id} PostController::showGET /products ProductController::indexGET /products/{id} ProductController::showFilter by HTTP method or path:
marko route:list --method=POSTmarko route:list --path=productsmarko route:list --method=GET --path=blogAPI Reference
Section titled “API Reference”Route Attributes
Section titled “Route Attributes”#[Get(path: '/path', middleware: [])]#[Post(path: '/path')]#[Put(path: '/path')]#[Patch(path: '/path')]#[Delete(path: '/path')]#[DisableRoute]#[Middleware(MiddlewareClass::class)]Request
Section titled “Request”class Request{ public function method(): string; public function path(): string; public function query(?string $key = null, mixed $default = null): mixed; public function post(?string $key = null, mixed $default = null): mixed; public function body(): string; public function header(string $name, ?string $default = null): ?string; public function headers(): array; public static function fromGlobals(): self;}Response
Section titled “Response”class Response{ public function __construct( string $body = '', int $statusCode = 200, array $headers = [], );
public function body(): string; public function statusCode(): int; public function headers(): array; public function send(): void; public static function json(mixed $data, int $statusCode = 200): self; public static function html(string $html, int $statusCode = 200): self; public static function redirect(string $url, int $statusCode = 302): self;}MiddlewareInterface
Section titled “MiddlewareInterface”interface MiddlewareInterface{ public function handle(Request $request, callable $next): Response;}