Пока мы проверяли путь прямо в index.php и выводили заголовок там же. Когда появятся список, форма, обработчик и страница книги, один файл станет трудно читать. Разделим приложение на маршрутизатор и шаблоны, оставив одну публичную точку входа.

Публичная папка — единственная точка входа

Веб-сервер должен отдавать файлы только из public/. Классы, JSON-каталог, настройки и зависимости Composer хранятся рядом или выше, но не по публичному URL. Это простая граница: браузеру доступны только файлы и ответы, которые приложение решило показать.

Распознаём два маршрута

Для небольшого приложения можно начать с проверки пары метод/путь. В public/index.php:

<?php
declare(strict_types=1);

require dirname(__DIR__) . '/vendor/autoload.php';

$method = $_SERVER['REQUEST_METHOD'] ?? 'GET';
$path = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH) ?? '/';

if ($method === 'GET' && $path === '/books') {
    $books = [];
    require dirname(__DIR__) . '/templates/books.php';
    exit;
}

http_response_code(404);
require dirname(__DIR__) . '/templates/404.php';

Имя шаблона в коде фиксировано. Не используйте ввод из URL как часть пути для require: адрес вида ?page=... не должен выбирать произвольный файл на сервере.

Экранируйте текст в шаблоне

Создайте templates/books.php. Функция e() экранирует обычный текст для HTML-контекста:

<?php
function e(string $value): string
{
    return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
?>

<!doctype html>
<html lang="ru">
<head>
    <meta charset="utf-8">
    <title>Мои книги</title>
</head>
<body>
    <h1>Мои книги</h1>
    <ul>
        <?php foreach ($books as $book) : ?>
            <li><?php echo e($book->title); ?></li>
        <?php endforeach; ?>
    </ul>
</body>
</html>

Если название содержит строку <script>, браузер покажет её текстом, а не выполнит как разметку. Экранируйте при выводе, потому что безопасная строка зависит от контекста: текст, атрибут, URL и JavaScript требуют разных правил.

Шаблон не должен загружать данные

Маршрут подготовил переменную $books, а шаблон отвечает за HTML. Если шаблон сам читает файл или выполняет SQL-запрос, отображение и хранение снова смешаются. Разделение помогает понять, где искать ошибку: данные не пришли или разметка вывела их неверно.

Добавляем 404-шаблон

В templates/404.php выведите простую страницу и оставьте ранее заданный статус 404. Не вставляйте туда переданные URL-фрагменты как HTML. Если показываете запрошенный путь, передайте его через e().

Проверьте /books, неизвестный маршрут и название книги со специальными символами. Теперь форма сможет отправить данные по отдельному POST-маршруту; в следующей части добавим проверку и защиту от поддельной отправки.

Часть 12 из 16.

← Часть 11: HTTP на PHP: разбираем запрос и формируем ответ · Часть 13: PHP-формы POST: проверка данных и защита CSRF →

Документация