Бывает так: заказчик просит «ну как WordPress, только чтобы работало быстрее и без этих ваших плагинов». Или вы сами хотите понять, как устроены CMS изнутри, а не просто ставить готовые. Создание своей системы управления контентом — это как собрать собственный велосипед: вроде и колеса есть, и рама, но всё под себя. В этой статье я расскажу, с чего начать, как не наступить на грабли и сделать так, чтобы ваш «велосипед» не развалился на первом километре.
С чего начать: архитектура будущей CMS
Прежде чем писать код, сядьте и подумайте: что ваша CMS будет уметь? Скорее всего, вам нужно хранить статьи, страницы, может быть, пользователей и настройки. Не пытайтесь объять необъятное — сделайте минимальный набор функций, который решает вашу задачу. Помните: каждая лишняя функция — это лишний код, который нужно поддерживать.
Ключевые модули, без которых не обойтись:
- Маршрутизация — определяет, какой контроллер обработает запрос.
- База данных — обычно MySQL или SQLite, для хранения контента.
- Админка — интерфейс для управления контентом.
- Шаблонизатор — отделяет HTML от PHP-логики.
Простейшая структура файлов может выглядеть так:
project/
├── public/ # корень веб-сервера
│ ├── index.php # точка входа
│ └── .htaccess # для ЧПУ
├── app/
│ ├── controllers/ # контроллеры
│ ├── models/ # модели
│ └── views/ # шаблоны
├── config/ # конфиги
└── vendor/ # зависимости (если есть)
Важно: никогда не храните файлы конфигурации с паролями в корне сайта — это дыра в безопасности.
Маршрутизация: как попасть в нужный контроллер
Маршрутизация — это механизм, который связывает URL с кодом. Например, пользователь заходит на /article/42, и система понимает, что нужно показать статью с id=42. Без маршрутизации пришлось бы делать файлы типа article.php?id=42, что некрасиво и неудобно.
Реализовать простую маршрутизацию можно с помощью .htaccess и index.php. Веб-сервер перенаправляет все запросы на точку входа, а PHP разбирает URL.
// public/.htaccess
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php [QSA,L]
// public/index.php
$path = trim($_SERVER['REQUEST_URI'], '/');
$segments = explode('/', $path);
$controllerName = !empty($segments[0]) ? ucfirst($segments[0]) : 'Home';
$actionName = $segments[1] ?? 'index';
$params = array_slice($segments, 2);
$controllerClass = "App\\Controllers\\{$controllerName}Controller";
if (class_exists($controllerClass)) {
$controller = new $controllerClass();
if (method_exists($controller, $actionName)) {
call_user_func_array([$controller, $actionName], $params);
} else {
http_response_code(404);
echo "Страница не найдена";
}
} else {
http_response_code(404);
echo "Страница не найдена";
}
В этом примере контроллер — это класс, а метод — действие. Например, для URL /article/42 будет вызван метод show класса ArticleController с параметром 42.
Работа с базой данных: PDO — ваш друг
Для хранения контента нужна база данных. Я рекомендую использовать PDO (PHP Data Objects) — это расширение, которое позволяет работать с разными БД единообразно и защищает от SQL-инъекций через подготовленные выражения.
Пример подключения к MySQL:
$host = 'localhost';
$dbname = 'mydb';
$user = 'root';
$pass = 'secret';
try {
$pdo = new PDO(
"mysql:host=$host;dbname=$dbname;charset=utf8",
$user,
$pass,
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]
);
} catch (PDOException $e) {
die('Ошибка подключения: ' . $e->getMessage());
}
Теперь можно выполнять запросы. Например, получить статью по ID:
$stmt = $pdo->prepare('SELECT * FROM articles WHERE id = :id');
$stmt->execute(['id' => $id]);
$article = $stmt->fetch();
Важно: используйте только подготовленные выражения, когда в запросе есть пользовательские данные. Никогда не вставляйте их напрямую в строку запроса.
| Метод PDO | Что делает | Пример |
|---|---|---|
| prepare() | Подготавливает запрос к выполнению | $stmt = $pdo->prepare('SELECT * FROM users WHERE email = :email'); |
| execute() | Выполняет подготовленный запрос с параметрами | $stmt->execute(['email' => $email]); |
| query() | Выполняет запрос без параметров (только для статических SQL) | $rows = $pdo->query('SELECT * FROM categories')->fetchAll(); |
| fetch() | Возвращает одну строку из результата | $row = $stmt->fetch(); |
| fetchAll() | Возвращает все строки | $rows = $stmt->fetchAll(); |
Админка: делаем удобное управление контентом
Без админки ваша CMS — просто скрипт, который выводит статьи из базы. Нужен интерфейс, где можно добавлять, редактировать и удалять записи. Проще всего сделать отдельный контроллер AdminController, который проверяет права пользователя.
Для начала достаточно простой авторизации по логину и паролю. Храните пароль в виде хеша (например, используйте password_hash()), а не в открытом виде.
// app/controllers/AdminController.php
class AdminController {
public function login() {
// показать форму логина
}
public function authenticate() {
// проверить логин и пароль
$user = $this->findUser($_POST['login']);
if ($user && password_verify($_POST['password'], $user['password_hash'])) {
$_SESSION['user_id'] = $user['id'];
header('Location: /admin');
} else {
echo 'Неверный логин или пароль';
}
}
public function logout() {
session_destroy();
header('Location: /');
}
}
Не забудьте про защиту от CSRF-атак: для каждой формы генерируйте токен и проверяйте его при отправке.
Шаблонизатор: разделяем логику и представление
Смешивать PHP и HTML в одном файле — плохая практика. Код быстро становится нечитаемым, а дизайнер не сможет править вёрстку. Поэтому используйте простые шаблоны. Можно написать свой мини-шаблонизатор или использовать готовый, например Twig. Но для обучения сделаем свой.
// app/views/article.php
= htmlspecialchars($article['title']) ?>
= nl2br(htmlspecialchars($article['content'])) ?>
В контроллере подключаем шаблон:
// app/controllers/ArticleController.php
public function show($id) {
$article = $this->getArticle($id);
if (!$article) {
http_response_code(404);
echo 'Статья не найдена';
return;
}
// передаём данные в шаблон
extract(['article' => $article]);
include __DIR__ . '/../views/article.php';
}
Обратите внимание на htmlspecialchars() — это защита от XSS-атак. Всегда экранируйте вывод данных, которые могут содержать HTML.
Безопасность: на что обратить внимание
Самописная CMS — это огромная ответственность. Вы отвечаете за безопасность данных пользователей. Вот базовые меры:
- Используйте подготовленные выражения для SQL.
- Экранируйте вывод с помощью htmlspecialchars().
- Храните пароли только в виде хешей (password_hash).
- Защитите админку от перебора паролей (например, ограничьте число попыток).
- Регулярно обновляйте PHP и сервер.
Также не забывайте про валидацию данных на сервере. Клиентская валидация — это лишь удобство, а не защита.
Итог: что в сухом остатке
Создание своей CMS — это увлекательный опыт, который помогает глубже понять устройство веб-приложений. Вы получаете систему, полностью адаптированную под ваши задачи, без лишнего «мусора» из готовых движков. Но помните: это требует времени и внимания к деталям.
Если вы делаете это для коммерческого проекта, оцените, сколько часов вы потратите на разработку и поддержку. Иногда проще взять готовую CMS и доработать её. Но если хочется экспериментов — дерзайте!
И напоследок: всегда делайте резервное копирование базы данных и кода. Даже самая лучшая CMS может упасть.



