Индексный доступ к Multibyte-строкам на PHP или изучение ООП на практике

от автора

Предыстория: вот, кажется, совсем недавно фирма, в которой я трудился Delphi программистом, издала последний вздох и развалилась. Юридически возможно и нет, но большое число сотрудников начало искать себе новое место работы, в том числе и я. Не буду дискутировать на счет востребованости сейчас desktop подхода, да и актуальности Delphi, но я решил воспользоваться сложившейся ситуацией для смены рода деятельности. А именно изучив предложения по трудоустройству в моем регионе (мелкий областной центр) я решил стать Web разработчиком на PHP. И в итоге мои НГ праздники прошли больше за книгой, нежели за праздничным столом.

Почти сразу я столкнулся с ситуацией меня немного смутившей: как язык, на котором крутится большинство сайтов интернета, в оном уже определилось абсолютное преимущество UTF-8, не имеет более-менее вменяемой его поддержки. Прочитав о стандартном методе решения – расширении mb_strings я успокоился, но некоторое неудобство в использовании оставило свой осадок. А именно: отсутствие метода доступа к символу как элементу массива и аналогов ряда стандартных функций в их мультибайт аналогах. Но задерживаться было нельзя, я и дальше штудировал литературу и по разным вопросам обращаясь к Google, но постоянно натыкался на топики начинающих о неудобстве работы с мультибайт строками и ожидании PHP6. Честно говоря, они мне даже поднадоели. Если в прямом виде аналогов в mb_strings нет, но все необходимое для собственной реализации было.

И вот, дойдя до темы о стандартных интерфейсы я увидел то, чего недоставало мне как новичку:

1. ООП подход для инкапсуляции информации о кодировке и методах обработки строки

class MBString {     private $string;     private $encoding;      public function __construct($string, $encoding = 'UTF-8') {         $this->string = $string;         $this->encoding = $encoding;     }      public function __toString() {         return (string)$this->string;     }      /**      * Длинна строки в символах      * @return int      */     function Length() {         return mb_strlen($this->string, $this->encoding);     }      /**      * Размер данных в байтах      * @return int      */     function Size() {         return strlen($this->string);     }      /**      * Возвращает кодировку строки      * @return string      */     function getEncoding() {         return $this->encoding;     }      /**      * Изменяет кодировку строки      * @param $encoding      */     function setEncoding($encoding) {         $this->string = mb_convert_encoding($this->string, $encoding, $this->encoding);         $this->encoding = $encoding;     }      /**      * Перечисляет поддерживаемые кодировки      * @return array      */     static function SupportEncodings() {         return mb_list_encodings();     }      /**      * Извлечение символа по индексу      * @param int $i      * @return string      */     function GetChar($i) {         return mb_substr($this->string, $i, 1, $this->encoding);     }      /**      * Устанавливает символ по индексу      * @param int $i      * @param string $char      */     function SetChar($i, $char) {         $this->string = mb_substr($this->string, 0, $i, $this->encoding)             .mb_substr($char, 0, 1, $this->encoding) //Защита на случай если передадут строку             .mb_substr($this->string, $i+1, $this->Length()-($i+1), $this->encoding);     }      /**      * Удаляет символ с индексом      * @param int $i      */     function UnSetChar($i){         $this->string = mb_substr($this->string, 0, $i, $this->encoding).mb_substr($this->string, $i+1, $this->Length()-($i+1), $this->encoding);     }      function UCFirst() {         $this->SetChar(0, mb_strtoupper($this->GetChar(0), $this->encoding));         return $this->string;     }      function UCWords() {         return $this->string = mb_convert_case($this->string, MB_CASE_TITLE, $this->encoding);     } } 

Ничего необычного в классе нет, он использует стандартные функции mb_strings для индексного доступа к символу строки, и далее уже на основе созданных методов приводится пример реализации пары функций недостающих в mb_strings: UCFirst и UCWords. В последствии класс можно дополнить всем чего не хватало именно вам. В классе реализован магический метод __toString() который оказался очень даже кстати для данного класса.

Пара замечаний:

  • так как при реализации подобного класса четко контролируются методы где размерность строки (в символах) может измениться, то рекомендуется реализовать метод Length кешируемым, т.е. хранить размер в приватной переменной и пересчитывать ее (mb_strlen($this->string, $this->encoding)) только в случае равенства null, об-null’ять же переменную во всех методах изменяющих размерность строки. По причине что вызовы Length частая операция, а функция mb_strlen не самая быстрая.
  • Можно добавить в класс функцию Add(MBString $string) для возможности объединения строк с учетом их кодировок (приводить присоединяемую строку к кодировке класса)
2. стандартного интерфейса Iterator (IteratorAggregate) для foreach

    protected $iterator_index = 0;      public function rewind() {         $this->iterator_index = 0;     }      public function current()     {         return $this->GetChar($this->iterator_index);     }      public function key() {         return $this->iterator_index;     }      public function next() {         ++$this->iterator_index;     }      public function valid() {         return ($this->iterator_index < $this->Length());     } 

Интерфейс Iterator требует реализации ряда простых методов, но позволит проходиться по массиву всеми так полюбившимся оператором foreach.

Замечания:

  • Обновите заголовок класса к виду class MBString implements Iterator
  • Существует так же альтернативный интерфейс IteratorAggregate с аналогичной функциональностью преимущество его в том, что можно «выкинуть» часть дублирующихся методов за пределы класса, а в самом классе реализовать только функцию getIterator возвращающую класс посредник. Реализация его тривиальна и практически ничем (кроме ссылки на родительский класс) не отличается от перечисленных выше методов.
3. интерфейса ArrayAccess для индексного доступа

    function offsetExists($offset) {         return ($offset < $this->Length());     }      public function offsetGet($offset) {         return $this->GetChar($offset);     }      public function offsetSet($offset, $value) {        $this->SetChar($offset, $value);     }      public function offsetUnset($offset) {         $this->UnSetChar($offset);     } 

Всего пара строк кода, но ООП и PHP теперь позволяют обращаться к символу строки как элементу массива, причем не зависимо от кодировки!

Замечания:

  • Обновите заголовок класса к виду class MBString implements Iterator, ArrayAccess
  • При изменении единичного символа все же необходимо учитывать кодировку (присваиваемый символ должен иметь кодировку строки в классе)
4. ну и интерфейса Countable для более полной эмуляции массивов

    public function count() {         return $this->Length();     } 

Теперь функцию count можно применять к классу и перебор строки циклом for будет сродни обходу массива.

Поовтрюсь: Обновите заголовок класса к виду class MBString implements Iterator, ArrayAccess, Countable

Пример использования

require_once('MBString.php'); $mbStr = new MBString('Здравствуй мир');  echo 'Использование магического __toString(): ',"$mbStr<br/>"; echo 'Length: ',$mbStr->Length(), ' Size: ', $mbStr->Size(), '<br/>'; echo '$mbStr[0]: ', $mbStr[0], '<br/>'; $mbStr->SetChar(0, 'z'); echo $mbStr, '<br/>'; echo 'UCFirst: ', $mbStr->UCFirst(), '<br/>'; echo 'UCWords: ', $mbStr->UCWords(), '<br/>'; foreach($mbStr as $k=>$v){     echo $v, '-'; } echo '<br>'; for ($i=0; $i< $mbStr->Length(); $i++){     echo $mbStr[$i], '+'; } 

Использование магического __toString(): Здравствуй мир Length: 14 Size: 27 $mbStr[0]: З zдравствуй мир UCFirst: Zдравствуй мир UCWords: Zдравствуй Мир Z-д-р-а-в-с-т-в-у-й- -М-и-р- Z+д+р+а+в+с+т+в+у+й+ +М+и+р+ 

P.S. Завтра… Вернее уже сегодня мое первое собеседование по PHP, так что всю критику и замечания учту позже.

ссылка на оригинал статьи http://habrahabr.ru/post/165107/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *