Продолжаем писать свой PHP framework дальше. Раз уж я решился написать вторую часть, значит, думаю, дело пойдёт. В этой статье, как бы это банально не звучало, мы закрепим обсуждаемый в прошлой статье шаблон проектирования фасад (Facade) и опишем на его основе еще одну нужную в любом движке сущность, называемую объектом запроса.
Как известно, PHP содержит посланные пользователем данные в различных супеглобальных переменных, основными из которых являются $_GET, $_POST и $_COOKIE. На основании этих данных приложение "понимает", что от него хочет пользователь и соответственно пользовательскому запросу выполняет необходимые действия. Что же дают эти суперглобальные массивы нам, как программистам? На основе структуризации этих данным мы может строить логику взаимодействия интерфейса пользователя и непосредственно приложения. Например, мы используем данные из $_GET массива, чтобы определить какую функциональность требует пользователь в данный момент:
Listing №1 (PHP)
if(isset($_GET['mode']) && ($_GET['mode'] === 'reg')) {
// Показываем форму регистрации...
}
else {
// Перенаправляем на index.php
header('Location: /index.php');
}
Приблизительно так же мы можем использовать данные из массивов $_POST, $_COOKIE и может даже остальных.
К чему я веду? В связи с теперешней архитектурой Интернета мы можем получить ответ лишь только если пошлём запрос. Поэтому, все web-приложения работают, так скажем, "по запросу". Грубо говоря - если пользователь ничего не делает, то ничего и не происходит. Если пользователь активирует свои действия, например, нажав на кнопку формы, то это непосредственно "чувствует" приложение. Какие действия выполнил пользователь с интерфейсом мы и определяем из вышеупомянутых суперглобальных массивов. Таким образом, последние являются единственной "отправной точкой" действия большинства web-приложений, написанных на PHP. Следовательно, можно рассматривать всю информацию, находящуюся в этих массивах, как некое отображение взаимодействия пользователя с программой - пользовательский запрос к логике программы, характеризующий обращение пользователя к web-интерфейсу в браузере.
Думаю, многие из вас могут согласиться с тем, что данные одной сущности неплохо было бы держать в одном месте и соответственно иметь одну точку доступа к ним для управления. Если вы так не думаете то попробуйте пересмотреть свои взгляды, или, хотя бы, примите моё мнение как одно из имеющих шанс на существование ;). Следуя вышесказанному, мы можем объединить данные из $_GET, $_POST и $_COOKIE массивов, инкапсулировав их в одном объекте. Сделав это, мы сконструируем уже знакомый нам фасад (Facade) к нескольким различным наборам данных, предоставив один общий интерфейс доступа к ним. Здесь в основном под интерфейсом доступа подразумевается не сам интерфейс доступа, а инкапсуляция доступа в одном объекте - одной точке доступа. Но так как эта точка доступа единственна и, естественно, имеет один интерфейс к различным наборам данных, то можно с уверенностью сказать, что это явный пример паттерна фасад (Facade). Но не спешите так закреплять свои знания об этом типовом решении. То, что я хочу сделать, очень похоже также на шлюз (Gateway) в том смысле, что мы получим основу для реализации фиктивной службы (Service Stub). Её смысл будет в эмуляции переданных пользователем параметров, предназначенной для тестирования приложения. Но так как интерфейс доступа, предоставляемый объектом фасада, будет отличаться от интерфейса, стоящего за ним объекта, мы будем говорить, что это именно фасад, а не шлюз, интерфейс которого может представлять собой точную копию инкапсулируемого интерфейса. Вообще это довольно сильно родственные шаблоны проектирования и на данный момент своей жизни мне самому иногда довольно трудно определить, что есть что.
Мартин Фаулер
Типовое решение
фасад также как и
шлюз упрощает работу с интерфейсом API, однако оно создаётся самим разработчиком внешней службы и предназначено для общего употребления. В свою очередь,
шлюз разрабатывается клиентом для использования конкретным приложением.
Также хочу добавить, что фасад больше чем шлюз "предрасположен" предоставлять более удобный интерфейс доступа. Этим нам тоже надо будет заняться. Также важно, что фасад реализует общий интерфейс над несколькими службами - в нашем случае несколькими источниками данных, в то время как шлюз обычно скрывает за собой одну службу.
Не тратя лишних слов на дальнейшие разбирательства, приступим непосредственно к реализации. Естественно это будет статический класс, с методами доступа к нашим суперглобальным массивам. Эти методы должны быть довольно удобны - то есть должны скрывать некую логику доступа. Таким образом, я думаю, что они будут проверять наличие запрашиваемых нами данных и возвращать NULL если они отсутствуют, либо можно будет задать значение по умолчанию. При этом каждый метод будет отражать доступ к конкретной структуре:
Listing №2 (PHP)
class Request {
/**
* @static
* @param string $VarName
* @param string $Default NULL
* @return mixed
*/
public static function Post($VarName, $Default = NULL)
{
return isset($_POST[$VarName]) ? $_POST[$VarName] : $Default;
}
/**
* @static
* @param string $VarName
* @param string $Default NULL
* @return mixed
*/
public static function Get($VarName, $Default = NULL)
{
return isset($_GET[$VarName]) ? $_GET[$VarName] : $Default;
}
/**
* @static
* @param string $VarName
* @param string $Default NULL
* @return mixed
*/
public static function Cookie($VarName, $Default = NULL)
{
return isset($_COOKIE[$VarName]) ? $_COOKIE[$VarName] : $Default;
}
Вот мы и сконцентрировали в одном месте данные о действиях пользователя. Теперь добавим методы эмуляции этих действий - установки значений соответствующих массивов:
Listing №3 (PHP)
class Request {
/**
* @static
* @param string $VarName
* @param string $Value
*/
public static function SetPost($VarName, $Value)
{
$_POST[$VarName] = $Value;
}
/**
* @static
* @param string $VarName
* @param string $Value
*/
public static function SetGet($VarName, $Value)
{
$_GET[$VarName] = $Value;
}
/**
* @static
* @param string $VarName
* @param string $Value
*/
public static function SetCookie($VarName, $Value)
{
$_COOKIE[$VarName] = $Value;
}
Метод SetCookie выглядит немного неуклюже, так как можно ошибочно представить, что он установит куку в браузере пользователя. Но так как это метод класса Request, то мы не будем обострять на этом внимание.
Ну что же, получилось всё довольно просто, поэтому я добавлю ещё одну особенность. Эта особенность так же будет немного выделять данное решение как реализацию шаблона фасад. Помимо претензий к разработчикам PHP, предъявляемым в прошлой статье, я хочу предъявить еще одну в этой. Это не совсем претензия, но некое недоразумение всё же присутствует. И имя ему Magic Quotes. Я надеюсь, читатель в курсе, что стоит за этими словами. Если нет, то я вкратце скажу, что если директива php.ini файла, называемая magic_quotes_gpc равна значению "On", то данные в наших суперглобальных массивах будут автоматически обработаны и одинарные, двойные кавычки, обратный слеш и NULL символы будут экранированы обратным слешем. Я считаю это функциональность бредовой, хотя бы потому, что аргументов против неё больше, чем аргументов за. В связи с этим я также как и в прошлый раз не хочу зависеть от каких-то там настроек php.ini и иметь на входе чистые данные. Так как директиву magic_quotes_gpc нельзя установить во время исполнения и данные пользователя могут прийти уже экранированными, то нам, возможно, придётся их "разэкранировать". Также я хочу по краям каждой входной переменной отрезать лишние пробелы, NUL-байты и вертикальные табуляции.
Для всего вышесказанного нам понадобиться сделать три метода. Один, для обработки одного суперглобального массива. Второй - для обработки всех массивов. Ну и третий - для инициализации объекта, при которой и будет проводиться зачистка. Третий будет схож с тем, что мы делали для класса обработки строк в прошлой статье:
Listing №4 (PHP)
class Request {
/**
* @static
* @param mixed &$Param
* @return mixed
*/
private static function _StripSlash(&$Param)
{
$Param = (is_array($Param)) ? array_map(array('self', '_StripSlash'), $Param) : trim(stripslashes($Param), " \x0B\0");
return $Param;
}
/**
* @static
*/
private static function _MagicFilter()
{
if (((function_exists('get_magic_quotes_gpc')) && get_magic_quotes_gpc()) ||
(ini_get('magic_quotes_sybase'))) {
self::_StripSlash($_GET);
self::_StripSlash($_POST);
self::_StripSlash($_COOKIE);
}
if (function_exists('get_magic_quotes_runtime') && get_magic_quotes_runtime()) {
set_magic_quotes_runtime(FALSE);
}
}
/**
* @static
*/
public static function Init()
{
self::_MagicFilter();
}
Метод _MagicFilter также обрабатывает директивы magic_quotes_sybase и magic_quotes_runtime, что также способствует оперированию в дальнейшем "чистыми" данными. Теперь вначале сценария надо вызвать метод инициализации объекта запроса. Он будет находиться в том же месте, где инициализируется работа со строками:
Listing №5 (PHP)
Str::Init(Str::MODE_MULTIBYTE, 'UTF-8');
Request::Init();
На этом примере мы завершим текущее обсуждение. В следующей статье я расскажу о более "архитектурных" частях нашего фреймворка и затрону также несколько других, более интересных, шаблонов проектирования.
Продолжение следует...
Прикольная статья жду продолжения