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\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.API Reference
Section titled “API Reference”Route Attributes
Section titled “Route Attributes”#[Get(path: '/path', name: 'route.name')]#[Post(path: '/path')]#[Put(path: '/path')]#[Patch(path: '/path')]#[Delete(path: '/path')]#[DisableRoute]#[Middleware(MiddlewareClass::class)]Request
Section titled “Request”class Request{ public function getMethod(): string; public function getPath(): string; public function getQueryParams(): array; public function getBody(): string;}Response
Section titled “Response”class Response{ public function __construct( string $content = '', int $status = 200, array $headers = [], );
public function getContent(): string; public function getStatus(): int; public function getHeaders(): array;}MiddlewareInterface
Section titled “MiddlewareInterface”interface MiddlewareInterface{ public function handle(Request $request, callable $next): Response;}