Режимы и службы
Я, наконец, решил написать про свою идеологию «режимов и служб» (РиС) — почти универсальный принцип построения стройных yet гибких PHP-приложений. Наверное, этот подход подойдёт и в случае использования других серверных языков, но про них я ничего не знаю. Принцип «режимов и служб» реализован в Plif One и в e2.
Всё началось с того, что мне никогда не нравилось, когда в веб-приложении есть несколько исполняемых php-скриптов (здесь я буду называть исполняемыми скрипты, которые содержат что-нибудь, кроме описаний функций). Типа, для главной страницы — index.php, для страницы about — about.php и так далее. Это очень неудобный (=плохой) способ, т. к. он предполагает, например, copy/paste кода дизайна во все скрипты. Многие будут возражать, что код дизайна можно вынести в отдельные файлы и инклюдить их. Да, конечно, но сам include — это уже код, одинаковый во всех файлах. При таком раскладе нельзя даже переименовать включаемый файл, потому, что это потребует правки огромной кучи других. Потом, во всех файлах могут потребоваться какие-то «общедвижковые» API, находящиеся во всяких других инклюд-файлах. Эти инклюды тоже будут повторяться из файла в файл и их нельзя будет спокойно трогать. В общем, этот подход образует довольно каменный код.
По абсолютно таинственным причинам, однако, именно он используется в подавляющем большинстве скриптов, которые я видел. Единственное исключение, которое приходит на ум — WackoWiki. Но сейчас речь не об этом. Речь о том, как сделать так, чтобы исполняемым был только один скрипт (index.php) и при это всё отлично работало. Условно все действия, выполняемые скриптом, можно поделить на 1) генерацию какого-то контента и скармливание его браузеру, и 2) обработку каких-нибудь POST-запосов.
Первый постулат принципа РиС: не бывает скриптов, которые бы хотя бы в какой-то мере сочетали первое и второе.
Режим
Термин «режим» возник очень просто: я часто делал, чтобы страницы были видны по адресам вида index.php?mode=about (mod_rewrit’ом это легко доводится до красоты, сейчас не об этом), т. е. называл переменную словом mode. Поэтому, проход по скрипту, в результате которого генерируется и скармливается браузеру какой-то контент, называется режимом. Для работы с режимами пишется функция («менеджер режимов»):
01 function modes () {
02 global $mode, $content;
03 $mode = @$_GET['mode'];
04 if (!$mode) $mode = 'frontpage';
05 @require_once 'system/'.$mode.'.php'; # например
06 if (!function_exists ($mode.'_mode')) $mode = 'error404';
07 $content = call_user_func ($mode.'_mode');
08 }
Что она делает — понятно. Ага? Каждый режим генерирует своя функция. Его внешний вид (HTML) она возвращает менеджеру режимов, а он это запихивает в глобальную переменную $content.
Служба
Проход по скрипту, в результате которого обрабатывается какая-нибудь информация из форм, например, а HTML’а браузеру не передаётся, называется службой. Службы обрабатываются так (это «менеджер служб»):
01 function services () {
02 global $service;
03 $service = @$_GET['service'];
04 if (!$service) return;
05 @require_once 'system/'.$service.'.php';
06 if (!function_exists ($service.'_service')) die ('Служба не найдена');
07 $go_to = call_user_func ($service.'_service');
08 if (!$go_to) $go_to = @$_POST['came_from'];
09 if (!$go_to) $go_to = 'http://'.$_SERVER['HTTP_HOST'];
10 header ('Location: '.$go_to);
11 }
Имея эти функции, index.php мы можем оформить примерно так:
01 <?
02 require_once 'ms.php'; # modes and services
03 services ();
04 modes ();
05 echo design ();
06 ?>
Рассмотрим теперь всё это подробнее. Сначала мы вызываем менеджер служб, потому, что это логично (смайлик). Он смотрит, не нужно ли сейчас выполнить какую службу (строки 03-04). Если не нужно, то он возвращает управление функции main (). Если же нужно, то он выполняет службу (строка 07).
Функция, реализующая службу, может вернуть URL, по которому она хочет перекинуть браузер после своего завершения (например. если функция обработала форму отправки комментария, то там может быть урл страницы со всеми комментариями). Если функция не вернула такой урл (строка 08), то тогда мы смотрим, не было ли в форме поля came_from, и если было, то считаем его значения искомым урлом. Если же и этого поля не оказалось (чего вообще-то не должно произойти, см. ниже), то мы перекидываем пользователя на главную (fallback).
Допустим, мы добрались до modes (). Это происходит, когда в параметрах index.php не прописан вызов службы (например, index.php?service=post-comment). Тогда если режим не указан, мы считаем, что мы на главной. Если функция-режим не найдена, то мы выполняем режим 404-й ошибки. Функция-режим всегда возвращает строку — HTML, который нужно показать пользователю. Разумеется, она выдаёт только ту часть HTML, которая в разных режимах разная. В результате, всё это попадает в глобальную переменную $content, вокруг которой в функции design () мы рисуем что нашей душе угодно. Точка.
Теперь про came_from. Ясно, что форма — это режим. Значит, её генерируют какие-то функции-режимы. Мы можем ввести понятие режим-форма и определить для него требование: любая форма должна содержать скрытый input с именем came_from и значением, равным $_SERVER[’HTTP_REFERER’]. Процесс добавления этого input’а можно легко автоматизировать. Тогда менеджер служб всегда будет знать, куда перекинуть браузер после выполнения службы.
Что это всё даёт? А то, что добавление новых типов страниц, функций и чего угодно на сайт, сделанный по такой схеме, становится очень простым делом — нужно просто создать файл в /system и написать в нём, что он должен делать. Всю организационно-дизайнерскую работу возмёт на себя уже готовый каркас.
Дальше, чтобы всё было совсем хорошо, можно сделать .htaccess примерно такого вида:
RewriteEngine On
RewriteRule ^services\/([^/.]+)(\/?)$ index.php?service=$1
RewriteRule ^([^/.]+)(\/?)$ index.php?mode=$1
Кроме того, что это сделает все урлы красивыми, ещё и action у формы будет иметь модный вид «services/post-comment».
Внимание! У этого подхода есть и некоторые недостатки. Если вы нашли один из них, не нужно думать, что вы большой герой, и громко про это кричать. Недостатки — это нормальное явление для всего. Вы можете просто спокойно их изложить, тогда этого не придётся делать мне. Спасибо.
Update: Ужас, сколько идиотов в комментариях; не читайте их.