Пишу, по большей части, про историю, свою жизнь и немного про программирование.

PHP и UTF-8: пятый с половиной этап

После последнего, на данный момент, пятого этапа, меня опять потянуло на нецелые числа. Что поделать — я работаю над другим проектом, поэтому на полноценный этап я ничего не наскрёб, но работу потихоньку продолжаю. В прошлый раз я занимался оптимизацией — сделал итератор по UTF-8 строке.

Тогда же я упомянул, что мне понадобится ещё один итератор, чтобы итерировать строку с конца. Сегодня я его написал, решил выложить, наверняка ещё кому-то понадобится.

<?
    class UtfReverseString implements Iterator
    {
        protected $str;   // строка
        protected $idx;   // бинарный индекс строки
        protected $pos;   // UTF-8-индекс
        protected $curend;  // бинарный указатель на конец текущего символа

        protected $idx_start; // бинарный индекс откуда стартуем
        protected $pos_start; // UTF-8-индекс откуда стартуем

        // проверка — байт не является началом символа
        protected function isNotBegin($ch)
        {
            return (ord($ch) & 192) == 128;
        }

        public function __construct($str, $pos_start = 0)
        {
            $len = strlen($str);

            $this->str = $str;
            $this->idx = strlen($str); // этот указатель будет уточнён тут же, ниже
            $this->pos = strlen(utf8_decode($str)) - 1 - $pos_start; // лучше использовать mb_strlen, если есть возможность

            while ($pos_start-- >= 0) {
                $this->curend = --$this->idx;

                while ($this->idx > 0 && $this->isNotBegin($this->str[$this->idx])) {
                    $this->idx--;
                }
            }

            $this->idx_start = $this->idx;
            $this->pos_start = $this->pos;
        }

        public function current()
        {
            return substr($this->str, $this->idx, $this->curend - $this->idx + 1);
        }

        public function key()
        {
            return $this->pos;
        }

        public function next()
        {
            $this->pos--;
            $this->curend = $this->idx - 1;

            for ($this->idx--; $this->isNotBegin($this->str[$this->idx]); $this->idx--);
        }

        public function rewind()
        {
            $this->idx = $this->idx_start;
            $this->pos = $this->pos_start;
        }

        public function valid()
        {
            return $this->idx >= 0;
        }

        public function __toString()
        {
            return $this->str;
        }
    }

Использовать так же просто, как и предыдущий, «прямой» итератор:

$str = new UtfReverseString('Привет', 1);

    foreach ($str as $ch) {
        echo $ch, ' ';
    }

Второй параметр указывает с какого символа (с конца) нужно начинать итерироваться.