Почти сразу я столкнулся с ситуацией меня немного смутившей: как язык, на котором крутится большинство сайтов интернета, в оном уже определилось абсолютное преимущество 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/
Добавить комментарий