milpa/http

PSR-15-native routing and middleware contracts for the Milpa PHP framework.

Maintainers

Package info

github.com/getmilpa/http

pkg:composer/milpa/http

Transparency log

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v0.1.0 2026-07-06 20:17 UTC

This package is auto-updated.

Last update: 2026-07-06 20:25:54 UTC


README

PSR-15-native routing contracts for the Milpa PHP framework, built on milpa/core. One immutable Route that is both the #[Route] attribute and the matched value; a router that turns a PSR-7 request into a typed RouteResult (matched / not-found / method-not-allowed) and never throws for a miss; and typed seams onto PSR-15 handlers and middleware.

CI Packagist PHP License

milpa/http carries the contracts for Milpa's web tier — routing and its integration with PSR-15 middleware, and nothing else. It sits one layer above milpa/core and speaks the PHP-FIG standards directly: PSR-7 for the request, PSR-15 for handlers and middleware. No kernel, no concrete router, no middleware runner — just the typed seams everything binds to.

Install

composer require milpa/http

What it is

Milpa splits its surface into small, dependency-light contract packages. milpa/core holds the framework-agnostic heart; milpa/http adds the web tier on top. It is deliberately minimal:

  • HttpMethod — a typed verb enum (isSafe(), isIdempotent()). No 'GET|POST' strings.
  • Route — one immutable value that is also the #[Route] attribute you put on a handler. Path, verbs, name, host, priority, defaults, and per-route middleware — the single source of truth used by both the declaration and the match.
  • RouterInterfacematch(ServerRequestInterface): RouteResult. A pure function: it never throws for a miss and never builds a response. A 404 or 405 is an expected result.
  • RouteResult — the typed, never-null outcome (MATCHED / NOT_FOUND / METHOD_NOT_ALLOWED), built through named constructors so illegal states can't exist.
  • HandlerResolverInterface & MiddlewareResolverInterface — the two seams onto PSR-15: turn a matched route's HandlerReference into a live RequestHandlerInterface, and its per-route middleware class-strings into live MiddlewareInterfaces.
  • UrlGeneratorInterface — reverse routing (name → URL) with a typed UrlReferenceType.

Be honest about scope: this package ships the contracts only. It does not match requests, dispatch middleware, or boot a server by itself — it defines the seams a concrete web runtime implements. Templating and lifecycle events are not here: routing is a PSR-15 middleware pipeline, and the view layer is a separate tier.

The shape

Declare a route with the #[Route] attribute — it is a Route, repeatable on one method:

use Milpa\Http\HttpMethod;
use Milpa\Http\Routing\Route;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;

final class ShowUser
{
    #[Route('/users/{id}', HttpMethod::GET, name: 'users.show')]
    public function __invoke(ServerRequestInterface $request): ResponseInterface
    {
        // ...
    }
}

Match a PSR-7 request and branch on the typed status — no nulls, no exceptions for a miss:

use Milpa\Http\Routing\MatchStatus;
use Milpa\Http\Routing\RouteResult;
use Milpa\Http\Routing\RouterInterface;

/** @var RouterInterface $router */
$result = $router->match($request);

$response = match ($result->status) {
    MatchStatus::MATCHED => $handlerResolver
        ->resolve($result->route->handler)
        ->handle($request->withAttribute(RouteResult::ATTRIBUTE, $result)),
    MatchStatus::NOT_FOUND         => $responseFactory->createResponse(404),
    MatchStatus::METHOD_NOT_ALLOWED => $responseFactory->createResponse(405),
};

What's inside

Namespace What it provides
Milpa\Http HttpMethod — the typed HTTP-verb vocabulary
Milpa\Http\Routing Route (+ #[Route] attribute), RouteResult, MatchStatus, HandlerReference, RouterInterface, HandlerResolverInterface, MiddlewareResolverInterface, UrlGeneratorInterface, UrlReferenceType
Milpa\Http\Exceptions RoutingExceptionInterface (marker) + RouteNotFoundException, MissingRouteParametersException (reverse-routing only)

Every public symbol carries a DocBlock. A match miss is never an exception — it is RouteResult::notFound(); the exceptions are raised only by URL generation, where an unknown route name or a missing parameter is a bug in the caller.

Requirements

Contributing

Contributions are welcome — see CONTRIBUTING.md. Please report security issues via SECURITY.md, and note that this project follows a Code of Conduct.

License

Apache-2.0 © the Milpa authors.

Milpa is designed, built, and maintained by TeamX Agency.