The entry point — public/index.php
Concept
public/index.php is the entry point for every HTTP request in your framework. In web server terminology this is called the "front controller" pattern: a single PHP file receives all requests, and routes them internally rather than having one PHP file per URL. This is in contrast to the old PHP approach where about.php, contact.php, and home.php each lived in the web root.
The front controller gives you three essential guarantees. First, you can run bootstrap code (error handlers, autoloading, environment loading) exactly once before any request processing. Second, you have a single place to catch uncaught exceptions and return a clean 500 response instead of letting PHP dump a stack trace. Third, your URL structure is completely decoupled from your file structure — /users/123/posts does not require a file at public/users/123/posts.php.
The Nginx configuration for this pattern uses try_files $uri $uri/ /index.php?$query_string; — try the exact URI first (for static assets like CSS and images), then try it as a directory, then fall back to index.php. Apache uses a RewriteRule in .htaccess to achieve the same thing.
The entry point must be as thin as possible. Its only job is to: require the Composer autoloader, instantiate the Application, pass the Application to the HTTP Kernel, pass the current HTTP request to the Kernel, and emit the response. Everything else belongs in a bootstrap file or a service provider. A common mistake is to put environment loading, error handler registration, or service provider registration directly in index.php — this makes those operations hard to replicate in CLI commands and tests.
Laravel's public/index.php is one of the shortest files in the entire project. It does exactly these steps in under 15 lines. Every framework should aspire to the same brevity at the entry point.
Code Example
<?php
declare(strict_types=1);
/**
* public/index.php — The Front Controller
*
* This file is the ONLY PHP file the web server executes directly.
* Nginx config: root /var/www/lumen/public;
* try_files $uri $uri/ /index.php?$query_string;
*
* Apache .htaccess:
* RewriteEngine On
* RewriteCond %{REQUEST_FILENAME} !-d
* RewriteCond %{REQUEST_FILENAME} !-f
* RewriteRule ^ index.php [L]
*/
// -----------------------------------------------------------------------
// 1. Enforce a time limit — never let a web request run forever
// -----------------------------------------------------------------------
ini_set('max_execution_time', '30');
// -----------------------------------------------------------------------
// 2. Load Composer's PSR-4 autoloader.
// __DIR__ is public/, so we go one level up to reach the project root.
// -----------------------------------------------------------------------
require_once __DIR__ . '/../vendor/autoload.php';
// -----------------------------------------------------------------------
// 3. Bootstrap the Application.
// The Application constructor registers base bindings and sets up paths.
// We pass the project root, not the public/ directory.
// -----------------------------------------------------------------------
$app = new \Lumen\Foundation\Application(
basePath: dirname(__DIR__)
);
// -----------------------------------------------------------------------
// 4. Run the bootstrap pipeline (env loading, config, providers, etc.)
// This is implemented in the next lessons; for now it's a no-op call.
// -----------------------------------------------------------------------
$bootstrap = new \Lumen\Foundation\Bootstrap($app);
$bootstrap->run();
// -----------------------------------------------------------------------
// 5. Create the HTTP Kernel and handle the request.
// The Kernel is covered in fw-01-008, but the interface is stable:
// it accepts a Request and returns a Response.
// -----------------------------------------------------------------------
$kernel = $app->getContainer()->make(\Lumen\Http\KernelInterface::class);
$request = \Lumen\Http\Request::capture(); // from $_SERVER, $_GET, $_POST
$response = $kernel->handle($request);
// -----------------------------------------------------------------------
// 6. Emit the response headers and body to the client.
// -----------------------------------------------------------------------
$response->send();
// -----------------------------------------------------------------------
// 7. Terminate — give middleware a chance to run after response is sent.
// (e.g., write access logs, commit sessions)
// -----------------------------------------------------------------------
$kernel->terminate($request, $response);Interview Q&A
Q: What is the front controller pattern and why is it used in frameworks?
The front controller is an architectural pattern where a single entry point handles all requests to an application. In PHP frameworks, public/index.php is that controller. It provides a centralised location for cross-cutting concerns: authentication checks, error handling, request logging, CORS headers. Without it, each PHP file in the old "file-per-page" model would need to repeat those concerns or require a shared bootstrap file. The front controller also enables clean URL routing — example.com/users/123 maps to a controller action, not a file path. Laravel, Symfony, Slim, and every modern PHP framework use this pattern. It is enforced at the web server level: only public/ is accessible.
Q: Why do we pass dirname(__DIR__) to the Application constructor rather than just __DIR__?
__DIR__ inside public/index.php is the public/ directory. The Application needs to resolve paths like config/app.php, storage/logs/, and vendor/autoload.php, which all live in the project root — one level above public/. Using dirname(__DIR__) gives us the correct base path. A common mistake is passing __DIR__ and then wondering why $app->configPath() returns public/config instead of config. This is why Laravel's index.php uses dirname(__DIR__) too.
Q: What should and should not live in index.php?
index.php should only: load the autoloader, instantiate the Application, run the bootstrap sequence, handle the request, emit the response, and call terminate(). It should NOT contain: inline service provider registrations, environment variable loading logic, direct database connections, routing definitions, or business logic. The rule of thumb is that index.php should be copy-pasteable into a different project with zero modification — only the bootstrap class and kernel implementation differ between projects. This level of thinness ensures that the CLI entry point (bin/console or Artisan) can share 100% of the bootstrap logic without duplicating anything from index.php.