Принцип работы простейшей CMS (системы управления сайтом)

Поводом для написания этой статьи послужил один разговор с одним неплохим человеком, который всесторонне развит, знает всего по-немножку, но глубже уйти, скажем, в тот же PHP не может — ввиду, по-сути, отсутствия нормальной информации и толковых уроков по написанию своих систем управления сайтом. А тема, однако, достаточно актуальная, ведь не многие профессионалы готовы поделиться своими наработками (тем более с широкой общественностью).

Итак, писать или нет свой движок для сайта — решать Вам (ну или Вашему начальству). Если будущий сайт будет обладать достаточно редким функционалом, то я, к примеру, использую свою CMS. Если это статейный сайт, состоящий из большого количества контента, то я возьму скорее всего популярный движок вроде DLE или Drupal.

Здесь речь пойдёт о том, как устроена моя собственная система управления сайтом (CMS). Возможно кому-то покажется полезным вникнуть в азы ООП на PHP. Статья рассчитана на человека, который в общих чертах владеет понятием программирования на PHP, не лезет в справочник, чтобы посмотреть что это за функции: print_f(), var_dump(), strtoupper(), strtolower(), substr(), strlen(), mysql_query(), mysql_fetch_assoc() и тд… По такому принципу работают 80% известных мне движков.

Итак, прежде чем начать, давайте условимся, что нас интересуют «человеко-понятные урлы» (ЧПУ) и работать будем только с кодировкой UTF-8 (про Windows-1251 забудьте раз и навсегда — она доставит Вам немало геммороя, если Вы захотите внедрить на сайт интерактивные элементы с использованием Javacsript).

Файл .htaccess (настраиваем сервер на работу с ЧПУ):

<FilesMatch "((error_log)|(\.(db|inc|class)))$">
  Order allow,deny
</FilesMatch>

Options -Indexes

php_value upload_max_filesize "15M"
php_value post_max_size "15M"

AddDefaultCharset utf-8

<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteBase /

  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteCond %{REQUEST_FILENAME} !-d
  RewriteRule ^(.*)$ index.php?/$1 [QSA,L]

</IfModule>

Итак, речь здесь пойдёт о простой модульной CMS, которая легко сопровождается, развивается и использует для навигации т.н. роутер.

Роутер в CMS — это набор правил для ядра системы по которым это ядро будет понимать какие модули и блоки подключить, какой шаблон для страницы использовать, — когда пользователь запросил конкретный адрес сайта. Некоторые мои первые CMS не использовали набор правил(роутер). Навигация была довольно простой, к примеру, если чел обратился по адресу: /contact , то подключался файл includes/modules/contact.php. Выглядело данное решение так:

// точка входа index.php
$url = trim($_SERVER['REQUEST_URI'], "/");    // например, /contact
if(file_exists("includes/modules/{$url}.php")){
    include "includes/modules/{$url}.php";    //модуль
}else if($uri == ''){
    include "include/modules/index.php";    // главная страница
}else{
    header("HTTP/1.0 404 Not Found");    // такого модуля нет
    die("Bad request!");
}

Данный метод не идеален и я давно отказался от него. Главным образом из-за того, что каждый модуль перерастал в свою отдельную программу и было достаточно сложно сопровождать проекты, хоть даже и не большие. В общем, минусов в таком подходе гораздо больше, чем плюсов.

В моём понимании, точка входа(это чаще всего наш файл index.php) в нормальной CMS должна подключать как минимум один файл ядра системы из отдельного файла(в данном случае класс core.class.php):

// точка входа index.php
include 'config.php';  // настройки бд и роутер
include 'includes/core.class.php';    // класс, обрабатывающий запросы + некоторые полезные методы
include 'includes/mysql.class.php';    // класс работы с базой данных mysql

$core = new Core($config);    // создаём дубликат класса
echo $core->bootstrap();    // запускаем, сопсна, всю прогу
exit;

Как и многие учебники я тоже буду настаивать, чтобы Вы воспринимали оба класса(в примере выше) как чёрные ящики, которые что-то умеют делать, если им ещё чего-нибудь передать. Согласитесь, ведь программисту не обязательно знать как работает функция echo(); — достаточно просто знать, что если передать ей параметр, то PHP позаботится о том, чтобы вывести этот параметр в окно браузера. А большое внимание необходимо уделить файлу роутера config.php

$config = array(
  'debug'    => true,                   // включает/выключает режим отладки
  'template' => 'templates/index.html', // шаблон по умолчанию (если в правиле не оговорён другой)

  'router' => array(    // описываем правила роутера - от сложнейшего к простейшему
    array(
      'rule'      => '/items/:sect/:num',  // вид правила
      'default'   => array('sect' => 'audi', 'num' => 14),  // значения по умолчанию переменных sect и num
      'condition' => array('sect' => '^[a-z]{1,10}$', 'num' => '^[0-9]{1,2}$'),  // регулярное выражение в соответствии с которым может меняться переменная
      'path'      => array(    // пути к блокам, которые надо подключить
        'content'   => 'modules/items.php',  // сам модуль
        'head'      => 'blocks/head.php',    // формирует динамические записи в <HEAD> документа
        'header'    => 'blocks/header.php',    // выводим шапку документа
        'left'      => 'blocks/left.php',    // выводим левый блок документа
        'footer'    => 'blocks/footer.php'    // выводим подвал документа
      )
    ),
    array(
      'rule'      => '/123',
      'template'  => 'templates/qweqwe.html',    // переопределим шаблон
      'path'      => array(
        'content'   => 'modules/index.php',
        'head'      => 'blocks/head.php',
        'header'    => 'blocks/header.php',
        'left'      => 'blocks/left.php',
        'footer'    => 'blocks/footer.php'
      )
    ),
    array(
      'rule'      => '/',
      'path'      => array(
        'content'   => 'modules/index.php',
        'head'      => 'blocks/head.php',
        'header'    => 'blocks/header.php',
        'left'      => 'blocks/left.php',
        'footer'    => 'blocks/footer.php'
      )
    )
  ),

  // параметры подключения к базе данных
  'db' => array(
    'server' => 'localhost',
    'user' => 'root',
    'password' => '',
    'name' => 'basename'
  )
);

В примере выше описаны только три правила роутера. Правила необходимо писать от сложнейшего к простейшему — таков принцип перебора их в ядре. Первое правило: «чел» перешёл по адресу вида /items/:sect/:num, где :sect — это набор символов от a до z длиной от 1 до 10, а :num — это цифры от 0 до 9 длиной от 1 до 2. Диапазоны этих переменных указаны в регулярных выражениях. Таким образом, при переходе чела по адресам вида:

  • /items/audi/12
  • /items/bmw/20
  • /items/opel/30

они будут обработаны как подходящие к условию и ядро подключит файлы соответствующих блоков и модулей:

  • modules/items.php
  • blocks/head.php
  • blocks/header.php
  • blocks/left.php
  • blocks/footer.php

Самое интересное!

Считаю, что ключ в понимании как устроена данная CMS лежит в файле шаблона страницы! Итак, давайте взглянем на простейший шаблон для данной CMS:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  %%HEAD%%
</head>
<body>
<div>
  %%HEADER%%
</div>
<div>
  %%CONTENT%%
</div>
<div>
  %%FOOTER%%
</div>
</body>
</html>

Здесь переменные %%HEAD%%, %%HEADER%% и тд являются идентификаторами замены на блоки, которые будут заменены всем тем, что вывелось(через echo «»;) в файлах ‘head’ => ‘blocks/head.php’, ‘header’ => ‘block/header.php’ и тд. на ВЫВОД.. Другими словами, если файл blocks/head.php у нас такой:

$meta = '<meta http-equiv="Content-language" content ="ru">
    <meta name="resource-type" content="document" />
    <meta name="document-state" content="dynamic" />
    <meta name="distribution" content="global" />
    <meta name="author" content="Ivan Ivanov" />
    <meta name="robots" content="index, follow" />';
echo $meta;

то %%HEAD%% из шаблона примет вид(строки 4-9):

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <meta http-equiv="Content-language" content ="ru">
  <meta name="resource-type" content="document" />
  <meta name="document-state" content="dynamic" />
  <meta name="distribution" content="global" />
  <meta name="author" content="Ivan Ivanov" />
  <meta name="robots" content="index, follow" />
</head>
<body>
<div>
  %%HEADER%%
</div>
<div>
  %%CONTENT%%
</div>
<div>
  %%FOOTER%%
</div>
</body>
</html>

Так произойдёт со всеми идентификаторами замены. Название идентификатора определяется ключом из правила (например, %%CONTENT%%  ——   content‘ => ‘modules/index.php’ (заметьте — обязательно верхний регистр — так задумано:)

А подвожу я это всё к тому, что каждый файл теперь может отвечать строго конкретной узкой задаче и не лезть в задачи остальных. Смешно смотреть, как некоторые зелёные спецы пишут сайты, которые формируют и head и content и футер в одном файле… Даже если Вы думаете, что сайт не будет в дальнейшем сопровождаться, всё-равно надо стараться писать правильно(читай «модульно»).

В конце статьи есть ссылка для скачки целиком этого движка, если есть желание можно разобраться и с устройством ядра системы.

А пока методы системы, т.н. туториал:

Core($config) Конструктор класса. Принимает массив $config (см. файл config.php)
bootstrap() Возвращает собранную страницу для окончательного вывода на экран
block_set($name, $value) Устанавливает в блок $name значение $value
block_get($name, $default) Возвращает $value последнего установленного блока с именем $name. $default — необязательный параметр — вернёт его, если имя блока не определено
connect_db() Производит подключение к базе данных и возвращает идентификатор этого подключения

Скачать простую и понятную CMS для сайта

Запись опубликована в рубрике PHP, Программирование с метками , . Добавьте в закладки постоянную ссылку.

8 комментариев: Принцип работы простейшей CMS (системы управления сайтом)

  1. MakowaR говорит:

    Большое спасибо за данную статью и базис для создания своего движка!
    По мере прочтения и пробы возник вопрос — можно ли прикрутить класс для работы с базой SQLite и как правильно сделать универсальность работы с базой выбранной в конфиге?
    Или только изначально затачивать под конкретную базу?

  2. admin говорит:

    А чём проблема?
    меняете класс БД на sqllite. Подправить там мизер надо в классе ядра — по подключению (и то, смотря чё за класс БД)

  3. MakowaR говорит:

    я думаю стоит сделать в конфиге переменную указывающую на тип БД, только как быть с разницей в командах разных баз? это же должно учитываться в самом движке…

    ЗЫ: а можно контакт ICQ или скайп для прямого общения? изучаю PHP было бы удобнее пообщаться….

    • admin говорит:

      Это обычный каркас для создания полноценной CMS. Возможность выбора типа БД это уже лишние навороты. А адаптация запросов должна происходить на уровне их классов, а не на уровне движка.

  4. MakowaR говорит:

    еще вопрос возник…
    в статье про классы для работы с БД в конце приведен чей-то класс с защитой от инжектов…
    а в классе который в составе этого каркаса есть защита от инжектов?

Добавить комментарий