🎯 Цель урока
Понять, зачем нужна архитектура MVC для веб-приложений. Создать базовую структуру проекта, реализовать простой роутинг и разделить код по ответственностям (логика, данные, представление).
📚 Теоретическая часть (40 минут)
🏗️ Почему нельзя просто писать весь код в одном файле?
"Спагетти-код" — это когда HTML, PHP-логика, работа с базой данных и проверки форм перемешаны в одном или нескольких файлах без четкой структуры.
🔍 Проблемы монолитного кода:
- Сложно поддерживать — чтобы изменить отображение, нужно искать код среди логики
- Трудно тестировать — нельзя проверить отдельные части приложения
- Невозможно повторно использовать — код заточен под конкретную страницу
- Опасно для безопасности — легко допустить ошибку при смешивании логики и вывода
- Затруднена командная работа — несколько разработчиков не могут работать одновременно
💡 Реальная аналогия: Представьте кухню, где продукты, ножи, посуда и готовые блюда разбросаны по всему столу без порядка. Шеф-повар (разработчик) тратит 80% времени на поиск нужного, а не на готовку.
📐 Архитектурный шаблон MVC (Model-View-Controller)
MVC — это способ организации кода, который разделяет приложение на три основных компонента, каждый со своей ответственностью.
| Компонент | Ответственность | Аналогия в ресторане | Что содержит |
|---|---|---|---|
| Model (Модель) | Работа с данными, бизнес-логика | Кухня и склад | Классы для работы с БД, расчеты, валидация |
| View (Вид/Представление) | Отображение данных пользователю | Зал и официант | HTML-шаблоны, CSS, JavaScript для интерфейса |
| Controller (Контроллер) | Прием запроса и координация Модели и Вида | Администратор ресторана | Логика обработки запросов, выбор шаблона |
✨ Как работает поток данных в MVC:
- Пользователь совершает действие (переходит по URL, отправляет форму)
- Роутер определяет, какой контроллер и метод вызвать
- Контроллер принимает запрос, запрашивает данные у Модели
- Модель работает с базой данных, выполняет бизнес-логику
- Контроллер получает данные от Модели и передает их во Вид
- Вид отрисовывает HTML-страницу с полученными данными
- Пользователь видит результат в браузере
📝 Важно: В классическом MVC компоненты общаются только в одном направлении: Контроллер → Модель → Контроллер → Вид. Вид никогда не обращается напрямую к Модели.
🛣️ Что такое роутинг (маршрутизация)?
Роутинг — это процесс определения, какой код выполнить для конкретного URL-адреса.
Примеры URL и их обработка:
https://site.ru/→ Главная страницаhttps://site.ru/about→ Страница "О нас"https://site.ru/posts/5→ Статья с ID 5https://site.ru/contact→ Страница контактов
Типы роутинга:
- "Наивный" (простой) роутинг — через параметры в URL (
?page=about) - ЧПУ (Человеко-Понятные Урлы) — красивые пути (
/about,/posts/5) - Роутинг в фреймворках — сложные системы с регулярными выражениями
💡 Простая аналогия: Роутинг — это как секретарь в приемной. Клиент (браузер) говорит "Мне нужно к менеджеру по продажам" (URL /sales), а секретарь (роутер) направляет его в нужный кабинет (контроллер).
⚙️ Подготовка проекта: новая структура папок
Для работы с MVC нужно правильно организовать файлы проекта.
📁 Новая структура папок для блога:
myblog/ # Корневая папка проекта
├── app/ # Ядро приложения
│ ├── controllers/ # Контроллеры
│ │ └── HomeController.php
│ ├── models/ # Модели
│ │ └── Post.php
│ └── views/ # Представления (пока обычные PHP-файлы)
│ ├── home.php
│ └── layout.php
├── public/ # Публичная папка (точка входа)
│ └── index.php # Единая точка входа
├── config/ # Конфигурационные файлы
└── vendor/ # Зависимости (позже добавим через Composer)
🔒 Почему public/index.php — точка входа?
- Все запросы идут через один файл
- Повышается безопасность — основной код находится вне public
- Упрощается конфигурация сервера
- Легче реализовать роутинг
💻 Практическая часть (2 часа)
✅ Шаг 1: Создание структуры проекта
- Создайте новую папку
mvc_blogв папке вашего локального сервера:Для OpenServer: C:\OpenServer\domains\mvc_blog\ Для XAMPP: C:\xampp\htdocs\mvc_blog\ - Внутри создайте структуру папок как показано выше
- Откройте проект в VS Code
📁 Важно: Папка public будет доступна из браузера, а папка app — нет. Это повышает безопасность.
✅ Шаг 2: Создание единой точки входа
В файле public/index.php напишите:
<?php
// public/index.php - Единая точка входа
// Включение отображения ошибок (только для разработки!)
ini_set('display_errors', 1);
error_reporting(E_ALL);
// Базовая конфигурация
define('ROOT_PATH', dirname(__DIR__));
define('APP_PATH', ROOT_PATH . '/app');
define('VIEW_PATH', APP_PATH . '/views');
// Простейший "наивный" роутинг через GET-параметр
$page = $_GET['page'] ?? 'home';
// Обработка роутов
switch ($page) {
case 'home':
$controller = 'HomeController';
$action = 'index';
break;
case 'about':
$controller = 'HomeController';
$action = 'about';
break;
default:
// Если страница не найдена
$controller = 'HomeController';
$action = 'notFound';
break;
}
// Подключаем контроллер
$controllerFile = APP_PATH . "/controllers/{$controller}.php";
if (file_exists($controllerFile)) {
require_once $controllerFile;
// Создаем объект контроллера и вызываем метод
$controllerInstance = new $controller();
if (method_exists($controllerInstance, $action)) {
$controllerInstance->$action();
} else {
die("Метод {$action} не найден в контроллере {$controller}");
}
} else {
die("Контроллер {$controller} не найден");
}
?>
📖 Разбор кода роутинга:
$_GET['page'] ?? 'home'— получаем параметр page или используем 'home' по умолчаниюswitch— определяем, какой контроллер и метод вызватьdefine()— создаем константы для путей к папкамfile_exists()— проверяем, существует ли файл контроллераrequire_once— подключаем файл контроллераnew $controller()— создаем объект контроллера (динамически по имени)method_exists()— проверяем, существует ли метод в контроллере
✅ Шаг 3: Создание первого контроллера
В файле app/controllers/HomeController.php напишите:
<?php
// app/controllers/HomeController.php
class HomeController
{
public function index()
{
// Данные для главной страницы
$data = [
'title' => 'Добро пожаловать в MVC блог!',
'posts' => [
['id' => 1, 'title' => 'Первая статья', 'content' => 'Содержание первой статьи...'],
['id' => 2, 'title' => 'Вторая статья', 'content' => 'Содержание второй статьи...'],
],
'current_date' => date('d.m.Y'),
];
// Подключаем вид (представление)
$this->render('home', $data);
}
public function about()
{
$data = [
'title' => 'О нашем блоге',
'description' => 'Этот блог создан для изучения MVC архитектуры.',
'author' => 'Команда разработчиков',
];
$this->render('about', $data);
}
public function notFound()
{
$data = [
'title' => 'Страница не найдена',
'message' => 'Запрошенная страница не существует.',
];
$this->render('404', $data);
}
/**
* Метод для отображения вида
*/
private function render($viewName, $data = [])
{
// Извлекаем переменные из массива данных
// ['title' => '...'] превратится в $title = '...';
extract($data);
// Подключаем основной шаблон (layout)
require_once VIEW_PATH . '/layout.php';
}
}
?>
📖 Разбор кода контроллера:
class HomeController— объявляем класс контроллераpublic function index()— метод для главной страницы$data— массив данных, которые передаются в вид$this->render()— вызов метода для отображения видаextract($data)— преобразует массив в переменные (ключи → имена переменных)require_once— подключаем файл шаблона
✅ Шаг 4: Создание базового шаблона (layout)
В файле app/views/layout.php напишите:
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo $title ?? 'MVC Блог'; ?></title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: Arial, sans-serif; line-height: 1.6; }
.container { max-width: 1200px; margin: 0 auto; padding: 0 20px; }
header { background: #2c3e50; color: white; padding: 1rem 0; }
nav ul { display: flex; list-style: none; gap: 20px; }
nav a { color: white; text-decoration: none; }
nav a:hover { text-decoration: underline; }
main { padding: 2rem 0; }
.post { background: #f9f9f9; padding: 1rem; margin-bottom: 1rem; border-left: 4px solid #3498db; }
footer { background: #34495e; color: white; padding: 1rem 0; margin-top: 2rem; }
.error { color: #e74c3c; padding: 2rem; text-align: center; }
</style>
</head>
<body>
<header>
<div class="container">
<h1>MVC Блог</h1>
<nav>
<ul>
<li><a href="/mvc_blog/public/?page=home">Главная</a></li>
<li><a href="/mvc_blog/public/?page=about">О блоге</a></li>
</ul>
</nav>
</div>
</header>
<main class="container">
<!-- Здесь будет динамический контент из отдельных видов -->
<?php
// Определяем, какой конкретный вид загрузить
$viewFile = VIEW_PATH . '/' . ($viewName ?? 'home') . '.php';
if (file_exists($viewFile)) {
require_once $viewFile;
} else {
echo '<div class="error">Шаблон не найден</div>';
}
?>
</main>
<footer>
<div class="container">
<p>© <?php echo date('Y'); ?> MVC Блог. Все права защищены.</p>
<p>Сегодня: <?php echo $current_date ?? date('d.m.Y'); ?></p>
</div>
</footer>
</body>
</html>
✅ Шаг 5: Создание отдельных видов (views)
1. Создайте app/views/home.php:
<h2><?php echo $title; ?></h2>
<p>Это главная страница нашего блога на MVC архитектуре.</p>
<h3>Последние статьи:</h3>
<?php if (!empty($posts)): ?>
<?php foreach ($posts as $post): ?>
<div class="post">
<h4><?php echo htmlspecialchars($post['title']); ?></h4>
<p><?php echo htmlspecialchars($post['content']); ?></p>
<a href="#">Читать далее...</a>
</div>
<?php endforeach; ?>
<?php else: ?>
<p>Статей пока нет.</p>
<?php endif; ?>
2. Создайте app/views/about.php:
<h2><?php echo $title; ?></h2>
<p><?php echo $description; ?></p>
<p><strong>Автор:</strong> <?php echo $author; ?></p>
<p>Этот проект демонстрирует:</p>
<ul>
<li>Архитектуру MVC</li>
<li>Простую маршрутизацию</li>
<li>Разделение логики и представления</li>
<li>Использование базового шаблона (layout)</li>
</ul>
3. Создайте app/views/404.php:
<div class="error">
<h2><?php echo $title; ?></h2>
<p><?php echo $message; ?></p>
<a href="/mvc_blog/public/?page=home">Вернуться на главную</a>
</div>
✅ Шаг 6: Тестирование приложения
- Откройте браузер и перейдите по адресу:
Должна открыться главная страница со списком статей.http://localhost/mvc_blog/public/?page=home - Проверьте другие страницы:
http://localhost/mvc_blog/public/?page=about http://localhost/mvc_blog/public/?page=non_existent - Нажмите Ctrl+U (просмотр исходного кода) - вы увидите чистый HTML без PHP!
🔍 Что произошло:
- Браузер запросил
?page=home index.phpопределил, что нужно вызватьHomeController::index()- Контроллер подготовил данные (массив статей)
- Контроллер вызвал
render('home', $data) - Был подключен
layout.php, который в свою очередь подключилhome.php - В браузер отправился готовый HTML
🧪 Эксперименты с кодом
Эксперимент 1: Добавьте новую страницу "Контакты"
- Добавьте новый case в switch в
index.php - Добавьте метод
contact()вHomeController - Создайте файл
app/views/contact.php - Проверьте:
?page=contact
Эксперимент 2: Измените роутинг
Попробуйте изменить параметр по умолчанию с 'home' на 'about' и посмотрите, что изменится.
Эксперимент 3: Создайте простую Модель
Создайте файл app/models/Post.php:
<?php
class Post
{
public static function getAll()
{
return [
['id' => 1, 'title' => 'Статья из модели', 'content' => '...'],
['id' => 2, 'title' => 'Еще статья', 'content' => '...'],
];
}
}
?>
Измените в контроллере: $posts = Post::getAll();
📋 Домашнее задание
✏️ Задание на день 1
- Создайте структуру MVC-проекта как в уроке и убедитесь, что всё работает
- Добавьте страницу "Контакты" с формой (пока без обработки):
<form method="POST"> <input type="text" name="name" placeholder="Ваше имя"> <input type="email" name="email" placeholder="Email"> <textarea name="message" placeholder="Сообщение"></textarea> <button type="submit">Отправить</button> </form> - Создайте простую Модель Post как в эксперименте 3
- Модифицируйте роутинг так, чтобы по умолчанию открывалась страница "about"
- Бонус: Создайте отдельный контроллер
PostControllerс методомshow($id)и страницей отдельной статьи. URL:?page=post&id=1 - Бонус 2: Добавьте в навигационное меню автоматическое выделение активной страницы (например, через добавление класса "active")
🧠 Проверка понимания
❓ Вопросы для самопроверки
- Какие три компонента MVC и что делает каждый из них?
- Почему "наивный" роутинг через GET-параметры не идеален для реальных проектов?
- Что делает функция
extract()и почему мы её используем? - Зачем нужна единая точка входа (public/index.php)?
- Почему мы разделили layout и отдельные views?
- Какой путь проходит запрос от браузера до отображения страницы в MVC?
- Что произойдет, если пользователь запросит несуществующую страницу?
- Почему в папке public только index.php, а остальной код в app/?
💡 Полезные советы по MVC
Начинайте с простого
Не пытайтесь сразу реализовать идеальный MVC. Ваш "наивный" роутинг — отличный первый шаг. Позже вы замените его на более продвинутый.
Используйте автозагрузку классов
Вместо множества require_once используйте автозагрузчик. Добавьте в index.php: spl_autoload_register(function($class) { require_once APP_PATH . '/' . str_replace('\\', '/', $class) . '.php'; });
Создайте конфигурационный файл
Вынесите настройки базы данных, пути и константы в отдельный файл config.php. Это упростит развертывание на разных серверах.
Безопасность входа в приложение
Всегда проверяйте входные данные в контроллерах. Используйте htmlspecialchars() для вывода пользовательского контента в шаблонах.
Создайте базовый контроллер
Вынесите общие методы (render, redirect, проверка авторизации) в BaseController. Все остальные контроллеры будут наследовать от него.
Тестируйте компоненты отдельно
Модели можно тестировать без контроллеров, контроллеры — без видов. Это главное преимущество MVC для разработки и отладки.
Комментарии
Классный урок