Application structure
A small site fits in one file. But with dozens of pages the code has to be spread across files and folders so that every part is easy to find. Almost every framework follows the same scheme — let us walk through it with a simple example.
The examples in this lesson need a web server and several files, so they cannot be run here.
Including files
require brings in code from another file. If the file is missing, the program stops with an error:
<?php
// index.php
require __DIR__ . '/functions.php';
echo formatPrice(250);require_once includes a file only once, even if the line is reached again. There is also include — it only raises a warning when the file is missing, so require is used for code.
The entry point
Old sites had a file for every page: about.php, contacts.php. Today every request is routed to one file — the entry point. It lives in the public folder, and the rest of the code sits above it, where the browser has no access:
project/
public/
index.php — the entry point
src/
Controllers/ — request handlers
Models/ — working with data
views/ — page templates
vendor/ — Composer librariesThis way a visitor cannot open a file with settings or classes directly.
The router
The entry point looks at the address and decides which code to call:
<?php
// public/index.php
require __DIR__ . '/../vendor/autoload.php';
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$routes = [
'/' => [App\Controllers\HomeController::class, 'index'],
'/posts' => [App\Controllers\PostController::class, 'index'],
];
if (!isset($routes[$path])) {
http_response_code(404);
exit('Page not found');
}
[$class, $method] = $routes[$path];
echo (new $class())->$method();An "address to handler" pair is called a route.
The controller
A controller accepts a request, gets the data and passes it to a template:
<?php
// src/Controllers/PostController.php
namespace App\Controllers;
use App\Models\Post;
class PostController
{
public function index(): string
{
$posts = Post::all();
return render('posts/index', ['posts' => $posts]);
}
}The controller itself contains neither SQL queries nor HTML.
The model
A model is responsible for data: it knows how to fetch and save it.
<?php
// src/Models/Post.php
namespace App\Models;
class Post
{
public static function all(): array
{
return database()->query('SELECT id, title FROM posts ORDER BY id DESC')->fetchAll();
}
}The template
A template is HTML with small PHP inserts for printing data:
<?php
// views/posts/index.php
?>
<h1>Posts</h1>
<ul>
<?php foreach ($posts as $post): ?>
<li><?= htmlspecialchars($post['title']) ?></li>
<?php endforeach; ?>
</ul><?= ... ?> is the short form of echo. Templates use the alternative loop syntax with a colon and endforeach: it reads better among HTML.
Model, view, controller
This split is called MVC — Model, View, Controller. Each part changes independently: a designer edits the template without touching the queries, and a change of database does not affect the pages.
Common mistake
Do not mix everything in one file: a database query, form validation and HTML all tangled together. Such code can be neither tested nor reused. If SQL turns up in a template or HTML in a model, some code is in the wrong place.