{"id":43640413,"url":"https://github.com/tonics-apps/tonics-router-system","last_synced_at":"2026-02-04T18:03:01.741Z","repository":{"id":131214916,"uuid":"500541109","full_name":"tonics-apps/tonics-router-system","owner":"tonics-apps","description":"A Super Fast PHP Router System","archived":false,"fork":false,"pushed_at":"2025-11-25T14:13:11.000Z","size":136,"stargazers_count":20,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-11-28T19:37:18.711Z","etag":null,"topics":["php","router","routing"],"latest_commit_sha":null,"homepage":"https://tonics.app","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tonics-apps.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2022-06-06T18:03:19.000Z","updated_at":"2025-11-25T14:11:20.000Z","dependencies_parsed_at":null,"dependency_job_id":"da895dd6-103e-458b-81f3-17f94c38654c","html_url":"https://github.com/tonics-apps/tonics-router-system","commit_stats":null,"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"purl":"pkg:github/tonics-apps/tonics-router-system","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tonics-apps%2Ftonics-router-system","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tonics-apps%2Ftonics-router-system/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tonics-apps%2Ftonics-router-system/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tonics-apps%2Ftonics-router-system/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tonics-apps","download_url":"https://codeload.github.com/tonics-apps/tonics-router-system/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tonics-apps%2Ftonics-router-system/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29092731,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-04T03:31:03.593Z","status":"ssl_error","status_checked_at":"2026-02-04T03:29:50.742Z","response_time":62,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["php","router","routing"],"created_at":"2026-02-04T18:02:57.266Z","updated_at":"2026-02-04T18:03:01.732Z","avatar_url":"https://github.com/tonics-apps.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Tonics Router System\n\nA Trie based PHP Router System For Tonics Projects. \n\nThis would serve as a base router for tonics web apps, the router is different from most PHP Router \nin the sense that it doesn't use regex for matching urls, it instead uses a tree data structure where every path is\nhierarchically organized making it faster for finding both static or dynamic url.\n\nAdditionally, I came up with a concept called Node Teleporting which can further enhance and speed up searching dynamic routes, in the best case, dynamic route would be matched directly just like the static routes, and in the worse case, it would teleport a couple of times which is also faster than mere traversing.\n\nYou can learn more about the teleporting in the part 2 of how the router works.\n\n## Features\n\n* **Fast Trie-based routing** - Uses a tree data structure instead of regex for faster URL matching\n* **Node Teleporting** - Advanced optimization for dynamic routes\n* **PSR-7 Support** - Full PSR-7 HTTP message interface compatibility\n* **Backward Compatible** - Works with traditional PHP globals or PSR-7 objects\n* **Request Interceptors** - Middleware-like functionality for request processing\n* **Route Groups** - Organize routes hierarchically with shared attributes\n* **Dependency Injection** - Built-in container for automatic dependency resolution\n\n## Requirements\n* PHP 8.0 and above\n* PHP mbstring extension enabled.\n\n## Installation\n\n```\ncomposer require devsrealm/tonics-router-system\n```\n\nIf you don't want to use composer, go-to the release section and download the zip file that has a postfix of `composer-no-required` e.g `tonics-router-system-v1.0.0-composer-no-required.zip`\n\nUnzip it and require it like so:\n\n```\nrequire 'path/to/tonics-router-system/vendor/autoload.php';\n```\n\n## How The Router Works\n\n1. [A Faster Router System in PHP (Part 1)](https://tonics.app/posts/ff9af70984746b91/faster-router-php)\n2. [A Faster Router System in PHP (Part 2) (Improvement \u0026 Benchmarks)](https://tonics.app/posts/409a745fcbf15371/faster-router-system-in-php-part-2-improvement-and-benchmarks)\n\n## Documentation\n\nBefore you get started, wire up the Router dependencies:\n\n```php\nuse Devsrealm\\TonicsRouterSystem\\Container\\Container;\nuse Devsrealm\\TonicsRouterSystem\\Events\\OnRequestProcess;\nuse Devsrealm\\TonicsRouterSystem\\Handler\\Router;\nuse Devsrealm\\TonicsRouterSystem\\RequestInput;\nuse Devsrealm\\TonicsRouterSystem\\Resolver\\RouteResolver;\nuse Devsrealm\\TonicsRouterSystem\\Response;\nuse Devsrealm\\TonicsRouterSystem\\Route;\nuse Devsrealm\\TonicsRouterSystem\\RouteNode;\nuse Devsrealm\\TonicsRouterSystem\\RouteTreeGenerator;\nuse Devsrealm\\TonicsRouterSystem\\State\\RouteTreeGeneratorState;\n\n$onRequestProcess = new OnRequestProcess(\n                        new RouteResolver(\n                            new Container()\n                        ),\n                        new Route(\n                            new RouteTreeGenerator(\n                                new RouteTreeGeneratorState(), new RouteNode()\n                                )\n                        )\n                );\n\n$router = new Router(\n    $onRequestProcess,\n    $onRequestProcess-\u003egetRouteObject(),\n    new Response(\n        $onRequestProcess, new RequestInput()\n        )\n    );\n```\n\n### Basic routing\n\nFirst parameter is the url paths which you want the route to match, and the second parameter\ncould be a closure or a callback function that the route would call once the route matches.\n\n```php\n$route = $router-\u003egetRoute();\n\n$route-\u003eget('/', function() {\n    return 'Welcome To My Home Page';\n});\n\n// Once your route is set up, dispatch it (don't forget to do this once all your route is set-up, otherwise, it won't work):\ntry {\n    $router-\u003edispatchRequestURL();\n} catch (Exception $e) {\n    // handle error or 404\n}\n```\n\nIf you want to keep things organized, you can also resolve through a class method, like so:\n\n```php\n$route-\u003eget('/', [HomePage::class, 'methodName']);\n```\n\n### Request Interceptors\nSome call it middleware, `requestInterceptor` sounds plain and simple to me. \nRequestInterceptors can be used to intercept a request before it moves to the next life cycle or to other request interceptors.\n\nFor example, if you have an admin url path: /admin, and you want to check if a user is logged in before processing the request, you use the request interceptor. Let's see an example:\n\n```php\n$route-\u003eget('admin', [AdminController::class, 'adminDashboard'], [IsAuthenticated::class]);\n```\n\nin `isAuthenticated()` class you can have something as such:\n\n```php\nclass Authenticated implements TonicsRouterRequestInterceptorInterface\n{\n    /**\n     * @inheritDoc\n     */\n    public function handle(OnRequestProcess $request): void\n    {\n       if (UserData::isAuthenticated() === false){\n           # If this is for admin, then redirect to admin login\n           if (str_starts_with($request-\u003egetRequestURL(), '/admin')){\n               redirect(route('admin.login'));\n           }\n\n           # If this is for customer, then redirect to customer login\n           if (str_starts_with($request-\u003egetRequestURL(), '/customer')){\n               redirect(route('customer.login'));\n           }\n\n           # Else...\n           SimpleState::displayUnauthorizedErrorMessage();\n       }\n    }\n}\n```\n\nWe implemented the `TonicsRouterRequestInterceptorInterface` (it is a must to implement the interface to use the request interceptor) which provides a handle method with the $request object.\nInside the handle method, I am checking if user is not authenticated, and thus redirecting them to their proper destination. \n\nHowever, if user is authenticated, the interceptor would move to the next life cycle in the route state, the next life cycle could be a new request interceptor or a class method or a callback delegation.\n\nTo add more request interceptors, simply do:\n\n```php\n$route-\u003eget('admin', \n    [AdminController::class, 'adminDashboard'], \n    [IsAuthenticated::class, MoreInterceptor::class, EvenMoreInterceptor::class]\n    );\n```\n\n### Route Required parameters\nTo match a dynamic url parameter you do:\n\n```php\n$route-\u003eget('posts/:slug', function($slug) {\n    return \"Post with slug: $slug\";\n});\n```\n\nwhere you capture the slug from the url, for example, if user visits /posts/blog-post-title, you get access \nto `blog-post-title`.\n\nAlternatively you can do\n\n```php\n$route-\u003eget('/posts/:slug', [PostsController::class, 'viewPost']);\n```\n\nwhere `PostsController could look like:\n```php\nclass PostsController\n{\n    viewPost($slug)\n    {\n        return \"Post with slug: $slug\";\n    }\n}\n```\n\n### Route Groups\n\nWith the route group you could organize route in a tree like fashion, the good thing about this approach is  \nyou can share route attributes, such as route interceptors, parent url paths, etc. across a large number of routes without needing to define those attributes on each individual route.\n\ninstead of doing this:\n\n```php\n$route-\u003eget('admin/login', [LoginController::class, 'showLoginForm'], [SpecialInterceptor::class, RedirectAuthenticated::class]);\n$route-\u003epost('admin/login', [LoginController::class, 'login'], [SpecialInterceptor::class]);\n$route-\u003epost('admin/logout', [LoginController::class, 'logout'], [SpecialInterceptor::class]);\n```\n\ndo this:\n\n```php\n$route-\u003egroup('admin', function (Route $route){\n    $route-\u003eget('login', [LoginController::class, 'showLoginForm'], [RedirectAuthenticated::class]);\n    $route-\u003epost('login', [LoginController::class, 'login']);\n    $route-\u003epost('logout', [LoginController::class, 'logout']);\n}, [SpecialInterceptor::class]);\n```\nThe end goal is identical to the above one but this is better organized.\n\nYou could also nest a group:\n\n```php\n$route-\u003egroup('/admin/posts', function (Route $route){\n            #---------------------------------\n        # POST CATEGORIES...\n    #---------------------------------\n    $route-\u003egroup('/category', function (Route $route){\n        $route-\u003eget('', [PostCategoryController::class, 'index']);\n        $route-\u003eget(':category/edit', [PostCategoryController::class, 'edit']);\n        $route-\u003eget('create', [PostCategoryController::class, 'create']);\n        $route-\u003epost('store', [PostCategoryController::class, 'store']);\n        $route-\u003epost(':category/trash', [PostCategoryController::class, 'trash']);\n        $route-\u003epost( '/trash/multiple', [PostCategoryController::class, 'trashMultiple']);\n        $route-\u003ematch(['post', 'put', 'patch'], ':category/update', [PostCategoryController::class, 'update']);\n        $route-\u003ematch(['post', 'delete'], ':category/delete', [PostCategoryController::class, 'delete']);\n    }, alias: 'category');\n\n}, [StartSession::class, CSRFGuard::class, Authenticated::class, PostAccess::class]);\n```\n\n### Route HTTP Verbs\n\n* `$route-\u003eget(string $url, array|Closure $callback, array $requestInterceptor = [])`\n* `$route-\u003epost(string $url, array|Closure $callback, array $requestInterceptor = [])`\n* `$route-\u003eput(string $url, array|Closure $callback, array $requestInterceptor = [])`\n* `$route-\u003epatch(string $url, array|Closure $callback, array $requestInterceptor = [])`\n* `$route-\u003edelete(string $url, array|Closure $callback, array $requestInterceptor = [])`\n* `$route-\u003ematch(array $method, string $url, \\Closure|array $callback, array $requestInterceptor = [])`\n\nWith match, you can match multiple HTTP verbs in one fell swoop.\n\n## PSR-7 Support\n\nThe Tonics Router System now has full PSR-7 support while maintaining 100% backward compatibility. You can choose to use PSR-7 HTTP message interfaces or continue using the traditional approach.\n\n### Why Use PSR-7?\n\nPSR-7 provides standardized HTTP message interfaces that:\n- Work seamlessly with modern PHP frameworks and libraries\n- Enable better testing with mock request/response objects\n- Provide immutable request/response objects for safer code\n- Follow PHP-FIG standards for better interoperability\n\n### Using PSR-7 Router (Recommended for new projects)\n\nThe easiest way to use PSR-7 is with the `Psr7Router` class:\n\n```php\nuse Devsrealm\\TonicsRouterSystem\\Handler\\Psr7Router;\n\n// Create router from PHP globals\n$router = Psr7Router::create();\n\n// Define your routes\n$router-\u003egetRoute()-\u003eget('/', function() {\n    return 'Welcome to PSR-7!';\n});\n\n$router-\u003egetRoute()-\u003eget('/user/:id', function($id) {\n    return \"User ID: $id\";\n});\n\n// Handle request and emit response\n$router-\u003erun();\n```\n\n**Important PSR-7 Best Practice:** Controllers should **return** content, not `echo` it:\n\n```php\n// ✅ GOOD: Return content (PSR-7 compliant)\n$router-\u003egetRoute()-\u003eget('/api/users', function() {\n    return json_encode(['users' =\u003e []]);\n});\n\n// ❌ BAD: Echo content (not PSR-7 compliant)\n$router-\u003egetRoute()-\u003eget('/api/users', function() {\n    echo json_encode(['users' =\u003e []]);  // Don't do this!\n});\n```\n\nIf you have legacy code that uses `echo`, you can use `handleWithOutputBuffering()` temporarily:\n\n```php\n// For legacy code only (not recommended)\n$response = $router-\u003ehandleWithOutputBuffering($request);\n$router-\u003eemit($response);\n```\n\nHowever, **refactoring to return values is strongly recommended** for proper PSR-7 compliance.\n\n### Manual PSR-7 Usage (Advanced)\n\nFor more control, you can manually create PSR-7 objects:\n\n```php\nuse Devsrealm\\TonicsRouterSystem\\Handler\\Psr7Router;\nuse Devsrealm\\TonicsRouterSystem\\Psr7Factory;\n\n// Create a PSR-7 request from globals\n$psrRequest = Psr7Factory::createServerRequestFromGlobals();\n\n// Create the router with the PSR-7 request\n$router = new Psr7Router($psrRequest);\n\n// Define routes\n$router-\u003egetRoute()-\u003eget('/api/data', function() {\n    return json_encode(['status' =\u003e 'success', 'data' =\u003e []]);\n});\n\n// Handle the request\n$psrResponse = $router-\u003ehandle($psrRequest);\n\n// Emit the response\n$router-\u003eemit($psrResponse);\n```\n\n\n### Creating Router with Custom Container\n\n```php\nuse Devsrealm\\TonicsRouterSystem\\Handler\\Psr7Router;\nuse Devsrealm\\TonicsRouterSystem\\Container\\Container;\nuse Devsrealm\\TonicsRouterSystem\\Resolver\\RouteResolver;\nuse Devsrealm\\TonicsRouterSystem\\Psr7Factory;\n\n// Create and configure your container\n$container = new Container();\n\n// Register all your dependencies\n$container-\u003eset(DatabaseInterface::class, fn() =\u003e new MySQLDatabase());\n$container-\u003eset(UserRepository::class, fn($c) =\u003e new UserRepository($c-\u003eget(DatabaseInterface::class)));\n// ... more registrations\n\n// Create route resolver with your configured container\n$routeResolver = new RouteResolver($container);\n\n// Create PSR-7 request\n$psrRequest = Psr7Factory::createServerRequestFromGlobals();\n\n// Create router with custom resolver\n$router = new Psr7Router($psrRequest, $routeResolver);\n\n// Now define routes\n$router-\u003egetRoute()-\u003eget('/', [HomeController::class, 'index']);\n$router-\u003erun();\n```\n\n### Auto-wiring Example\n\nThe container can automatically resolve dependencies if they're type-hinted:\n\n```php\n$router = Psr7Router::create();\n\n// Register only what can't be auto-resolved (primitives, interfaces, etc.)\n$router-\u003egetContainer()-\u003eset(DatabaseInterface::class, function() {\n    return new MySQLDatabase('localhost', 'mydb', 'user', 'pass');\n});\n\n// These classes will be auto-resolved\nclass EmailService {\n    // No dependencies - will be auto-created\n}\n\nclass UserRepository {\n    // DatabaseInterface must be registered (interface)\n    public function __construct(private DatabaseInterface $db) {}\n}\n\nclass UserService {\n    // UserRepository will be auto-created, EmailService will be auto-created\n    public function __construct(\n        private UserRepository $repo,\n        private EmailService $email\n    ) {}\n}\n\nclass UserController {\n    // UserService and all its dependencies will be auto-resolved!\n    public function __construct(private UserService $service) {}\n    \n    public function show($id) {\n        return json_encode($this-\u003eservice-\u003efindById($id));\n    }\n}\n\n// Just register the route - everything else is automatic!\n$router-\u003egetRoute()-\u003eget('/users/:id', [UserController::class, 'show']);\n```\n\n### Using PSR-7 Request Adapter\n\nYou can also use PSR-7 requests with individual components:\n\n```php\nuse Devsrealm\\TonicsRouterSystem\\Adapter\\Psr7RequestAdapter;\nuse Devsrealm\\TonicsRouterSystem\\Psr7Factory;\n\n$psrRequest = Psr7Factory::createServerRequestFromGlobals();\n$requestAdapter = new Psr7RequestAdapter($psrRequest);\n\n// Use it like the traditional RequestInput\n$postData = $requestAdapter-\u003efromPost()-\u003eall();\n$userId = $requestAdapter-\u003efromGet()-\u003eretrieve('user_id');\n```\n\n### Using PSR-7 Response Adapter\n\nFor PSR-7 compliant responses:\n\n```php\nuse Devsrealm\\TonicsRouterSystem\\Adapter\\Psr7ResponseAdapter;\nuse Devsrealm\\TonicsRouterSystem\\Psr7Factory;\n\n$psrResponse = Psr7Factory::createResponse(200);\n$responseAdapter = new Psr7ResponseAdapter($psrResponse);\n\n// Use Tonics-style methods\n$responseAdapter-\u003ejson(['status' =\u003e 'success']);\n// or\n$responseAdapter-\u003eredirect('/dashboard');\n```\n\n### Backward Compatibility\n\n**All existing code continues to work!** The traditional approach still works exactly as before:\n\n```php\nuse Devsrealm\\TonicsRouterSystem\\Container\\Container;\nuse Devsrealm\\TonicsRouterSystem\\Events\\OnRequestProcess;\nuse Devsrealm\\TonicsRouterSystem\\Handler\\Router;\nuse Devsrealm\\TonicsRouterSystem\\RequestInput;\nuse Devsrealm\\TonicsRouterSystem\\Resolver\\RouteResolver;\nuse Devsrealm\\TonicsRouterSystem\\Response;\nuse Devsrealm\\TonicsRouterSystem\\Route;\nuse Devsrealm\\TonicsRouterSystem\\RouteNode;\nuse Devsrealm\\TonicsRouterSystem\\RouteTreeGenerator;\nuse Devsrealm\\TonicsRouterSystem\\State\\RouteTreeGeneratorState;\n\n$onRequestProcess = new OnRequestProcess(\n    new RouteResolver(new Container()),\n    new Route(\n        new RouteTreeGenerator(\n            new RouteTreeGeneratorState(), \n            new RouteNode()\n        )\n    )\n);\n\n$router = new Router(\n    $onRequestProcess,\n    $onRequestProcess-\u003egetRouteObject(),\n    new Response($onRequestProcess, new RequestInput())\n);\n\n// Traditional usage works as always\n$route = $router-\u003egetRoute();\n$route-\u003eget('/', function() {\n    return 'Hello World';\n});\n\n$router-\u003edispatchRequestURL();\n```\n\n### PSR-7 in Controllers\n\nWhen using PSR-7, you can type-hint PSR-7 interfaces in your controllers:\n\n```php\nuse Psr\\Http\\Message\\ServerRequestInterface;\nuse Psr\\Http\\Message\\ResponseInterface;\n\nclass UserController\n{\n    public function show(ServerRequestInterface $request, string $id)\n    {\n        // Access PSR-7 request\n        $queryParams = $request-\u003egetQueryParams();\n        $headers = $request-\u003egetHeaders();\n        \n        return \"User: $id\";\n    }\n}\n```\n\n### Testing with PSR-7\n\nPSR-7 makes testing much easier:\n\n```php\nuse Devsrealm\\TonicsRouterSystem\\Handler\\Psr7Router;\nuse Nyholm\\Psr7\\Factory\\Psr17Factory;\n\n// Create a test request\n$factory = new Psr17Factory();\n$testRequest = $factory-\u003ecreateServerRequest('GET', '/api/user/123');\n\n// Create router and handle\n$router = new Psr7Router($testRequest);\n$router-\u003egetRoute()-\u003eget('/api/user/:id', function($id) {\n    return json_encode(['id' =\u003e $id]);\n});\n\n$response = $router-\u003ehandle($testRequest);\n\n// Assert response\nassert($response-\u003egetStatusCode() === 200);\nassert($response-\u003egetHeaderLine('Content-Type') === 'application/json');\n```\n\n## Working with Controllers\n\nControllers help organize your application logic. Here are practical examples:\n\n### Basic Controller Example\n\n```php\nnamespace App\\Controllers;\n\nclass HomeController\n{\n    public function index()\n    {\n        return 'Welcome to the homepage!';\n    }\n    \n    public function about()\n    {\n        return 'About us page';\n    }\n}\n\n// Register routes\n$route-\u003eget('/', [HomeController::class, 'index']);\n$route-\u003eget('/about', [HomeController::class, 'about']);\n```\n\n### Controller with Route Parameters\n\n```php\nnamespace App\\Controllers;\n\nclass PostController\n{\n    public function show($slug)\n    {\n        // Fetch post from database\n        $post = Post::findBySlug($slug);\n        \n        if (!$post) {\n            http_response_code(404);\n            return 'Post not found';\n        }\n        \n        return json_encode($post);\n    }\n    \n    public function showById($id)\n    {\n        $post = Post::find($id);\n        return json_encode($post);\n    }\n}\n\n// Register routes\n$route-\u003eget('/posts/:slug', [PostController::class, 'show']);\n$route-\u003eget('/posts/id/:id', [PostController::class, 'showById']);\n```\n\n### Controller with Dependency Injection\n\nThe router automatically resolves dependencies through the container:\n\n```php\nnamespace App\\Controllers;\n\nuse App\\Services\\UserService;\nuse App\\Services\\EmailService;\n\nclass UserController\n{\n    private UserService $userService;\n    private EmailService $emailService;\n    \n    // Dependencies are automatically injected\n    public function __construct(UserService $userService, EmailService $emailService)\n    {\n        $this-\u003euserService = $userService;\n        $this-\u003eemailService = $emailService;\n    }\n    \n    public function show($id)\n    {\n        $user = $this-\u003euserService-\u003efindById($id);\n        return json_encode($user);\n    }\n    \n    public function sendWelcomeEmail($id)\n    {\n        $user = $this-\u003euserService-\u003efindById($id);\n        $this-\u003eemailService-\u003esendWelcome($user-\u003eemail);\n        return json_encode(['message' =\u003e 'Email sent']);\n    }\n}\n\n// Register routes\n$route-\u003eget('/user/:id', [UserController::class, 'show']);\n$route-\u003epost('/user/:id/welcome', [UserController::class, 'sendWelcomeEmail']);\n```\n\n### RESTful Controller Example\n\n```php\nnamespace App\\Controllers;\n\nuse Devsrealm\\TonicsRouterSystem\\RequestInput;\n\nclass ApiUserController\n{\n    private RequestInput $input;\n    \n    public function __construct(RequestInput $input)\n    {\n        $this-\u003einput = $input;\n    }\n    \n    // GET /api/users\n    public function index()\n    {\n        $users = User::all();\n        return json_encode(['users' =\u003e $users]);\n    }\n    \n    // GET /api/users/:id\n    public function show($id)\n    {\n        $user = User::find($id);\n        return json_encode(['user' =\u003e $user]);\n    }\n    \n    // POST /api/users\n    public function store()\n    {\n        $data = $this-\u003einput-\u003efromPost();\n        \n        $name = $data-\u003eretrieve('name');\n        $email = $data-\u003eretrieve('email');\n        \n        $user = User::create(['name' =\u003e $name, 'email' =\u003e $email]);\n        \n        http_response_code(201);\n        return json_encode(['user' =\u003e $user]);\n    }\n    \n    // PUT /api/users/:id\n    public function update($id)\n    {\n        $data = $this-\u003einput-\u003efromPost();\n        \n        $user = User::find($id);\n        $user-\u003ename = $data-\u003eretrieve('name', $user-\u003ename);\n        $user-\u003eemail = $data-\u003eretrieve('email', $user-\u003eemail);\n        $user-\u003esave();\n        \n        return json_encode(['user' =\u003e $user]);\n    }\n    \n    // DELETE /api/users/:id\n    public function destroy($id)\n    {\n        User::find($id)-\u003edelete();\n        return json_encode(['message' =\u003e 'User deleted']);\n    }\n}\n\n// Register RESTful routes\n$route-\u003eget('/api/users', [ApiUserController::class, 'index']);\n$route-\u003eget('/api/users/:id', [ApiUserController::class, 'show']);\n$route-\u003epost('/api/users', [ApiUserController::class, 'store']);\n$route-\u003eput('/api/users/:id', [ApiUserController::class, 'update']);\n$route-\u003edelete('/api/users/:id', [ApiUserController::class, 'destroy']);\n```\n\n### PSR-7 Controller Example\n\n```php\nnamespace App\\Controllers;\n\nuse Psr\\Http\\Message\\ServerRequestInterface;\nuse Devsrealm\\TonicsRouterSystem\\Adapter\\Psr7ResponseAdapter;\n\nclass Psr7UserController\n{\n    // Type-hint PSR-7 interfaces\n    public function show(ServerRequestInterface $request, $id)\n    {\n        // Access query parameters\n        $queryParams = $request-\u003egetQueryParams();\n        $includeEmail = $queryParams['include_email'] ?? false;\n        \n        // Access headers\n        $authToken = $request-\u003egetHeaderLine('Authorization');\n        \n        // Get request body\n        $body = $request-\u003egetBody()-\u003egetContents();\n        \n        $user = User::find($id);\n        \n        if ($includeEmail === 'true') {\n            return json_encode(['id' =\u003e $user-\u003eid, 'name' =\u003e $user-\u003ename, 'email' =\u003e $user-\u003eemail]);\n        }\n        \n        return json_encode(['id' =\u003e $user-\u003eid, 'name' =\u003e $user-\u003ename]);\n    }\n}\n```\n\n### Complete Example with Container + PSR-7\n\nHere's a full real-world example showing how everything works together:\n\n```php\nuse Devsrealm\\TonicsRouterSystem\\Handler\\Psr7Router;\n\n// 1. Create router\n$router = Psr7Router::create();\n\n// 2. Configure container with your dependencies\n$container = $router-\u003egetContainer();\n\n// Register database\n$container-\u003esingleton(PDO::class, function() {\n    return new PDO('mysql:host=localhost;dbname=myapp', 'user', 'pass');\n});\n\n// Register repositories\n$container-\u003eset(UserRepository::class, function($c) {\n    return new UserRepository($c-\u003eget(PDO::class));\n});\n\n$container-\u003eset(PostRepository::class, function($c) {\n    return new PostRepository($c-\u003eget(PDO::class));\n});\n\n// Register services\n$container-\u003eset(UserService::class, function($c) {\n    return new UserService(\n        $c-\u003eget(UserRepository::class),\n        $c-\u003eget(EmailService::class)\n    );\n});\n\n$container-\u003eset(EmailService::class, function() {\n    return new EmailService(getenv('SMTP_HOST'), getenv('SMTP_PORT'));\n});\n\n// 3. Define your routes\n$router-\u003egetRoute()-\u003eget('/', [HomeController::class, 'index']);\n$router-\u003egetRoute()-\u003eget('/users/:id', [UserController::class, 'show']);\n$router-\u003egetRoute()-\u003epost('/users', [UserController::class, 'store']);\n$router-\u003egetRoute()-\u003eget('/posts/:slug', [PostController::class, 'show']);\n\n// 4. Run the application\n$router-\u003erun();\n\n// Controller examples\nclass HomeController {\n    public function index() {\n        return json_encode(['message' =\u003e 'Welcome to our API']);\n    }\n}\n\nclass UserController {\n    // Dependencies auto-injected via container\n    public function __construct(\n        private UserService $userService,\n        private UserRepository $userRepo\n    ) {}\n    \n    public function show($id) {\n        try {\n            $user = $this-\u003euserService-\u003efindById($id);\n            return json_encode(['user' =\u003e $user]);\n        } catch (NotFoundException $e) {\n            http_response_code(404);\n            return json_encode(['error' =\u003e 'User not found']);\n        }\n    }\n    \n    public function store() {\n        // Using PSR-7 request in constructor\n        $data = json_decode(file_get_contents('php://input'), true);\n        \n        $user = $this-\u003euserService-\u003ecreateUser(\n            $data['name'] ?? '',\n            $data['email'] ?? ''\n        );\n        \n        http_response_code(201);\n        return json_encode(['user' =\u003e $user]);\n    }\n}\n\nclass PostController {\n    public function __construct(private PostRepository $postRepo) {}\n    \n    public function show($slug) {\n        $post = $this-\u003epostRepo-\u003efindBySlug($slug);\n        \n        if (!$post) {\n            http_response_code(404);\n            return json_encode(['error' =\u003e 'Post not found']);\n        }\n        \n        return json_encode(['post' =\u003e $post]);\n    }\n}\n\n// Service layer\nclass UserService {\n    public function __construct(\n        private UserRepository $userRepo,\n        private EmailService $emailService\n    ) {}\n    \n    public function findById($id) {\n        return $this-\u003euserRepo-\u003efind($id);\n    }\n    \n    public function createUser(string $name, string $email) {\n        $user = $this-\u003euserRepo-\u003ecreate(['name' =\u003e $name, 'email' =\u003e $email]);\n        $this-\u003eemailService-\u003esendWelcome($user-\u003eemail);\n        return $user;\n    }\n}\n\n// Repository layer\nclass UserRepository {\n    public function __construct(private PDO $db) {}\n    \n    public function find($id) {\n        $stmt = $this-\u003edb-\u003eprepare('SELECT * FROM users WHERE id = ?');\n        $stmt-\u003eexecute([$id]);\n        return $stmt-\u003efetch(PDO::FETCH_OBJ);\n    }\n    \n    public function create(array $data) {\n        $stmt = $this-\u003edb-\u003eprepare('INSERT INTO users (name, email) VALUES (?, ?)');\n        $stmt-\u003eexecute([$data['name'], $data['email']]);\n        return $this-\u003efind($this-\u003edb-\u003elastInsertId());\n    }\n}\n```\n\n## Request Interceptors (Middleware) Examples\n\nRequest Interceptors act as middleware to process requests before they reach your controllers.\n\n### Authentication Interceptor\n\n```php\nnamespace App\\Middleware;\n\nuse Devsrealm\\TonicsRouterSystem\\Events\\OnRequestProcess;\nuse Devsrealm\\TonicsRouterSystem\\Interfaces\\TonicsRouterRequestInterceptorInterface;\n\nclass AuthenticationMiddleware implements TonicsRouterRequestInterceptorInterface\n{\n    public function handle(OnRequestProcess $request): void\n    {\n        // Check if user is authenticated\n        if (!isset($_SESSION['user_id'])) {\n            // Redirect to login page\n            http_response_code(401);\n            header('Location: /login');\n            exit;\n        }\n        \n        // If authenticated, continue to next interceptor or controller\n    }\n}\n\n// Usage\n$route-\u003eget('/dashboard', [DashboardController::class, 'index'], [AuthenticationMiddleware::class]);\n```\n\n### CORS Interceptor\n\n```php\nnamespace App\\Middleware;\n\nuse Devsrealm\\TonicsRouterSystem\\Events\\OnRequestProcess;\nuse Devsrealm\\TonicsRouterSystem\\Interfaces\\TonicsRouterRequestInterceptorInterface;\n\nclass CorsMiddleware implements TonicsRouterRequestInterceptorInterface\n{\n    public function handle(OnRequestProcess $request): void\n    {\n        // Add CORS headers\n        header('Access-Control-Allow-Origin: *');\n        header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');\n        header('Access-Control-Allow-Headers: Content-Type, Authorization');\n        \n        // Handle preflight requests\n        if ($request-\u003egetRequestMethod() === 'OPTIONS') {\n            http_response_code(200);\n            exit;\n        }\n        \n        // Continue to next interceptor or controller\n    }\n}\n\n// Usage on API routes\n$route-\u003egroup('/api', function (Route $route) {\n    $route-\u003eget('/users', [ApiUserController::class, 'index']);\n    $route-\u003epost('/users', [ApiUserController::class, 'store']);\n}, [CorsMiddleware::class]);\n```\n\n### JSON Content-Type Validator\n\n```php\nnamespace App\\Middleware;\n\nuse Devsrealm\\TonicsRouterSystem\\Events\\OnRequestProcess;\nuse Devsrealm\\TonicsRouterSystem\\Interfaces\\TonicsRouterRequestInterceptorInterface;\n\nclass JsonContentTypeMiddleware implements TonicsRouterRequestInterceptorInterface\n{\n    public function handle(OnRequestProcess $request): void\n    {\n        $method = $request-\u003egetRequestMethod();\n        \n        // Check Content-Type for POST, PUT, PATCH requests\n        if (in_array($method, ['POST', 'PUT', 'PATCH'])) {\n            $contentType = $request-\u003egetHeaderByKey('CONTENT_TYPE');\n            \n            if (strpos($contentType, 'application/json') === false) {\n                http_response_code(415);\n                echo json_encode(['error' =\u003e 'Content-Type must be application/json']);\n                exit;\n            }\n        }\n        \n        // Continue to next interceptor or controller\n    }\n}\n```\n\n### Rate Limiting Interceptor\n\n```php\nnamespace App\\Middleware;\n\nuse Devsrealm\\TonicsRouterSystem\\Events\\OnRequestProcess;\nuse Devsrealm\\TonicsRouterSystem\\Interfaces\\TonicsRouterRequestInterceptorInterface;\n\nclass RateLimitMiddleware implements TonicsRouterRequestInterceptorInterface\n{\n    private int $maxRequests = 100;\n    private int $perMinutes = 1;\n    \n    public function handle(OnRequestProcess $request): void\n    {\n        $ip = $request-\u003egetHeaderByKey('REMOTE_ADDR');\n        $key = \"rate_limit:$ip\";\n        \n        // Get current count from cache (Redis, Memcached, etc.)\n        $count = Cache::get($key, 0);\n        \n        if ($count \u003e= $this-\u003emaxRequests) {\n            http_response_code(429);\n            echo json_encode(['error' =\u003e 'Too many requests. Please try again later.']);\n            exit;\n        }\n        \n        // Increment counter\n        Cache::increment($key);\n        Cache::expire($key, $this-\u003eperMinutes * 60);\n        \n        // Continue to next interceptor or controller\n    }\n}\n```\n\n### Logging Interceptor\n\n```php\nnamespace App\\Middleware;\n\nuse Devsrealm\\TonicsRouterSystem\\Events\\OnRequestProcess;\nuse Devsrealm\\TonicsRouterSystem\\Interfaces\\TonicsRouterRequestInterceptorInterface;\n\nclass LoggingMiddleware implements TonicsRouterRequestInterceptorInterface\n{\n    public function handle(OnRequestProcess $request): void\n    {\n        $method = $request-\u003egetRequestMethod();\n        $url = $request-\u003egetRequestURL();\n        $ip = $request-\u003egetHeaderByKey('REMOTE_ADDR');\n        $userAgent = $request-\u003egetUserAgent();\n        \n        // Log the request\n        error_log(sprintf(\n            \"[%s] %s %s from %s - %s\",\n            date('Y-m-d H:i:s'),\n            $method,\n            $url,\n            $ip,\n            $userAgent\n        ));\n        \n        // Continue to next interceptor or controller\n    }\n}\n\n// Apply to all routes\n$route-\u003egroup('/', function (Route $route) {\n    // All your routes here\n}, [LoggingMiddleware::class]);\n```\n\n### Multiple Interceptors Example\n\n```php\nnamespace App\\Middleware;\n\n// Chain multiple interceptors\n$route-\u003egroup('/admin', function (Route $route) {\n    $route-\u003eget('/dashboard', [AdminController::class, 'dashboard']);\n    $route-\u003eget('/users', [AdminController::class, 'users']);\n    $route-\u003epost('/users', [AdminController::class, 'createUser']);\n}, [\n    LoggingMiddleware::class,           // First: Log the request\n    AuthenticationMiddleware::class,     // Second: Check if user is logged in\n    AdminAuthorizationMiddleware::class, // Third: Check if user is admin\n    CsrfMiddleware::class               // Fourth: Validate CSRF token\n]);\n```\n\n## Best Practices\n\n### 1. Controller Organization\n\n**✅ DO:** Keep controllers focused and single-purpose\n```php\n// Good: Focused controller\nclass UserController {\n    public function show($id) { /* ... */ }\n    public function update($id) { /* ... */ }\n}\n\nclass UserProfileController {\n    public function show($id) { /* ... */ }\n    public function updateAvatar($id) { /* ... */ }\n}\n```\n\n**❌ DON'T:** Create god controllers\n```php\n// Bad: Too many responsibilities\nclass UserController {\n    public function show($id) { /* ... */ }\n    public function updateProfile($id) { /* ... */ }\n    public function uploadAvatar($id) { /* ... */ }\n    public function sendEmail($id) { /* ... */ }\n    public function generateReport($id) { /* ... */ }\n    // ... 50 more methods\n}\n```\n\n### 2. Return Values vs Echo\n\n**✅ DO:** Return values from controllers (especially for PSR-7)\n```php\npublic function show($id) {\n    $user = User::find($id);\n    return json_encode($user);  // ✅ Return\n}\n```\n\n**❌ DON'T:** Echo directly in controllers\n```php\npublic function show($id) {\n    $user = User::find($id);\n    echo json_encode($user);  // ❌ Echo (not PSR-7 compliant)\n}\n```\n\n### 3. Use Dependency Injection\n\n**✅ DO:** Inject dependencies via constructor\n```php\nclass UserController {\n    private UserRepository $userRepo;\n    \n    public function __construct(UserRepository $userRepo) {\n        $this-\u003euserRepo = $userRepo;  // ✅ Injected\n    }\n    \n    public function show($id) {\n        return json_encode($this-\u003euserRepo-\u003efind($id));\n    }\n}\n```\n\n**❌ DON'T:** Create dependencies inside methods\n```php\nclass UserController {\n    public function show($id) {\n        $userRepo = new UserRepository();  // ❌ Tight coupling\n        return json_encode($userRepo-\u003efind($id));\n    }\n}\n```\n\n### 4. Request Interceptor Best Practices\n\n**✅ DO:** Keep interceptors focused on one concern\n```php\n// Good: Single responsibility\nclass AuthenticationMiddleware implements TonicsRouterRequestInterceptorInterface {\n    public function handle(OnRequestProcess $request): void {\n        // Only handles authentication\n        if (!$this-\u003eisAuthenticated()) {\n            $this-\u003eredirectToLogin();\n        }\n    }\n}\n```\n\n**✅ DO:** Chain interceptors for multiple checks\n```php\n$route-\u003egroup('/admin', function (Route $route) {\n    // Routes here\n}, [\n    AuthenticationMiddleware::class,    // Check if logged in\n    AdminAuthorizationMiddleware::class, // Check if admin\n    CsrfMiddleware::class               // Validate CSRF\n]);\n```\n\n**❌ DON'T:** Create monolithic interceptors\n```php\n// Bad: Does too much\nclass MegaMiddleware implements TonicsRouterRequestInterceptorInterface {\n    public function handle(OnRequestProcess $request): void {\n        // Authentication\n        // Authorization\n        // CSRF validation\n        // Rate limiting\n        // Logging\n        // ... everything in one class\n    }\n}\n```\n\n### 5. Route Organization\n\n**✅ DO:** Group related routes\n```php\n// Good: Organized by feature\n$route-\u003egroup('/api/v1', function (Route $route) {\n    $route-\u003egroup('/users', function (Route $route) {\n        $route-\u003eget('', [UserController::class, 'index']);\n        $route-\u003eget(':id', [UserController::class, 'show']);\n        $route-\u003epost('', [UserController::class, 'store']);\n    });\n    \n    $route-\u003egroup('/posts', function (Route $route) {\n        $route-\u003eget('', [PostController::class, 'index']);\n        $route-\u003eget(':id', [PostController::class, 'show']);\n    });\n}, [CorsMiddleware::class, AuthMiddleware::class]);\n```\n\n**❌ DON'T:** Mix unrelated routes\n```php\n// Bad: No organization\n$route-\u003eget('/api/v1/users', [UserController::class, 'index']);\n$route-\u003eget('/admin/dashboard', [AdminController::class, 'dashboard']);\n$route-\u003eget('/api/v1/posts', [PostController::class, 'index']);\n$route-\u003eget('/public/about', [PageController::class, 'about']);\n```\n\n### 6. Error Handling\n\n**✅ DO:** Handle errors gracefully\n```php\npublic function show($id) {\n    try {\n        $user = User::findOrFail($id);\n        return json_encode(['user' =\u003e $user]);\n    } catch (NotFoundException $e) {\n        http_response_code(404);\n        return json_encode(['error' =\u003e 'User not found']);\n    } catch (Exception $e) {\n        http_response_code(500);\n        return json_encode(['error' =\u003e 'Internal server error']);\n    }\n}\n```\n\n**✅ DO:** Use proper HTTP status codes\n```php\n// 200 - OK\nreturn json_encode(['message' =\u003e 'Success']);\n\n// 201 - Created\nhttp_response_code(201);\nreturn json_encode(['user' =\u003e $newUser]);\n\n// 400 - Bad Request\nhttp_response_code(400);\nreturn json_encode(['error' =\u003e 'Invalid input']);\n\n// 401 - Unauthorized\nhttp_response_code(401);\nreturn json_encode(['error' =\u003e 'Authentication required']);\n\n// 403 - Forbidden\nhttp_response_code(403);\nreturn json_encode(['error' =\u003e 'Access denied']);\n\n// 404 - Not Found\nhttp_response_code(404);\nreturn json_encode(['error' =\u003e 'Resource not found']);\n\n// 500 - Internal Server Error\nhttp_response_code(500);\nreturn json_encode(['error' =\u003e 'Server error']);\n```\n\n### 7. Input Validation\n\n**✅ DO:** Validate input data\n```php\npublic function store(RequestInput $input) {\n    $data = $input-\u003efromPost();\n    \n    // Validate required fields\n    if (!$data-\u003ehasValue('name')) {\n        http_response_code(400);\n        return json_encode(['error' =\u003e 'Name is required']);\n    }\n    \n    if (!$data-\u003ehasValue('email')) {\n        http_response_code(400);\n        return json_encode(['error' =\u003e 'Email is required']);\n    }\n    \n    // Validate email format\n    $email = $data-\u003eretrieve('email');\n    if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {\n        http_response_code(400);\n        return json_encode(['error' =\u003e 'Invalid email format']);\n    }\n    \n    // Create user\n    $user = User::create([\n        'name' =\u003e $data-\u003eretrieve('name'),\n        'email' =\u003e $email\n    ]);\n    \n    return json_encode(['user' =\u003e $user]);\n}\n```\n\n### 8. Use Response Helper (PSR-7)\n\n**✅ DO:** Use response adapter for clean code\n```php\nuse Devsrealm\\TonicsRouterSystem\\Adapter\\Psr7ResponseAdapter;\n\npublic function show(Psr7ResponseAdapter $response, $id) {\n    $user = User::find($id);\n    \n    if (!$user) {\n        return $response-\u003ehttpResponseCode(404)\n            -\u003ejson(['error' =\u003e 'User not found']);\n    }\n    \n    return $response-\u003ejson(['user' =\u003e $user]);\n}\n```\n\n### 9. Security Best Practices\n\n**✅ DO:** Sanitize user input\n```php\npublic function search(RequestInput $input) {\n    $query = $input-\u003efromGet()-\u003eretrieve('q', '');\n    \n    // Sanitize input\n    $query = htmlspecialchars($query, ENT_QUOTES, 'UTF-8');\n    \n    $results = Search::query($query);\n    return json_encode(['results' =\u003e $results]);\n}\n```\n\n**✅ DO:** Use HTTPS for sensitive operations\n```php\nclass SecureMiddleware implements TonicsRouterRequestInterceptorInterface {\n    public function handle(OnRequestProcess $request): void {\n        if (!$request-\u003eisSecure()) {\n            header('Location: https://' . $request-\u003egetHost() . $request-\u003egetRequestURL());\n            exit;\n        }\n    }\n}\n```\n\n**✅ DO:** Validate CSRF tokens\n```php\nclass CsrfMiddleware implements TonicsRouterRequestInterceptorInterface {\n    public function handle(OnRequestProcess $request): void {\n        if (in_array($request-\u003egetRequestMethod(), ['POST', 'PUT', 'DELETE'])) {\n            $token = $_POST['csrf_token'] ?? '';\n            \n            if (!$this-\u003evalidateCsrfToken($token)) {\n                http_response_code(403);\n                echo json_encode(['error' =\u003e 'Invalid CSRF token']);\n                exit;\n            }\n        }\n    }\n    \n    private function validateCsrfToken(string $token): bool {\n        return isset($_SESSION['csrf_token']) \u0026\u0026 \n               hash_equals($_SESSION['csrf_token'], $token);\n    }\n}\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftonics-apps%2Ftonics-router-system","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftonics-apps%2Ftonics-router-system","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftonics-apps%2Ftonics-router-system/lists"}