Бывает так: заказчик просит «ну как WordPress, только чтобы работало быстрее и без этих ваших плагинов». Или вы сами хотите понять, как устроены CMS изнутри, а не просто ставить готовые. Создание своей системы управления контентом — это как собрать собственный велосипед: вроде и колеса есть, и рама, но всё под себя. В этой статье я расскажу, с чего начать, как не наступить на грабли и сделать так, чтобы ваш «велосипед» не развалился на первом километре.

Digital-студия WNDER

С чего начать: архитектура будущей CMS

Прежде чем писать код, сядьте и подумайте: что ваша CMS будет уметь? Скорее всего, вам нужно хранить статьи, страницы, может быть, пользователей и настройки. Не пытайтесь объять необъятное — сделайте минимальный набор функций, который решает вашу задачу. Помните: каждая лишняя функция — это лишний код, который нужно поддерживать.

Ключевые модули, без которых не обойтись:

  • Маршрутизация — определяет, какой контроллер обработает запрос.
  • База данных — обычно MySQL или SQLite, для хранения контента.
  • Админка — интерфейс для управления контентом.
  • Шаблонизатор — отделяет HTML от PHP-логики.

Простейшая структура файлов может выглядеть так:


project/
├── public/          # корень веб-сервера
│   ├── index.php    # точка входа
│   └── .htaccess    # для ЧПУ
├── app/
│   ├── controllers/ # контроллеры
│   ├── models/      # модели
│   └── views/       # шаблоны
├── config/          # конфиги
└── vendor/          # зависимости (если есть)

Важно: никогда не храните файлы конфигурации с паролями в корне сайта — это дыра в безопасности.

Правило №1: Начните с малого. Сделайте систему, которая умеет создавать, редактировать и удалять одну сущность — например, «статьи». Когда заработает — расширяйте.

Маршрутизация: как попасть в нужный контроллер

Маршрутизация — это механизм, который связывает 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();
Лайфхак: Если вы пишете SQL-запрос, в котором есть условия, всегда используйте подготовленные выражения. Это убережёт от взлома через SQL-инъекции.

Админка: делаем удобное управление контентом

Без админки ваша 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

В контроллере подключаем шаблон:


// 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 и сервер.

Также не забывайте про валидацию данных на сервере. Клиентская валидация — это лишь удобство, а не защита.

Правило №2: Никогда не доверяйте данным, пришедшим от пользователя. Всегда проверяйте и фильтруйте их.

Итог: что в сухом остатке

Создание своей CMS — это увлекательный опыт, который помогает глубже понять устройство веб-приложений. Вы получаете систему, полностью адаптированную под ваши задачи, без лишнего «мусора» из готовых движков. Но помните: это требует времени и внимания к деталям.

Если вы делаете это для коммерческого проекта, оцените, сколько часов вы потратите на разработку и поддержку. Иногда проще взять готовую CMS и доработать её. Но если хочется экспериментов — дерзайте!

И напоследок: всегда делайте резервное копирование базы данных и кода. Даже самая лучшая CMS может упасть.

Студия WNDER