PHPackages                             preflow/routing - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. preflow/routing

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

preflow/routing
===============

Preflow routing — file-based and attribute-based hybrid router

v0.13.1(1mo ago)0433MITPHPPHP &gt;=8.4

Since Apr 10Pushed 1mo agoCompare

[ Source](https://github.com/getpreflow/routing)[ Packagist](https://packagist.org/packages/preflow/routing)[ RSS](/packages/preflow-routing/feed)WikiDiscussions main Synced 1w ago

READMEChangelogDependencies (2)Versions (32)Used By (3)

Preflow Routing
===============

[](#preflow-routing)

Hybrid file-based + attribute-based router for Preflow. Implements `RouterInterface` from `preflow/core`.

Installation
------------

[](#installation)

```
composer require preflow/routing
```

Requires PHP 8.4+.

What's included
---------------

[](#whats-included)

ComponentDescription`FileRouteScanner`Maps `app/pages/` directory structure to Component routes`AttributeRouteScanner`Scans PHP classes for `#[Route]`, `#[Get]`, `#[Post]`, etc.`Router`Combines both scanners, implements `RouterInterface``RouteMatcher`Matches requests with priority: static &gt; dynamic &gt; catch-all`RouteCompiler`Caches the route collection to a PHP file for productionFile-based routing
------------------

[](#file-based-routing)

Files in the pages directory map directly to URLs. The scanner handles three file conventions:

FileURL pattern`index.twig``/` (or parent directory path)`about.twig``/about``[slug].twig``/{slug}` — dynamic segment`[...path].twig``/{path}` — catch-all (matches slashes too)`_layout.twig`excluded (underscore prefix)Example structure:

```
app/pages/
  index.twig           → GET /
  about.twig           → GET /about
  blog/
    index.twig         → GET /blog
    [slug].twig        → GET /blog/{slug}
  docs/
    [...path].twig     → GET /docs/{path}  (catches /docs/a/b/c)
  _layout.twig         → (ignored)

```

File routes resolve to `RouteMode::Component` — the handler value is the relative template path (e.g. `blog/[slug].twig`).

Attribute-based routing
-----------------------

[](#attribute-based-routing)

Controllers use `#[Route]` on the class for a path prefix, then HTTP method attributes on methods. These resolve to `RouteMode::Action` with the handler `ClassName@methodName`.

```
use Preflow\Routing\Attributes\Delete;
use Preflow\Routing\Attributes\Get;
use Preflow\Routing\Attributes\Middleware;
use Preflow\Routing\Attributes\Post;
use Preflow\Routing\Attributes\Put;
use Preflow\Routing\Attributes\Route;

#[Route('/api/posts')]
#[Middleware(ApiAuthMiddleware::class)]
final class PostController
{
    #[Get('/')]
    public function index(): ResponseInterface { /* ... */ }

    #[Get('/{id}')]
    public function show(): ResponseInterface { /* ... */ }

    #[Post('/')]
    public function create(): ResponseInterface { /* ... */ }

    #[Put('/{id}')]
    #[Middleware(OwnerMiddleware::class)]   // stacks on top of class middleware
    public function update(): ResponseInterface { /* ... */ }

    #[Delete('/{id}')]
    public function destroy(): ResponseInterface { /* ... */ }
}
```

Method paths are appended to the class prefix: `#[Get('/{id}')]` on `#[Route('/api/posts')]` becomes `/api/posts/{id}`. A method path of `'/'` resolves to the prefix alone.

`#[Middleware]` is repeatable on both class and method. Method middleware merges on top of class middleware.

### Catch-all params in attribute routes

[](#catch-all-params-in-attribute-routes)

Append `...` inside the braces to create a catch-all segment that matches slashes. Useful for nested path parameters in controllers.

```
#[Route('/files')]
final class FileController
{
    #[Get('/{path...}')]
    public function show(ServerRequestInterface $request): ResponseInterface
    {
        $path = $request->getAttribute('path'); // e.g. 'images/2026/photo.jpg'
        // ...
    }
}
```

`{path...}` compiles to a `.+` regex, matching one or more characters including `/`.

Router setup
------------

[](#router-setup)

```
use Preflow\Routing\Router;

$router = new Router(
    pagesDir:    __DIR__ . '/app/pages',      // file-based routes (optional)
    controllers: [PostController::class],      // attribute-based routes (optional)
    cachePath:   __DIR__ . '/storage/routes.php', // null = no cache
);

// Pass to Application
$app->setRouter($router);
```

Either `pagesDir` or `controllers` (or both) can be omitted. When `cachePath` is set and the cache file exists, the scanner is bypassed entirely.

Route cache (production)
------------------------

[](#route-cache-production)

```
use Preflow\Routing\RouteCompiler;

$compiler = new RouteCompiler();

// Generate cache
$compiler->compile($router->getCollection(), __DIR__ . '/storage/routes.php');

// Invalidate
$compiler->clear(__DIR__ . '/storage/routes.php');
```

The compiled file is a plain PHP `return` statement — no eval, OPcache-friendly.

Matching priority
-----------------

[](#matching-priority)

For a given HTTP method, `RouteMatcher` tests routes in three passes:

1. **Static** — exact string match, no parameters
2. **Dynamic** — has `{param}` segments, matched by regex
3. **Catch-all** — has `{...param}`, matches across slashes

The first match wins. Throws `NotFoundHttpException` if nothing matches.

###  Health Score

42

—

FairBetter than 88% of packages

Maintenance89

Actively maintained with recent releases

Popularity8

Limited adoption so far

Community10

Small or concentrated contributor base

Maturity52

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

31

Last Release

56d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/6d8b54efbc79683c32645e3fa8d3590037fa003963a16e0ed989104c6f4a2723?d=identicon)[smyr](/maintainers/smyr)

---

Top Contributors

[![smeyerme](https://avatars.githubusercontent.com/u/1925560?v=4)](https://github.com/smeyerme "smeyerme (11 commits)")

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/preflow-routing/health.svg)

```
[![Health](https://phpackages.com/badges/preflow-routing/health.svg)](https://phpackages.com/packages/preflow-routing)
```

###  Alternatives

[acsiomatic/device-detector-bundle

Symfony Bundle for https://github.com/matomo-org/device-detector

12194.7k](/packages/acsiomatic-device-detector-bundle)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
