Find framework reference pages for handlers, routes, middleware, modules, database, views, and security.

File-Based Routing

CorianderPHP 0.4.1 discovers routes from method files in src/Views. The folder path describes the URL; the filename suffix describes the HTTP method. You do not include route files in public/routes.php or register them with $router->get().

Create Your First Route

From the project root, you can run:

php coriander make:route hello
php coriander routes:list

The first command creates src/Views/hello.get.php. Replace its contents with:

<?php
declare(strict_types=1);

use CorianderCore\Core\Http\Responses;
use Psr\Http\Message\ServerRequestInterface;

return static function (ServerRequestInterface $request) {
    return Responses::html('<h1>Hello</h1>');
};

Open /hello. This example returns a callable; that callable receives the request and returns a PSR response. It must not print output. Use Responses::view() for prepared templates or Responses::json() for JSON.

For a simple HTML page, you can instead put PHP/HTML directly in a .get.php file with no explicit return. The framework adds layouts, metadata, and compiled page scripts. php coriander make:view hello creates src/Views/hello/index.get.php; use it instead of hello.get.php, not alongside it, because both represent the same URL. See Static View Guide.

Non-GET files must return callable handlers. Never mix printed HTML and a returned handler in one file.

Creating these files by hand is also supported. Generators are conveniences, not a registration step.

Paths And Methods

FileRequest
index.get.phpGET /
about.get.phpGET /about
admin/index.get.phpGET /admin
articles/[id].get.phpGET /articles/42
articles/[id].patch.phpPATCH /articles/42
teams/[team]/users/[id].get.phpGET /teams/5/users/42

Supported lowercase suffixes are get, post, put, patch, delete, head, and options. index means the directory's own URL. For a POST handler:

php coriander make:route "articles/[id].post"

A dynamic segment such as [id] matches a segment, not a validated database id. Read and validate it before using it:

<?php
use CorianderCore\Core\Http\Responses;
use Psr\Http\Message\ServerRequestInterface;

return static function (ServerRequestInterface $request) {
    $id = (string) $request->getAttribute('id');
    if (!ctype_digit($id) || (int) $id < 1) {
        return Responses::json(['error' => 'Invalid article id.'], 404);
    }

    return Responses::json(['id' => (int) $id]);
};

Regex, optional parameters, and catch-all segments from earlier router APIs are not supported. Put constraints in middleware or the handler.

Read The Request

  • getAttribute('id'): matched path parameter.
  • getQueryParams(): query values such as ?page=2.
  • getParsedBody(): parsed form or JSON data.
  • getUploadedFiles(): PSR uploaded-file objects, including nested fields.
  • getCookieParams() and getHeaderLine('Content-Type'): cookies and headers.

These methods are available on the request created by the starter's RequestFactory. See Request Handlers for validation examples.

Middleware And Visibility

Place _middleware.php in the route directory you want to protect. Root middleware runs first; child directories add their own middleware. Children cannot remove a parent's protection. See Middleware.

Files and directories whose names start with _ or . are private. Reusable classes belong in src/Actions or src/Modules, not disguised as routable PHP files. Unsuffixed templates do not create URLs; method-suffixed files in the view tree do.

Matching And Errors

Static paths win before method selection. If articles/new.get.php and articles/[id].post.php exist, POST /articles/new returns 405 rather than treating new as an id.

  • No matching path: 404.
  • Matching path, unsupported method: 405 with an Allow header.
  • GET supplies a HEAD fallback unless an explicit .head.php exists.
  • The response emitter suppresses all HEAD bodies, including errors.
  • OPTIONS needs an explicit handler.
  • Paths are case-sensitive; trailing slashes are accepted.
  • Duplicate definitions and conflicting dynamic parameter names are errors.

Discovery reads filenames without executing handlers. Only the selected handler loads after middleware permits it.

Inspect And Cache Routes

php coriander routes:list

This lists URLs, methods, handler files, HEAD fallbacks, and inherited middleware without executing app code. The route map refreshes automatically; there is no controller-cache build step.

For a custom bootstrap, use (new Router())->handle($request) and emit the response with the original request method. See Request Lifecycle.

Upgrading from 0.2.x? Follow the 0.4.1 migration checklist before replacing your old route registration.