Структура проекта и безопасность: MVC-основы, ошибки, защита
До этого в курсе вы писали отдельные PHP-скрипты, принимали данные через HTTP (GET/POST), работали с сессиями и сохраняли данные в базе через PDO. Следующий шаг, который делает код похожим на реальное веб-приложение — навести порядок в структуре проекта и добавить базовую безопасность как системное правило, а не как набор разрозненных проверок.
В этой статье вы:
Поймёте, зачем нужен единый вход в приложение и структура папок
Освоите MVC на уровне практического смысла: кто за что отвечает
Научитесь централизованно обрабатывать ошибки и исключения
Соберёте базовый набор защит: SQL-инъекции, XSS, CSRF, безопасные сессии и загрузки!Путь HTTP-запроса через MVC-слои
Зачем нужна структура проекта
Когда вы учитесь, удобно иметь файлы вроде register.php, upload.php, create_task.php. Но по мере роста приложения появляются проблемы:
трудно найти, где логика получения данных, где логика работы с БД, где вывод
повторяется код подключения к БД, запуска сессии, валидации
ошибки обрабатываются по-разному в каждом файле
безопасность держится на дисциплине: легко забыть одну проверкуРешение на старте — договориться о структуре и едином месте входа в приложение.
Минимальная структура проекта
Ниже — учебная структура без фреймворков, но уже похожая на продакшен.
| Путь | Назначение |
|---|---|
| public/ | Публичная папка, доступная из браузера |
| public/index.php | Единая точка входа (front controller) |
| src/ | Код приложения (контроллеры, модели, сервисы) |
| src/Controller/ | Контроллеры: принимают запрос, вызывают бизнес-логику |
| src/Model/ | Модели или репозитории: работа с данными и БД |
| src/View/ | Представления: формирование ответа |
| storage/ | Файлы приложения: логи, загрузки (не в public) |
| config/ | Конфигурация (DSN, режим окружения) |
Ключевое правило:
в public/ должен быть минимум кода
загрузки и база данных не должны лежать в public/MVC простыми словами
MVC — это способ разделить код по ответственности.
Model (модель): отвечает за данные и операции с ними (часто: PDO-запросы, репозитории, валидация доменных правил)
View (представление): отвечает за формат ответа (HTML/JSON/текст)
Controller (контроллер): связывает запрос с нужными действиями, вызывает модель, выбирает представлениеВажно: MVC не делает приложение автоматически безопасным, но помогает встроить безопасность в правильные места.
Front controller и роутинг
Front controller — это один файл (обычно public/index.php), который принимает все запросы и сам решает, какой код выполнить.
Плюсы:
единая инициализация: сессии, конфиг, обработка ошибок
единые правила безопасности
проще масштабировать приложениеРоутинг на старте можно сделать очень простым: по пути и методу.
Справка:
PHP Manual: parse_url
PHP Manual: method = path = parse_url(method === 'GET' && method === 'GET' && controller = new \App\Controller\TaskController();
echo method === 'POST' && controller = new \App\Controller\TaskController();
echo _POST);
exit;
}
http_response_code(404);
echo "404 Not Found\n";
} catch (Throwable e);
echo "500 Server Error\n";
}
php
<?php
declare(strict_types=1);
// Простейшая автозагрузка для учебного проекта
spl_autoload_register(function (string prefix = 'App\\';
if (strpos(prefix) !== 0) {
return;
}
class, strlen(file = __DIR__ . '/' . str_replace('\\', '/', file)) {
require pdo)
{
}
public function all(): array
{
this->pdo->query('SELECT id, title, is_done, created_at FROM tasks ORDER BY id DESC');
return title): int
{
this->pdo->prepare(
'INSERT INTO tasks (title, is_done, created_at) VALUES (:title, 0, :created_at)'
);
title,
':created_at' => date('c'),
]);
return (int)_POST
репозиторий всегда использует prepare для данных пользователя
Справка:
PHP Manual: PDO::prepare
PHP Manual: PDO::queryПодключение к БД (как отдельная функция)
Файл src/db.php:
Контроллер задач (Controller)
Файл src/Controller/TaskController.php:
Так у вас появляются понятные границы:
контроллер работает с HTTP-входом
репозиторий работает с БД
public/index.php отвечает за маршрутизацию и обработку ошибокОшибки и исключения: как делать правильно
Новичкам часто хочется просто включить ошибки и смотреть их в браузере. Это полезно в разработке, но опасно в реальной среде.
Режим разработки и режим продакшена
В разработке:
display_errors = 1
error_reporting(E_ALL)В продакшене:
display_errors = 0
ошибки пишутся в лог
пользователю показывается нейтральное сообщениеПочему нельзя показывать ошибки в продакшене:
сообщения могут раскрыть пути на сервере, структуру БД, куски кода
это помогает атакующимСправка:
PHP Manual: Error Reporting
PHP Manual: error_logЦентрализованный try/catch
Идея простая:
в public/index.php обернуть выполнение маршрута в try/catch
в catch логировать исключение
отдавать 500Это лучше, чем ловить ошибки в каждом контроллере.
Безопасность: базовый набор защит
Безопасность удобно воспринимать как слои. Если один слой ошибся, другой не даёт приложению упасть полностью.
Входные данные: не доверяйте ничему
Источники ввода:
_POST
_FILES
часть URL (REQUEST_URI)Правило:
санитизация и валидация делаются на границе контроллера
в модельный слой должны попадать уже проверенные значенияSQL-инъекции: только prepare
Вы уже делали это в PDO-уроке, здесь просто закрепим как правило структуры:
контроллер получает s): string
{
return htmlspecialchars(_SESSION
требовать токен в каждом POST-запросе, который меняет состояниеПример генерации токена:
Пример проверки в контроллере:
Справка:
PHP Manual: random_bytes
PHP Manual: hash_equalsСессии: безопасная авторизация как правило проекта
Из прошлой статьи вы уже знаете основу:
session_start()
перед использованием _SESSION['user'])) {
http_response_code(401);
echo "Нужно войти\n";
exit;
}
return (array)_POST | Controller | create(array s) при выводе |
| CSRF | Controller или Middleware-функция | проверка csrf_token |
| Авторизация | Middleware-функция | requireAuth() |
| Ошибки и логирование | Front controller | try/catch + error_log |
Итог
Вы перешли от набора отдельных скриптов к каркасу приложения:
единая точка входа public/index.php и простой роутинг
понятные роли MVC: контроллер, модель (репозиторий), представление
централизованная обработка ошибок и логирование
базовый набор защит, встроенный в структуру: prepare для SQL, экранирование вывода, CSRF-токены, безопасные сессии, хранение загрузок вне public`Дальше такую структуру проще расширять: добавлять новые сущности, формы, страницы, JSON-API, а также постепенно улучшать архитектуру (сервисы, middleware, шаблонизация, Composer).