Design Patterns And Data Architecture
Front Controller Pattern
The front controller pattern gives a PHP web application one controlled entry point for HTTP requests. Instead of exposing many PHP files directly under the public web root, the web server sends dynamic requests to one script, commonly public/index.php. That script bootstraps the application, creates or obtains the framework kernel, routes the request, runs middleware or listeners, and sends the response.
This pattern is one reason modern PHP applications feel different from older collections of standalone scripts. In a script-per-page application, /login.php, /account.php, and /orders.php might each include configuration, start sessions, connect to the database, check authentication, and render output. With a front controller, shared request setup happens once at the boundary. The router chooses the controller or handler for the matched path.
The front controller is not the same as MVC. MVC separates controller, model, and view responsibilities after the request reaches the application. The front controller controls how the request enters the application in the first place. Most Laravel and Symfony projects use both ideas: the web server points at a public directory, index.php starts the framework, and the framework dispatches to controllers.
Why One Entry Point Helps
A single entry point makes cross-cutting behavior consistent. Security headers, error handling, session start, request parsing, authentication middleware, routing, dependency injection, logging, and response sending can be applied in one predictable pipeline.
It also improves deployment safety. The web server can expose only the public/ directory, while application source, environment files, Composer dependencies, storage, tests, and configuration remain outside the document root. If the server is misconfigured to expose the whole project, users may be able to download source files or secrets. A front-controller architecture works best when paired with a correct public root.
The pattern also supports clean URLs. A route like /products/123 can be handled by public/index.php without a visible products.php file. The router decides what code handles the path.
A Minimal Front Controller Shape
A real framework front controller is small because most work is delegated to framework bootstrapping code. This simplified example shows the shape without requiring a framework:
<?php
declare(strict_types=1);
function handle(string $method, string $path): string
{
return match ([$method, $path]) {
['GET', '/'] => 'Home page',
['GET', '/health'] => 'OK',
default => 'Not found',
};
}
$method = 'GET';
$path = '/health';
echo handle($method, $path) . PHP_EOL;
// Prints:
// OK
In a real request, $method and $path would come from the server environment or framework request object. The front controller would not contain every route in a match expression forever. It would hand the request to a router or kernel.
The important idea is that requests pass through one boundary before application code runs.
Public Root And Project Root
A front-controller application should distinguish project root from public root. The project root contains source code, Composer files, configuration, tests, migrations, storage, and private application files. The public root contains assets that may be served directly and the front controller script.
A typical shape is:
project/
app/
config/
storage/
vendor/
public/
index.php
css/
images/
The web server document root should be project/public, not project. This prevents direct browser access to vendor/, .env, source files, migrations, and other internal files. The front controller can still load application code from outside the public directory because PHP runs on the server with filesystem access.
This is a common production failure. If a PHP application works locally only when the document root is the project root, fix the path assumptions rather than exposing private files.
Rewrites And Static Files
The web server must decide which requests go directly to static files and which go to the front controller. Static assets such as CSS, images, and compiled JavaScript can be served directly when they exist. Application routes should be rewritten to index.php.
The exact configuration differs by server. Apache commonly uses mod_rewrite rules or framework-provided .htaccess files. Nginx often uses try_files to serve existing files or fall back to /index.php. Caddy and FrankenPHP have their own configuration patterns. The principle is the same: serve real public files directly, route everything else through the application entry point.
Do not rewrite every request blindly if it prevents real static assets from being served efficiently. Do not serve arbitrary files from storage directories just because the URL path matches. Public assets and private storage need separate rules.
Bootstrap Responsibilities
The front controller should be small, but it has important responsibilities:
- load Composer autoloading
- load environment or configuration through the framework's supported mechanism
- create the application container or kernel
- convert server globals into a request object where the framework uses one
- dispatch the request through router, middleware, and controllers
- send the response
- terminate or run after-response hooks where the framework supports them
It should not contain business logic. If public/index.php knows how to cancel subscriptions, calculate discounts, or query reports, the application boundary has leaked.
It should also avoid environment-specific branching that belongs in deployment configuration. The front controller can select bootstrap files, but it should not become a long list of production, staging, and developer machine special cases.
Error Handling And Early Failures
Because every dynamic request enters through the front controller, early failures need clear handling. Composer autoloading might be missing. Configuration might be invalid. The framework kernel might fail before the normal exception handler is registered.
In development, showing detailed errors can help. In production, early failures should produce a safe generic response and useful logs. Never display environment variables, database credentials, stack traces, or full file paths to public users.
A mature application also has a health endpoint that exercises the runtime enough to prove the application can boot. A static OK file proves the web server is alive, but it does not prove Composer dependencies, configuration, or the PHP application can run.
Security Boundaries
The front controller pattern supports security, but it does not guarantee it. The web server must still be configured correctly. PHP files in private directories should not be downloadable. Dotfiles and backup files should not be exposed. Uploaded files should not execute as PHP. Static file rules should not let a user traverse into private storage.
The application should also apply authentication and authorization in the request pipeline or controller layer, not in scattered public scripts. One entry point makes it easier to ensure every route passes through the same middleware stack, but route exclusions still need review. Public routes such as login, password reset, webhooks, and health checks should be intentional.
When debugging a production routing issue, avoid fixes that expose more of the filesystem. A 404 caused by rewrite configuration is a server-routing problem; pointing the document root at the project root may appear to fix the route while creating a serious exposure.
Relationship To Frameworks
Laravel's public entry point is public/index.php, which boots the application and handles the incoming request through the framework. Symfony's public front controller also lives under public/ and boots the kernel. Slim, Mezzio, and many custom applications follow the same pattern.
Frameworks hide much of the front-controller pipeline behind abstractions, but the deployment requirement remains visible. The server must send dynamic requests to the correct entry file. If PHP-FPM receives the wrong SCRIPT_FILENAME, or the document root points to the wrong directory, the application may return 404s, 502s, or expose files.
Understanding the pattern helps diagnose those failures. You can ask: did the request reach the public root, did the rewrite fall back to index.php, did PHP execute the right script, did the framework boot, and did the router match a route?
Testing And Verification
Verify the front controller at several levels. Locally, request a known route and a missing route. Confirm that /health or another simple dynamic endpoint reaches the application, not only the web server. Confirm that a real static asset is served directly if that is the intended server behavior.
Check that private files are not reachable. Requests for /.env, /composer.json, /vendor/autoload.php, /storage/logs/app.log, and source paths should not reveal file contents. The exact status code may differ, but the body must not expose secrets or code.
Check URL generation and redirects behind proxies. A front-controller application often sits behind load balancers, reverse proxies, or TLS terminators. If trusted proxy settings are wrong, generated URLs may use http instead of https, wrong hosts, or client IPs from the proxy rather than the user.
For automated tests, include routing tests, middleware tests, and one deployment smoke test that exercises the real web-server-to-PHP path. A framework feature test is useful, but it may bypass the web server rewrite layer that fails in production.
When Not To Overcomplicate It
A small educational script does not need a front-controller framework before it prints a form. The pattern becomes important when the application has multiple routes, shared middleware, authentication, templates, assets, and production deployment.
Do not build a custom framework just to use the pattern. Established frameworks already solve routing, request objects, middleware, response sending, error handling, and proxy support. If a custom front controller is used, keep it small and well tested.
The front controller should make the application easier to reason about: one public entry, one routing pipeline, one place to bootstrap, and a clear split between public files and private code.
What To Check
Before moving on, make sure you can:
- explain why modern PHP applications use
public/index.phpas a single entry point - distinguish project root from public document root
- describe how rewrites route dynamic requests to the front controller
- keep business logic out of the front controller
- identify static-file, private-file, and uploaded-file risks
- trace a request from web server to PHP script to router to controller
- verify that private files are not publicly downloadable
- test the real deployment path, not only in-process framework routing
After this lesson, you should be able to look at a PHP application's server configuration and directory structure and decide whether requests enter through a safe, deliberate front controller or through accidental direct file exposure.
Practice
Task: Trace Request Entry
A PHP application receives GET /orders/123 in production. The web server document root is intended to be public/, and dynamic requests should reach public/index.php.
Write the request path from web server to controller.
Requirements
Include static-file checking, rewrite fallback, PHP script execution, framework bootstrap, router matching, controller dispatch, and response sending.
Check your work
The answer should distinguish the front controller from the final MVC controller.
Show solution
The web server receives GET /orders/123 under the public/ document root. It first checks whether a real public file exists at that path. Because there is no static asset named orders/123, the rewrite rule falls back to public/index.php.
PHP executes index.php. The front controller loads Composer autoloading, boots the framework or application kernel, creates a request object from server data, and dispatches the request through middleware and routing.
The router matches GET /orders/{id} and dispatches to the order controller or handler. That MVC controller handles HTTP-specific work, calls application behavior, chooses a response, and returns it. The framework sends the response headers and body back through PHP and the web server.
The front controller is public/index.php; the MVC controller is the route-specific handler selected later by the router.
Task: Review Public Root Configuration
A deployment points the web server document root at the project root because routes did not work when it pointed at public/. After the change, /vendor/autoload.php and /composer.json return file contents in the browser.
Explain the defect and propose a safe fix.
Requirements
Cover document root, rewrite configuration, private files, and why exposing the project root is not an acceptable route fix.
Check your work
The answer should keep the public root as public/ and fix routing without exposing source or dependency files.
Show solution
The defect is that the deployment exposes the project root instead of the public root. That makes private application files, Composer metadata, source paths, and possibly environment files reachable over HTTP. It is a security issue, not a harmless routing workaround.
The document root should be changed back to project/public. The web server rewrite rules should then be fixed so existing public files are served directly and all other dynamic routes fall back to public/index.php. For Nginx this is commonly a try_files style rule; for Apache it may be framework .htaccess or virtual-host rewrite configuration.
Requests for /vendor/autoload.php, /composer.json, /.env, source directories, and storage logs should not return file contents. They should be outside the public document root or explicitly denied. The correct fix keeps private files private and makes the front controller receive application routes.
Task: Design A Front Controller Flow
Design a small custom PHP application front-controller flow for a site with HTML pages, JSON API routes, static assets, and uploaded private documents.
Requirements
Describe:
- what belongs in
public/ - what the front controller bootstraps
- how static assets are served
- how private uploads are protected
- where authentication middleware runs
- how you would smoke test deployment
Check your work
The design should avoid putting business logic in public/index.php and should avoid serving private uploads directly.
Show solution
The public/ directory should contain index.php and public assets such as compiled CSS, JavaScript, images, and favicon files. Application source, configuration, Composer dependencies, templates, logs, cache, tests, and private uploads should live outside the public document root.
public/index.php should load Composer autoloading, bootstrap configuration, create the application container or kernel, convert server input into a request object, dispatch through middleware and routing, and send the response. It should not contain page-specific business logic.
The web server should serve real static assets directly from public/ and rewrite other paths to index.php. Private uploads should be stored outside public/ and delivered through an authorized controller that checks permissions before streaming or redirecting to a protected storage mechanism.
Authentication middleware should run after the request enters the application and before protected route handlers. Public routes such as login, health checks, and selected webhooks should be explicit exceptions.
Deployment smoke tests should request a dynamic HTML page, a JSON API route, a real static asset, a missing route, and private file paths such as /.env and /vendor/autoload.php. The tests should prove both routing and non-exposure.