Каждый из 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, а при инициализации заполнить необходимые свойства. Оставляю это на ваше усмотрение.
Продолжение следует...