Request Lifecycle

Browser
  │
  ├─ .htaccess          rewrites all .php requests to __flexagon.php
  │
  ├─ __flexagon.php ──► flexagon.php ──► Bootstrap.php
  │                                        │
  │                                        ├─ defines constants (PROJECT_ROOT, PUBLIC_ROOT, _FLEXAGON_ROOT ...)
  │                                        ├─ registers the autoloader
  │                                        ├─ loads _Const.php / _Global.php / _Config.php
  │                                        └─ parses the URL → _Global::$URL_PARAM
  │
  ├─ public/_entry.php      ① request-start hook (site title, shared headers, etc.)
  ├─ [automatic session start] ② when $SESSION_AUTO_START is true
  ├─ public/_prepare.php    ③ post-session hook (authorization checks, etc.)
  └─ public/_router.php     ④ dispatch — default implementation uses TemplateLoader::content()
                                 → executes public/<URL path>.php

The three hooks run at distinct stages of the request lifecycle.

File Stage Purpose
_entry.php Before session Site metadata, response headers, locale
_prepare.php After session Authentication/authorization checks, shared data loading
_router.php Dispatch Executes the page. Replace this file when custom routing is needed

URL Mapping Rules

Request URL Executed file
/ public/index.php
/hello public/hello.php
/user/profile public/user/profile.php
/user/ public/user/index.php
/user/profile?id=3 public/user/profile.php

Request paths are normalized first by HttpUtil::normalizePath(). It removes NUL bytes, converts backslashes to /, and removes empty segments, ., and .., preventing traversal to parent directories.

TemplateLoader then resolves the final path with realpath() and includes it only after verifying that it remains inside public/ (or _Template/). Paths outside that boundary are rejected with a 404. This protection is enforced by the framework itself rather than relying solely on web-server path normalization.

The parsed result is stored in _Global::$URL_PARAM (UrlParamsModel).

_Global::$URL_PARAM->filePath;       // 'user/profile'
_Global::$URL_PARAM->filePathArray;  // ['user', 'profile']
_Global::$URL_PARAM->filePathEnd;    // 'profile'
_Global::$URL_PARAM->params;         // '?id=3'

Custom Routing

To dispatch requests manually instead of using file mapping, replace public/_router.php.

<?php
// public/_router.php
$path = _Global::$URL_PARAM->filePath;

if (str_starts_with($path, 'api/')) {
    header('Content-Type: application/json');
    include_once PUBLIC_ROOT . '/api/_dispatch.php';
} else {
    TemplateLoader::content();
}