Docs

Router

A tiny, attribute-driven HTTP router that turns any PHP class into a fully documented REST controller. It powers request dispatching, the route:list CLI, and your OpenAPI generator.

1. Installation

composer require monkeyscloud/monkeyslegion-router

The package only brings psr/http-message and the MonkeysLegion HTTP kernel.

2. Defining Routes with #[Route]

Annotate public methods in any controller class:

namespace App\Http\Controller;

use MonkeysLegion\Router\Attributes\Route;
use Psr\Http\Message\ResponseInterface;

final class PostController
{
    #[Route('GET', '/posts', summary: 'List posts', tags: ['Post'])]
    public function index(): ResponseInterface { /* … */ }

    #[Route(['GET','POST'], '/posts', name: 'post_create')]
    public function create(): ResponseInterface { /* … */ }

    #[Route('DELETE', '/posts/{id:[0-9]+}', summary: 'Delete one')]
    public function delete(int $id): ResponseInterface { /* … */ }
}

Parameter

Type

Purpose

methods

string|string[]

HTTP verb(s) to match

path

string

URI template ({id} automatically typed as string, or supply a regex)

name

string

Optional route name / OpenAPI operationId

summary

string

One-liner for Swagger UI

tags

string[]

Grouping labels in docs

3. Registering Controllers

use DI\Container;
use MonkeysLegion\Router\Router;
use MonkeysLegion\Router\RouteCollection;

// services.php
return [
    RouteCollection::class => fn() => new RouteCollection(),
    Router::class          => fn($c) => new Router(
        $c->get(RouteCollection::class),
        $c                     // any PSR-11 container
    ),
];

Then, at boot:

$router = $container->get(Router::class);
$router->registerController(App\Http\Controller\PostController::class);
// or entire folders via Core’s RouteLoader

4. Imperative & Dynamic Routes

Need a route outside a controller class? Call add():

$router->add(
    methods: ['GET'],
    path:    '/healthz',
    handler: [HealthController::class, 'probe'],
    name:    'health_check',
    tags:    ['Ops'],
);

Both attribute- and imperative routes live in the same RouteCollection.

5. Dispatching with PSR-15

use Laminas\Diactoros\ServerRequestFactory;
use Middlewares\Utils\Dispatcher;

$request = ServerRequestFactory::fromGlobals();

$response = Dispatcher::run(
    [
        // global middleware (auth, body-parser, etc.)
        $router->getMiddleware(),   // performs route matching & invokes handler
    ],
    $request
);

(new Laminas\HttpHandlerRunner\Emitter\SapiEmitter())->emit($response);

6. CLI Goodies

php vendor/bin/ml route:list
# ───────────────────────────────────────────────────────────────
#  METHOD   PATH                      NAME
# ───────────────────────────────────────────────────────────────
#  GET      /posts                    post_index
#  POST     /posts                    post_create
#  DELETE   /posts/{id}               post_delete
# ───────────────────────────────────────────────────────────────

php vendor/bin/ml openapi:export  > openapi.yaml   # full spec

7. Under the Hood

Class

Role

MonkeysLegion\Router\Attributes\Route

PHP 8 attribute storing route metadata

MonkeysLegion\Router\Route

Immutable value object (methods, path, handler, docs)

MonkeysLegion\Router\RouteCollection

Iterable registry of Route objects

MonkeysLegion\Router\Router

Scans controllers, validates paths, adds routes programmatically

8. Extending the Router

  • Path parameter metadata—add descriptions, examples, or custom validators.

  • API versioning—prepend a version prefix in Router or at the attribute level.

  • Per-route middleware—store middleware stacks on the Route object and apply during dispatch.

Pull requests are welcome—check the GitHub issues for “good first issues”.

License

MIT © 2025 MonkeysCloud