Search by

kreme201 / file-base-router

kreme201

File based router for PHP

Package info

github.com/kreme201/file-base-router

pkg:composer/kreme201/file-base-router

Statistics

Installs: 13

Dependents: 0

Suggesters: 0

Stars: 2

Open Issues: 0

0.2.0 2026-11-02 06:30 UTC

This package is auto-updated.

Last update: 2026-11-02 06:33:18 UTC


README

The File Based Router library is designed to handle dynamic routing based on files.

Install

composer require kreme201/file-base-router

It has been developed and tested on PHP 8.3.

Apache Setting

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ /index.php [L]

Usage

Project Structure

.
├── pages
│   ├── board
│   │   ├── [id].php
│   │   └── index.php
│   ├── index.php
├── public
│   └── index.php
└── vendor

public/index.php

<?php
require '/path/to/vendor/autoload.php';

use Kreme201\FileBaseRouter\Container;
use Kreme201\FileBaseRouter\Exception\RouteNotFoundException;
use Kreme201\FileBaseRouter\Router;

$router = new Router(dirname(__DIR__) . '/pages');

/** @var Container $container built-in container (default) */
$container = $router->getContainer();
$container->bind(App\BoardRepository::class, App\DbBoardRepository::class);

// To use another PSR-11 container instead:
// $router->setContainer($otherContainer);

$router
    ->on(RouteNotFoundException::class, function (RouteNotFoundException $e, string $path) {
        http_response_code(404);
        include dirname(__DIR__) . '/errors/404.php';
    })
    ->on(Throwable::class, function (Throwable $e) {
        http_response_code(500);
        include dirname(__DIR__) . '/errors/500.php';
    })
    ->handle(); // ServerRequest::fromGlobals()

Router::handle() dispatches the request, renders the template and handles exceptions.

  • on(ExceptionClass::class, $handler) registers a handler that receives the exception and the request path. Handlers are matched in registration order (the first instanceof match wins).
  • RouteNotFoundException is thrown when no template matches. InvalidRouteParameterException extends it, so both are handled as 404.
  • Without a handler, RouteNotFoundException sets a 404 status and any other exception is rethrown.
  • Output of a template that throws is discarded, so handlers can render a clean error page.
  • The built-in Container is used by default. Replace it with setContainer() (any PSR-11 container); getContainer() returns the current one.
  • Dispatcher and Renderer can still be used directly for custom flows. Renderer has the same setContainer() / getContainer().

When creating an instance of the Dispatcher, routing is handled based on the provided path.
Dynamic paths can be processed using [{name}] and [...{name}] to capture dynamic data; [...{name}] includes all sub-paths.
dispatch() returns a Route ($route->template, $route->params) or false.

/: {base_path}/index.php
/board: {base_path}/board/index.php
/board/123: {base_path}/board/[id].php, params => ['id' => '123']
/test/slug/example: {base_path}/test/[...slug].php, params => ['slug' => 'slug/example']

Injecting variables with PHPDoc

Renderer reads the @var tags of the template's first docblock and injects each variable.

<?php // pages/board/[id].php
use App\BoardRepository;

/**
 * @var int $id                 route parameter, converted to int
 * @var BoardRepository $repo   resolved from the container
 * @var ?string $q              null when it cannot be resolved
 */

$post = $repo->find($id);
?>
<h1><?= $post->title ?></h1>
  • A route parameter with the same name is converted to string, int, float, bool or array (string[] splits a catch-all by /). If the conversion fails, InvalidRouteParameterException is thrown (e.g. /board/abc), which is usually handled as a 404 (see above).
  • Class or interface types are resolved from the container. Any PSR-11 ContainerInterface implementation can be passed to $router->setContainer($container); @var ContainerInterface $container injects the container itself. use imports and the namespace of the template are respected.
  • Nullable types become null when nothing matches. Otherwise ContainerException is thrown.
  • Route parameters are no longer written to $_GET.

Handling forms (PSR-7)

Router::handle() without an argument builds a PSR-7 ServerRequest from the superglobals (ServerRequest::fromGlobals()). You can also pass any ServerRequestInterface, or a URI string (a GET request is built). Templates receive the request by declaring its type; route parameters are also available as request attributes.

<?php // pages/board/[id]/edit.php
use Kreme201\FileBaseRouter\Http\ServerRequest;

/**
 * @var int $id
 * @var ServerRequest $request    or Psr\Http\Message\ServerRequestInterface
 */

if ($request->isMethod('POST')) {
    $title = $request->getParam('title');                  // parsed body, then query string
    $file = $request->getUploadedFiles()['attachment'] ?? null;
    $file?->moveTo('/path/to/uploads/' . bin2hex(random_bytes(8)));
}
  • Implementations in Kreme201\FileBaseRouter\Http: ServerRequest, Request, Uri, Stream, UploadedFile (psr/http-message 2.0).
  • getParsedBody() is $_POST for POST forms. For PUT/PATCH/DELETE, application/x-www-form-urlencoded and JSON bodies are parsed as well.
  • getUploadedFiles() normalizes $_FILES, including nested fields such as photos[].
  • getParam() and isMethod() are helpers that are not part of PSR-7.
  • Exception handlers receive the request as the third argument: function (Throwable $e, string $path, ServerRequestInterface $request).

Non-ASCII paths

Request paths are percent-decoded before matching, so file, directory, parameter and variable names can be written in any language.

/%EC%86%8C%EA%B0%9C       → {base_path}/소개.php
/게시판/첫번째%20글          → {base_path}/게시판/[글번호].php, $글번호 => '첫번째 글'
/태그/여행/제주도            → {base_path}/태그/[...이름].php, $이름 => '여행/제주도'
  • Each segment is decoded once (%2525 → %25). Empty segments are collapsed (//a/ → /a).
  • Paths are never matched when a segment starts with . or __ (including ..), decodes to /, \ or NUL, or is not valid UTF-8.
  • Route::$path, RouteNotFoundException::$path and the handler's $path contain the decoded path.
  • macOS may store file names in Unicode NFD while browsers send NFC. Save page files in NFC when deploying from macOS.

Example

The playground directory is a runnable example app (Korean paths, route parameters, container injection, form submission and file upload).

composer install
composer serve    # http://localhost:8000