Skip to main content

Overview

Understanding the request lifecycle is crucial for building applications with S-PHP. This guide walks you through every stage of an HTTP request, from the moment it enters your application until a response is sent back to the user.

Lifecycle Diagram

Stage 1: Entry Point

Every request enters through public/index.php, which serves as the single entry point for your application.
public/index.php
require_once '../Sphp/function.php';
require_once '../vendor/autoload.php';

spl_autoload_register(function ($class) {
    $class = ltrim($class, '\\');
    $base_dir = __DIR__ . '/../';
    $class_path = str_replace('\\', '/', $class) . '.php';
    $file = $base_dir . $class_path;

    if (file_exists($file)) {
        require_once $file;
    } else {
        exit("Autoloader Error: Unable to load class '$class'. Expected file at $file not found.");
    }
});
The autoloader converts namespaces to file paths. For example, App\Controllers\HomeController maps to app/Controllers/HomeController.php.

Stage 2: Route Detection

S-PHP determines whether the request is for the API or web routes:
public/index.php
$requestUri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);

if (strpos($requestUri, '/api') === 0) {
    require_once __DIR__ . '/../app/router/api.php';
} else {
    require_once __DIR__ . '/../app/router/web.php';
}
Routes without the /api prefix load app/router/web.php:
// Handles: /, /dashboard, /users/123, etc.
$router->get('/', HomeController::class, 'index');
$router->get('/users/{id}', UserController::class, 'show');

Stage 3: Router Initialization

The router file creates a Router instance and defines all routes:
app/router/web.php
use App\Controllers\HomeController;
use App\Middleware\AuthMiddleware;
use Sphp\Core\Router;

$router = new Router();

// Public routes
$router->get('/', HomeController::class, 'index');
$router->post('/login', HomeController::class, 'login');

// Protected routes
$router->get('/dashboard', HomeController::class, 'dashboard', AuthMiddleware::class);

// Dispatch the router
$router->dispatch();

Stage 4: Route Matching

The dispatch() method matches the incoming request to a defined route:
Sphp/Core/Router.php
public function dispatch()
{
    // Get request details
    $method = $_SERVER['REQUEST_METHOD'];  // GET, POST, etc.
    $route = $_SERVER['REQUEST_URI'];     // /users/123

    // Remove query strings
    $route = strtok($route, '?');

    $matchedRoute = null;
    $params = [];

    // Match dynamic routes
    if ($method == 'GET') {
        foreach ($this->getRoutes as $definedRoute => $config) {
            $matchedRoute = $this->matchDynamicRoute($definedRoute, $route, $params);
            if ($matchedRoute) {
                // Track URLs for redirects
                $currentUrl = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http");
                $currentUrl .= "://{$_SERVER['HTTP_HOST']}{$_SERVER['REQUEST_URI']}";

                $_SESSION['previous_url'] = $_SESSION['current_url'] ?? '/'; 
                $_SESSION['current_url'] = $currentUrl;
                $prev_url = $_SESSION['previous_url'] == $_SESSION['current_url'] ? "/" : $_SESSION['previous_url'];
                
                $this->handle_request($config, $params, $prev_url);
                return;
            }
        }
    }
    
    // No route found
    View::render('404.html');
}

Dynamic Route Matching

The router converts route patterns to regex for matching:
// Route pattern: /users/{id}
// Regex pattern: /users/([^/]+)
// Matches: /users/123, /users/abc, etc.

private function matchDynamicRoute($definedRoute, $currentRoute, &$params)
{
    // Convert {id} placeholders to regex
    $definedRoutePattern = preg_replace('/\{[a-zA-Z0-9_]+\}/', '([^/]+)', $definedRoute);
    $definedRoutePattern = str_replace('/', '\\/', $definedRoutePattern);

    if (preg_match('/^' . $definedRoutePattern . '$/', $currentRoute, $matches)) {
        array_shift($matches); // Remove full match
        $params = $matches;    // Extract parameters
        return true;
    }

    return false;
}
1

Pattern Conversion

The route pattern /users/{id} is converted to the regex /users/([^/]+).
2

Regex Matching

The regex is tested against the current URL path.
3

Parameter Extraction

If matched, captured groups become the $params array.

Stage 5: Session Tracking

For GET requests, S-PHP tracks the current and previous URLs:
$_SESSION['previous_url'] = $_SESSION['current_url'] ?? '/'; 
$_SESSION['current_url'] = $currentUrl;
This is useful for:
  • Redirecting users back after login
  • “Return to previous page” functionality
  • Breadcrumb navigation

Stage 6: Middleware Execution

If the route has middleware, it’s executed before the controller:
Sphp/Core/Router.php
private function handle_request($route, $params = [], $previous_url = '/')
{
    // Middleware handling
    $middleware = $route['middelware'];
    if ($middleware) {
        $response_from_middleware = $this->handleMiddleware($middleware);
        
        if (is_bool($response_from_middleware)) {
            if (!$response_from_middleware) {
                View::render('403.html');
                exit;
            }
        } else {
            redirect($previous_url);
        }
    }

    $this->callController($route, $params);
}

Middleware Flow

// Middleware returns true
public function handle()
{
    if (Auth::check()) {
        return true; // Continue to controller
    }
}

Stage 7: POST Data Sanitization

For POST requests, S-PHP automatically sanitizes input:
Sphp/Core/Router.php
elseif ($method == 'POST') {
    foreach ($this->postRoutes as $definedRoute => $config) {
        $matchedRoute = $this->matchDynamicRoute($definedRoute, $route, $params);
        if ($matchedRoute) {
            // Sanitize HTML (except 'content' field)
            if(!$_POST['content']) {
                $_POST = sanitizeHtml($_POST);
            }
            $this->handle_request($config, $params);
            return;
        }
    }
}
The content field is excluded from sanitization to allow rich text editors. Always sanitize this field manually if needed.

Stage 8: Controller Instantiation

The router instantiates the controller and calls the method:
Sphp/Core/Router.php
private function callController($route, $params = [])
{
    $controllerName = $route['controller'];
    $methodName = $route['method'];

    if (class_exists($controllerName)) {
        $controller = new $controllerName();
        if (method_exists($controller, $methodName)) {
            // Pass parameters to the controller method
            call_user_func_array([$controller, $methodName], $params);
        } else {
            echo "Method $methodName not found in $controllerName.";
        }
    } else {
        echo "Controller $controllerName not found.";
    }
}

Controller Initialization

When instantiated, controllers automatically set up database and config:
Sphp/Core/Controller.php
class Controller
{
    public $env;
    public $db;

    public function __construct()
    {
        $this->env = require('../app/config/config.php');
        $this->db = new Database($this->env);
    }
}

Stage 9: Controller Method Execution

The controller method processes the request:
namespace App\Controllers;

use Sphp\Core\Controller;
use Sphp\Core\View;
use App\Models\User;

class UserController extends Controller
{
    public function show($id)
    {
        // Access database via inherited $this->db
        $user = $this->db->query(
            "SELECT * FROM users WHERE id = ?", 
            [$id]
        );
        
        // Or use a model
        $userModel = new User();
        $user = $userModel->findByID($id);
        
        // Render view with data
        View::render('user/profile.php', [
            'user' => $user
        ]);
    }
}

Stage 10: Model Interaction

Controllers use models to interact with the database:
// Using base model methods
$userModel = new User();

// Query methods
$users = $userModel->select(['id', 'name', 'email']);
$user = $userModel->findByID($id);

// Data manipulation
$userModel->create($_POST);
$userModel->update($_POST, $id);
$userModel->delete($id);

Stage 11: View Rendering

The View class renders the template with data:
Sphp/Core/View.php
public static function render($filename, $data = [])
{
    extract($data); // Extract variables

    $viewPath = '../app/views/' . $filename;
    if (file_exists($viewPath)) {
        // Load file content
        $content = file_get_contents($viewPath);

        // Process @layout directives
        $content = preg_replace_callback("/@layout\('([^']+)'(?:\s*,\s*(\[.*?\]))\)/", 
            function ($matches) { /* ... */ }, $content);

        // Process @component directives
        $content = preg_replace_callback("/@component\('([^']+)'(?:\s*,\s*(\$[\w]+))\)/", 
            function ($matches) use ($data) { /* ... */ }, $content);

        // Evaluate PHP
        eval ('?>' . $content);
    } else {
        require('../app/views/404.html');
    }
}

View Processing

1

Data Extraction

The $data array is extracted into individual variables.
2

Layout Processing

@layout() directives are replaced with layout content.
3

Component Processing

@component() directives are replaced with component content.
4

PHP Evaluation

The resulting PHP code is evaluated and rendered.

Stage 12: Response

The rendered HTML is sent back to the client, completing the request lifecycle.

Complete Example

Here’s a complete request flow example:
1

User visits /users/123

GET /users/123 HTTP/1.1
Host: example.com
2

Request enters index.php

// Load autoloader and functions
// Determine route type (web)
// Load app/router/web.php
3

Router matches route

$router->get('/users/{id}', UserController::class, 'show', AuthMiddleware::class);
// Extracts $params = ['123']
4

Middleware executes

AuthMiddleware::handle() // Returns true
5

Controller instantiated

$controller = new UserController();
// $this->db and $this->env initialized
6

Method called

$controller->show('123');
7

Model queries database

$user = $userModel->findByID('123');
8

View rendered

View::render('user/profile.php', ['user' => $user]);
9

HTML response sent

<html>
  <body>
    <h1>User Profile: John Doe</h1>
    ...
  </body>
</html>

Performance Considerations

The autoloader only loads classes when needed. Keep your file structure aligned with namespaces for optimal performance.
Static routes are faster than dynamic routes. Place frequently accessed static routes first in your route files.
Each middleware adds processing time. Only attach middleware to routes that need it.
Controllers reuse the same database connection. Close connections explicitly if you have long-running processes.
Consider caching rendered views for pages that don’t change frequently.

Next Steps

MVC Pattern

Learn about the MVC architecture

Routing

Master the routing system

Middleware

Implement request filtering

Build docs developers (and LLMs) love