Поводом для написания этой статьи послужил один разговор с одним неплохим человеком, который всесторонне развит, знает всего по-немножку, но глубже уйти, скажем, в тот же 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 для сайта
Большое спасибо за данную статью и базис для создания своего движка!
По мере прочтения и пробы возник вопрос — можно ли прикрутить класс для работы с базой SQLite и как правильно сделать универсальность работы с базой выбранной в конфиге?
Или только изначально затачивать под конкретную базу?
А чём проблема?
меняете класс БД на sqllite. Подправить там мизер надо в классе ядра — по подключению (и то, смотря чё за класс БД)
я думаю стоит сделать в конфиге переменную указывающую на тип БД, только как быть с разницей в командах разных баз? это же должно учитываться в самом движке…
ЗЫ: а можно контакт ICQ или скайп для прямого общения? изучаю PHP было бы удобнее пообщаться….
Это обычный каркас для создания полноценной CMS. Возможность выбора типа БД это уже лишние навороты. А адаптация запросов должна происходить на уровне их классов, а не на уровне движка.
еще вопрос возник…
в статье про классы для работы с БД в конце приведен чей-то класс с защитой от инжектов…
а в классе который в составе этого каркаса есть защита от инжектов?
Нет, каждый запрос надо предварительно обрабатывать с помощью mysql_real_escape_string();
достаточно в классе каждому запросу эту обработку добавить?
В принципе, да. Можно и в классе добавить метод mysql_real_escape_string($sql);
Но надо учесть, что часто он будет работать в холостую — ведь в запросах не всегда участвуют данные полученные от пользователей