Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

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 libraries

This 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.

Thanks for reading!

Donate to the site — we work hard for you.