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


Продолжение следует...
Теги:
Facade, шаблон, паттерн, работа со строками, кодировки, framework
Добавлено: 08 Августа 2018 07:48:52 Добавил: Андрей Ковальчук Нравится 0
Добавить
Комментарии:
Нету комментариев для вывода...