Предыстория: вот, кажется, совсем недавно фирма, в которой я трудился 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, так что всю критику и замечания учту позже.
Автор: salopot