PHPackages                             sethrensei/ren-router - 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. [HTTP &amp; Networking](/categories/http)
4. /
5. sethrensei/ren-router

ActiveLibrary[HTTP &amp; Networking](/categories/http)

sethrensei/ren-router
=====================

PHP micro-router with RBAC, Twig support, URL extension spoofing and centralized HTTP exception handling

1.1.0(1mo ago)15MITPHP

Since Jan 13Pushed 1mo agoCompare

[ Source](https://github.com/SethRensei/RenRouter)[ Packagist](https://packagist.org/packages/sethrensei/ren-router)[ RSS](/packages/sethrensei-ren-router/feed)WikiDiscussions main Synced 1w ago

READMEChangelogDependencies (17)Versions (11)Used By (0)

RenRouter
=========

[](#renrouter)

**RenRouter** is a modern, lightweight and secure PHP micro-router — designed to be the routing core of a custom framework or as a standalone HTTP layer for projects that don't need a full framework.

It provides clean HTTP orchestration (routing, dispatching, views, error handling), declarative security (authentication and roles), pluggable template engines (PHP or Twig), and URL extension spoofing for stack obfuscation.

---

✨ Key Features
--------------

[](#-key-features)

- HTTP routing powered by **AltoRouter**
- **Fluent, readable** route registration with `get()`, `post()`, `route()` shortcuts
- **RouterFactory** — clean builder pattern, no positional `null` arguments
- **Pluggable template engines**: native PHP files or Twig (Symfony-style)
- **URL extension spoofing** — serve `/contact.html` or `/about.aspx` while routes stay clean internally
- Declarative route protection: **authentication and role-based access control**
- Centralized HTTP exception handling (401, 403, 404, 500) with dedicated error views
- `Controller@method` string target support alongside callables and view names
- **AbstractController** base class with rendering, redirects, JSON responses, flash messages and request helpers
- PSR-3 logger support (optional)
- PHP 8.1+ with `readonly` properties and `never` return types

---

🧱 Architecture
--------------

[](#-architecture)

```
RenRouter/
├── Router.php                        Core router and dispatcher
├── RouterFactory.php                 Fluent builder for Router assembly
│
├── Controller/
│   └── AbstractController.php        Base controller (render, redirect, json, flash, guards)
│
├── Template/
│   ├── TemplateEngineInterface.php   Contract for template engines
│   ├── PhpTemplateEngine.php         Native PHP file renderer (default)
│   └── TwigEngine.php                Twig adapter (mirrors Symfony conventions)
│
├── Security/
│   └── Auth.php                      Session-based auth helper (login, logout, roles)
│
├── Http/
│   ├── Request.php                   HTTP request abstraction
│   ├── UploadedFile.php              Secure file upload wrapper
│   └── Exception/
│       ├── HttpException.php
│       ├── UnauthorizedHttpException.php   (401)
│       ├── ForbiddenHttpException.php      (403)
│       └── NotFoundHttpException.php       (404)

Using/
├── views/  (or templates/ for Twig)
│   ├── base.php                      Layout wrapper ($pg_content injected)
│   └── errors/
│       ├── 401.php
│       ├── 403.php
│       ├── 404.php
│       └── 500.php
│
└── public/
    └── index.php                     Front controller

```

---

🚀 Quick Start
-------------

[](#-quick-start)

### 1. Install

[](#1-install)

```
composer require sethrensei/ren-router
composer require sethrensei/ren-router:version
```

### 2. Bootstrap (PHP templates)

[](#2-bootstrap-php-templates)

```
use RenRouter\RouterFactory;

$router = RouterFactory::create(__DIR__ . '/../views')
    ->withLogger($logger)               // PSR-3, optional
    ->withUrlExtension('.html')         // /contact becomes /contact.html publicly
    ->withSecurityRoute('auth.login')   // redirect target when not authenticated
    ->build();

$router
    ->get('/',        'home/index',  'home.app')
    ->get('/login',   'auth/login',  'auth.login')
    ->post('/login',  [$security, 'login'],  'auth.login.post')
    ->get('/logout',  [$security, 'logout'], 'auth.logout')
    ->get('/dashboard', 'app/dashboard', 'app.dashboard', ['auth' => true])
    ->get('/admin',     'admin/index',   'admin.index',   ['auth' => true, 'roles' => ['ROLE_ADMIN']])
    ->run();
```

### 3. Bootstrap (Twig)

[](#3-bootstrap-twig)

```
$router = RouterFactory::create(__DIR__ . '/../templates')
    ->withTwig(
        debug:     ($_ENV['APP_ENV'] === 'DEV'),
        cachePath: __DIR__ . '/../var/cache/twig',
    )
    ->withUrlExtension('.html')
    ->build();
```

---

🔀 Route Registration
--------------------

[](#-route-registration)

MethodSignatureUse case`get()``get(uri, target, name, options)`Single GET route`post()``post(uri, target, name, options)`Single POST route`route()``route(uri, target, method, name, options)`Any method or `GET|POST`**Targets** can be:

```
// A view name (rendered by the template engine)
->get('/about', 'pages/about', 'page.about')

// A callable
->get('/ping', fn(Router $r, array $p) => print('pong'), 'app.ping')

// A controller method array
->get('/users', [$userController, 'index'], 'user.index')

// A "Class@method" string
->get('/users', 'App\Controller\UserController@index', 'user.index')
```

**AltoRouter patterns** are supported in URIs:

```
->get('/user/[i:id]',        ...)   // integer
->get('/post/[a:slug]',      ...)   // alphanumeric + dash
->get('/file/[*:path]',      ...)   // anything including slashes
->get('/lang/[en|fr|de:lg]', ...)   // fixed options
```

---

🔐 Security &amp; Authorization
------------------------------

[](#-security--authorization)

Security is declared **at route level**, not inside controllers.

```
->get('/dashboard', 'app/dashboard', 'app.dashboard', [
    'auth'  => true,
    'roles' => ['ROLE_USER', 'ROLE_EDITOR'],
])
```

OptionTypeBehaviour`auth``bool`Redirects to the security route if not authenticated`roles``string|string[]`Throws 403 if no matching role is foundAutomatically thrown exceptions:

SituationExceptionHTTP CodeNot authenticated`UnauthorizedHttpException`401Wrong role`ForbiddenHttpException`403No matching route`NotFoundHttpException`404Any other error`HttpException` / caught `Throwable`500### Auth helper

[](#auth-helper)

```
use RenRouter\Security\Auth;

// Write (call right after credential verification)
Auth::login(['id' => 1, 'name' => 'Alice', 'roles' => ['ROLE_USER']]);
Auth::logout();
Auth::refreshSession();   // regenerate ID, keep data

// Read
Auth::check();                          // bool
Auth::id();                             // int|string|null
Auth::user();                           // full user array
Auth::get('name');                      // single field
Auth::roles();                          // string[]
Auth::hasRole('ROLE_ADMIN');            // bool
Auth::hasAnyRole(['ROLE_A', 'ROLE_B']); // bool — at least one
Auth::hasAllRoles(['ROLE_A', 'ROLE_B']);// bool — all required
```

---

🎮 Controllers
-------------

[](#-controllers)

Extend `AbstractController` for instant access to all response helpers.

```
use RenRouter\Controller\AbstractController;
use RenRouter\Router;

class PostController extends AbstractController
{
    public function __construct(Router $router)
    {
        parent::__construct($router);   // inject once, use everywhere
    }

    public function index(array $params): void
    {
        $this->requireAuth();
        $this->render('posts/index', ['posts' => []]);
    }

    public function show(array $params): void
    {
        $post = PostRepository::find((int) $params['id'])
            ?? $this->notFound("Post #{$params['id']} not found.");

        $this->render('posts/show', ['post' => $post]);
    }

    public function delete(array $params): void
    {
        $this->requireRole('ROLE_ADMIN');
        // delete…
        $this->flashSuccess('Post deleted.');
        $this->redirectToRoute('posts.index');
    }

    public function store(array $params): void
    {
        $this->requireAuth();
        $data = $this->postData(only: ['title', 'body']);

        if (empty($data['title'])) {
            $this->jsonError('Title is required.', 422);
        }

        $this->json(['success' => true], 201);
    }
}
```

### AbstractController API

[](#abstractcontroller-api)

CategoryMethodDescription**Guards**`requireAuth(?Router)`Redirect to login if not authenticated`requireRole(roles, ?Router)`Requires auth + matching role`denyUnless(bool, message)`Throws 403 when condition is false`notFound(message): never`Throws 404 immediately**Rendering**`render(view, data, ?Router)`Renders via the template engine`renderPartial(view, data, ?Router): string`Returns rendered HTML as string**Redirects**`redirectToRoute(name, params, status, ?Router)`Named route redirect`redirect(url, status, ?Router)`Raw URL redirect**JSON**`json(data, status): never`JSON response + exit`jsonError(message, status, extra): never``{error, message, code}` + exit**Flash**`flash(type, message)`Write flash to session`flashSuccess(message)`Shorthand for type `success``flashError(message)`Shorthand for type `error``getFlash(type): array`Read + clear one type`getAllFlash(): array`Read + clear all types**Request**`input(key, default, from)`Read from POST/GET/both`postData(only): array`All POST, optionally filtered`isMethod(method): bool`Check HTTP method`isAjax(): bool`XHR / JSON Accept detection---

🌐 Twig Integration
------------------

[](#-twig-integration)

`TwigEngine` mirrors Symfony's Twig integration exactly.

These functions are available in every template automatically:

Twig functionPHP equivalentExample output`path('route', {id:1})``Router::path()``/contact.html``url('route', {id:1})``Router::url()``https://example.com/contact.html``asset('css/app.css')``Router::asset()``https://example.com/css/app.css``route_exists('name')``Router::hasRoute()``true` / `false````
{# templates/home/index.twig #}
About
Contact

{% if route_exists('admin.dashboard') %}
    Admin
{% endif %}
```

For custom extensions and globals:

```
$twig = TwigEngine::create(viewsPath: __DIR__ . '/templates', debug: true);

$twig->addGlobal('app_name', 'MyApp');
$twig->addFunction('format_date', fn(\DateTimeInterface $d) => $d->format('d/m/Y'));
$twig->getTwig()->addExtension(new \Twig\Extension\StringLoaderExtension());

$router = RouterFactory::create(__DIR__ . '/templates')
    ->withTemplateEngine($twig)
    ->build();
```

---

🎭 URL Extension Spoofing
------------------------

[](#-url-extension-spoofing)

Confuse security scanners and bots by exposing a fake tech stack:

```
// Routes are always defined without extension:
->get('/contact', 'pages/contact', 'page.contact')

// Public-facing URLs get the suffix automatically:
// /contact.html  →  ".html" camouflage (Apache/Nginx static site)
// /contact.aspx  →  ".aspx" camouflage (IIS / ASP.NET)
// /contact.jsp   →  ".jsp"  camouflage (Java / Tomcat)
```

```
RouterFactory::create(__DIR__ . '/views')
    ->withUrlExtension('.aspx')
    ->build();
```

Generated URLs follow the same rule:

```
$router->url('page.contact');   // https://example.com/contact.aspx
$router->path('page.contact');  // /contact.aspx
```

The extension is stripped from incoming requests before matching — your route definitions never need to change.

---

❗ Error Handling
----------------

[](#-error-handling)

Errors are caught centrally by the router. In **production**, dedicated view files are rendered:

```
views/errors/401.php   — Unauthorized
views/errors/403.php   — Forbidden
views/errors/404.php   — Not Found
views/errors/500.php   — Internal Server Error

```

The `$exception` and `$code` variables are available inside error views.

In **development** (`APP_ENV=DEV`), the raw exception message and stack trace are printed as plain text.

You can also map error codes to named routes:

```
$router->setErrorRoute(404, 'error.notfound');
$router->setErrorRoute(403, 'error.forbidden');
```

---

🔗 URL Generation
----------------

[](#-url-generation)

```
// Absolute URL
$router->url('user.show', ['id' => 42]);
// => https://example.com/user/42.html

// Relative path
$router->path('user.show', ['id' => 42]);
// => /user/42.html

// Asset (never gets the fake extension)
$router->asset('img/logo.png');
// => https://example.com/img/logo.png

// Named redirect
$router->redirect('home.app');
$router->redirectUrl('https://example.com');
```

---

📦 Requirements
--------------

[](#-requirements)

PHP≥ 8.1AltoRouter`composer require altorouter/altorouter`Twig`composer require twig/twig`Symfony Dotenv`composer require symfony/dotenv`HTMLPurifier`composer require ezyang/htmlpurifier"`PSR-3 logger, HTTP message and factoryany PSR-3 compatible package---

🎯 Philosophy
------------

[](#-philosophy)

RenRouter is built around three principles:

- **Clarity over magic** — every behaviour is explicit and traceable
- **Security by default** — auth and roles declared at the route, not buried in controllers
- **A solid, extensible core** — swap the template engine, add a logger, override any component

It is not a framework. It is a **reliable foundation** to build one.

---

📄 License
---------

[](#-license)

MIT — free to use, modify and distribute.

###  Health Score

37

—

LowBetter than 81% of packages

Maintenance89

Actively maintained with recent releases

Popularity5

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity41

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 ~18 days

Recently: every ~2 days

Total

10

Last Release

57d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/130877906?v=4)[Seth Sita](/maintainers/SethRensei)[@SethRensei](https://github.com/SethRensei)

---

Top Contributors

[![SethRensei](https://avatars.githubusercontent.com/u/130877906?v=4)](https://github.com/SethRensei "SethRensei (34 commits)")

### Embed Badge

![Health badge](/badges/sethrensei-ren-router/health.svg)

```
[![Health](https://phpackages.com/badges/sethrensei-ren-router/health.svg)](https://phpackages.com/packages/sethrensei-ren-router)
```

###  Alternatives

[shopware/core

Shopware platform is the core for all Shopware ecommerce products.

595.8M674](/packages/shopware-core)[shopware/platform

The Shopware e-commerce core

3.4k1.5M3](/packages/shopware-platform)[symfony/symfony

The Symfony PHP framework

31.4k87.4M2.2k](/packages/symfony-symfony)[tempest/framework

The PHP framework that gets out of your way.

2.3k37.6k21](/packages/tempest-framework)[guzzlehttp/psr7

PSR-7 message implementation that also provides common utility methods

7.9k1.1B4.4k](/packages/guzzlehttp-psr7)[matomo/matomo

Matomo is the leading Free/Libre open analytics platform

21.7k39.6k](/packages/matomo-matomo)

PHPackages © 2026

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