Примеры шаблонов проектирования или как написать свой PHP framework. Часть 1: Строковый фасад
Каждый из Web-программистов может прийти к такому моменту своей жизни, когда захочет сделать свой собственный движок, цивилизованными словами - framework, для каких-то своих разработок. Я хочу помочь вам в этом и отдать свой опыт разработки в ваше распоряжение. Возможно, вы почерпнёте из этого материала хороший кусок знаний, так как сама разработка будет основана на применении различных шаблонов проектирования. В этой статье я расскажу о применении шаблона проектирования фасад (Facade) в контексте класса обработки срок. Что же это за класс и зачем он нужен? Допустим, вы пишете русскоязычный сайт. Естественно при этом вы пользуетесь различными строковыми функция, такими как substr, strlen и т.д., которые работают с однобайтовой кодировкой, например Windows-1251. Вот вы написали сайт, запустили его, он работает и радует ваш глаз. Но вдруг вам понадобилось расширить локализацию сайта языком, алфавит которого не может описаться одним байтом. Тут, конечно же, вам на помощь придёт кодировка UTF-8. Как известно PHP нормально работает с многобайтовой кодировкой посредством расширения mbstring. И вы задаёте себе вопрос: заменять все строковые функции в проекте на mb_*? Конечно же нет. Для таких случаев уже придумали фичу, которая заключается в регулировании параметра php.ini mbstring.func_overload. По умолчанию значение этого параметра равно нулю, но если вы установите его в двойку, то строковые функции, работающие с однобайтовыми символами, перегрузятся функциями из расширения mbstring. То есть вместо substr будет вызвана mb_substr, хотя написана будет именно первая. Этот очень хороший механизм пригодится, когда вам необходимо внедрить код, написанный с помощью стандартных строковых функций в приложение, работающее в многобайтном режиме. Класс, который я собираюсь вам показать, попросту абстрагирует приложение от параметра mbstring.func_overload. Конечно же, многим этот подход может не понравиться, вы даже можете сказать, что это вообще не нужно делать. Но я люблю быть независимым от подобных настроек, поэтому и вам рекомендую пользоваться таким подходом, раз уж разработчики PHP не были в состоянии сделать сразу всё как полагается. Итак, какие задачи будет решать этот класс? Первое, что он должен сделать, это инкапсулировать в себе строковый функционал, запретив доступ программиста к стандартным строковым функциям, будь то substr или mb_substr. В это же время он предоставит один интерфейс, то есть одни методы доступа, для вызова различных функций. Как я уже сказал выше, это будет явный представитель типового решения фасад (Facade), так как для доступа к двум интерфейсам функций мы предоставим доступ через один унифицированный. Для удобства он преобразует свой интерфейс в интерфейсы вызываемых функций. Перечислим обязанности нашего "строкового фасада": 1. Выбор режимов функционирования: однобайтовый / многобайтовый. 2. Детектирование настроек окружения и выбор соответствующих вызовов. 3. Статический интерфейс, совместимый с интерфейсом функций. Давайте определим некоторые свойства класса и напишем метод, который будет определять, какого типа функцию нужно вызвать фасаду. Listing №1 (PHP) class Str { const MODE_ANSII = 0; const MODE_MULTIBYTE = 1; /** * Текущий режим работы * Принимает значения * MODE_MULTIBYTE для работы в многобайтовом режиме * или MODE_ANSII в однобайтовом * По умолчанию в многобайтовом * @static int */ private static $_Mode = 1; /** * Текущая кодировка * По умолчанию UTF-8 * @static stirng */ private static $_Encoding = 'UTF-8'; /** * Значение параметра mbstring.func_overload * @static array */ private static $_OverloadBitmask = 0; /** * Возвращает TRUE если нужно вызвать mb_* функцию * @param int $OvelroadBitmask - флаг выбора возможных перезагруженных функций * @return bool */ private static function _NeedUseMBFuncs($OvelroadBitmask) { return (self::$_Mode && self::$_OverloadBitmask) ? (!($OvelroadBitmask & self::$_OverloadBitmask)) : self::$_Mode; }
Как видите, мы выбираем конечную функцию в зависимости от нескольких параметров. Если режим работы многобайтовый и функции перегружены, то мы вызывает те функции, которые перегрузились, сравнивая побитно (обратите внимание на одинарный знак &) значение mbstring.func_overload с флагом, определяющим, в факте перегруженности каких функций мы сейчас заинтересованы. Обычно реализуя какие-нибудь подобные вещи, я конструирую метод инициализации, который будет выполнять функцию конструктора для статического класса: Listing №2 (PHP) /** * @static * @param int $Mode self::MODE_MULTIBYTE * @param string $Encoding 'UTF-8' */ public static function Init($Mode = self::MODE_MULTIBYTE, $Encoding = 'UTF-8') { self::$_Mode = $Mode; if (!function_exists('mb_substr')) { self::$_Mode = self::MODE_ANSII; } else { mb_internal_encoding($Encoding); } self::$_Encoding = $Encoding; self::$_OverloadBitmask = ini_get('mbstring.func_overload'); }
Необходимо вызвать этот метод, перед первым использованием данного класса. Конечно, вы можете автоматизировать этот процесс по умолчанию, но я обычно не пользуюсь такими штуками, а люблю задавать всё явно. Теперь в конфигурации нашего проекта мы может инициализировать работу класса такой строкой: Listing №3 (PHP) Str::Init(Str::MODE_MULTIBYTE, 'UTF-8');
Итак, всё готово. Теперь непосредственно реализуем то, для чего мы это всё затеяли. Не забываем согласовывать интерфейс с родными функциями. Конечно для паттерна фасад (Facade) это не обязательно, но в данном случае это хороший плюс. В итоге мы получаем приблизительно такую общую картину: Listing №4 (PHP) class Str { const MODE_ANSII = 0; const MODE_MULTIBYTE = 1; /** * Текущий режим работы * Принимает значения * MODE_MULTIBYTE для работы в многобайтовом режиме * или MODE_ANSII в однобайтовом * По умолчанию в многобайтовом * @static int */ private static $_Mode = 1; /** * Текущая кодировка * По умолчанию UTF-8 * @static stirng */ private static $_Encoding = 'UTF-8'; /** * Значение параметра mbstring.func_overload * @static array */ private static $_OverloadBitmask = 0; /** * @static * @param int $Mode self::MODE_MULTIBYTE * @param string $Encoding 'UTF-8' */ public static function Init($Mode = self::MODE_MULTIBYTE, $Encoding = 'UTF-8') { self::$_Mode = $Mode; if (!function_exists('mb_substr')) { self::$_Mode = self::MODE_ANSII; } else { mb_internal_encoding($Encoding); } self::$_Encoding = $Encoding; self::$_OverloadBitmask = ini_get('mbstring.func_overload'); } /** * @static * @return string */ public static function GetCharset() { return self::$_Encoding; } /** * @static * @param string $String * @param int $Start * @param int $Length * @return string */ public static function Substr($String, $Start, $Length = NULL) { if (self::_NeedUseMBFuncs(2)) { if (NULL === $Length) { return mb_substr($String, $Start); } else { return mb_substr($String, $Start, (int)$Length, self::$_Encoding); } } else { if (NULL === $Length) { return substr($String, $String, $Length); } else { return substr($String, $String); } } } /** * @static * @param string $String * @return int */ public static function Strlen($String) { if (self::_NeedUseMBFuncs(2)) { return mb_strlen($String, self::$_Encoding); } else { return strlen($String); } } /** * @static * @param string $String * @return string */ public static function Strtolower($String) { return (self::_NeedUseMBFuncs(2)) ? mb_strtolower($String, self::$_Encoding) : strtolower($String); } /** * @static * @param string $String * @return string */ public static function Strtoupper($String) { return (self::_NeedUseMBFuncs(2)) ? mb_strtoupper($String, self::$_Encoding) : strtoupper($String); } /** * @static * @param string $HayStack * @param string $Needle * @param int $Offset * @return int */ public static function Strpos($Haystack, $Needle, $Offset = NULL) { if (self::_NeedUseMBFuncs(2)) { return mb_strpos($Haystack, $Needle, $Offset, self::$_Encoding); } else { return strpos($Haystack, $Needle, $Offset); } } /** * @static * @param string $HayStack * @param string $Needle * @param int $Offset * @return int */ public static function Strrpos($Haystack, $Needle, $Offset = NULL) { if (self::_NeedUseMBFuncs(2)) { return mb_strrpos($Haystack, $Needle, $Offset, self::$_Encoding); } else { return strrpos($Haystack, $Needle, $Offset); } } /** * @static * @param string $Pattern * @param string $String * @param int $Limit = NULL */ public static function Split($Pattern, $String, $Limit = NULL) { if (self::_NeedUseMBFuncs(2)) { if (NULL === $Limit) { return mb_split($Pattern, $String); } else { return mb_split($Pattern, $String, $Limit); } } else { if (NULL === $Limit) { return split($Pattern, $String); } else { return split($Pattern, $String, $Limit); } } } /** * @static * @param string $To * @param string $Subject * @param string $Message * @param string $AdditionalHeaders = NULL * @param string $AdditionalParameters * @return bool */ public static function Mail($To, $Subject, $Message, $AdditionalHeaders = NULL, $AdditionalParameters = NULL) { if (self::_NeedUseMBFuncs(1)) { if (NULL === $AdditionalHeaders) { return mb_send_mail($To, $Subject, $Message); } else { if (NULL === $AdditionalParameters) { return mb_send_mail($To, $Subject, $Message, $AdditionalHeaders); } else { return mb_send_mail($To, $Subject, $Message, $AdditionalHeaders, $AdditionalParameters); } } } else { if (NULL === $AdditionalHeaders) { return mail($To, $Subject, $Message); } else { if (NULL === $AdditionalParameters) { return mail($To, $Subject, $Message, $AdditionalHeaders); } else { return mail($To, $Subject, $Message, $AdditionalHeaders, $AdditionalParameters); } } } } /** * Возвращает TRUE если нужно вызвать mb_* функцию * @param int $OvelroadBitmask - флаг выбора возможных перезагруженных функций * @return bool */ private static function _NeedUseMBFuncs($OvelroadBitmask) { return (self::$_Mode && self::$_OverloadBitmask) ? (!($OvelroadBitmask & self::$_OverloadBitmask)) : self::$_Mode; } }
Теперь, работа со строками будет проводиться в таком стиле: Listing №5 (PHP) $RequestString = Str::Strtolower($_SERVER['REQUEST_URI']);
Если вы заметили, я описал не все функции. Для большинства задач этих будет достаточно, но если вы настаиваете, то можете дописать их сами, обратившись к документации. Многие функции, например str_replace, не попадают в нашу категорию, поэтому вы можете оставить её как есть или, для полной унификации интерфейса работы со строками, просто добавить в разработанный класс методы с их использованием. Это сделает наш фасад более "чистым" по отношению к предмету его действия. Также вы можете не вызывать каждый раз метод _NeedUseMBFuncs, а при инициализации заполнить необходимые свойства. Оставляю это на ваше усмотрение. Продолжение следует...
Comments
|
епт =)
а что мешает использовать сразу mb_* ?
и не городить велосипедов