Как известно, важнейшим элементом в языке программирования PHP являются массивы. И это не удивительно, так как практически всегда приходится работать с какими-либо наборами данных. В PHP массивы — ассоциативные, т.е., хранят пары ключ-значение. Ключ массива должен быть либо типа int, либо типа string, а значение может быть любого типа. Массивы в PHP поистине универсальны. (1)

Итерации с массивами

Вот пример операций с массивом:

<?php

$array = [
   'a' => 1,
   'b' => 2,
   'c' => [ 'd', 'e', 'f' ],
];

// добавляем новое значение в массив
$array['x'] = 'Значение 1';

// а здесь индекс формируется автоматически, ( он будет числовой )
$array[] = 'Значение 2';

// в PHP 7 появилась новая особенность
// "распаковка" массива, операция-синоним конструкции list()
[$a, $b, $c, $x, $n ] = $array;

// тоже самое, что и пример выше

list($a, $b, $c, $x, $n) = $array;

?>

Есть около сотни функций для работы с массивами в PHP. С ними можно ознакомиться на интернет представительстве языка.

В добавок ко всем этим функциям, самым основным свойством массивом — является возможность перебора всех частей, для получения или ключа, или значения, или того и другого сходу. Т.е. итерация по массиву, либо процесс прохода по массиву в PHP играет только принципиальное значение.

И, невзирая на простоту идеи итерации, за ней кроются массивные возможности, которые можно применять для перебора всех видов коллекций данных. Практически процесс итерации обеспечивает нам возможность идентично работать с хоть каким итерируемым (перебираемым) объектом, но не лишь с массивом. Т.е. мы можем воплотить последующее:

<?php

  class MyClass
  {

    // общедоступное поле
    public $public = "Public Property";

    // защищенное поле
    protected $protected = "Protected Property";

    // закрытое поле
    private $private = "Private Property";
  }

  $myclass = new MyClass();

  foreach( $myclass as $prop ) {
    echo $prop, PHP_EOL;
  }

  // Напечатает Public Property

?>

Как видно из примера выше, мы можем пройтись по объекту как будто по массиву. Однако написано будет лишь значение общедоступного свойство, в связи с тем, что защищенные и закрытые поля класса не могут быть прочитаны в цикле. Однако, практическая полезность итерации по свойствам объекта непонятна, она указывает, что объекты в PHP являются итерируемыми сущностями.

Интерфейс Iterator

Как же организовать итерацию по такому объекту, у которого нет публичных свойств? И как вообще организовать итерацию по какому-то собственному нестандартному алгоритму? (2)

Для реализации собственных алгоритмов итерации PHP (а точнее SPL) предоставляет специальный интерфейс Iterator, состоящий из пяти методов:

// Метод должен вернуть значение текущего элемента
public function current();

// Метод должен вернуть ключ текущего элемента
public function key();

// Метод должен сдвинуть "указатель" на следующий элемент
public function next(): void;

// Метод должен поставить "указатель" на первый элемент
public function rewind(): void;

// Метод должен проверять - не вышел ли указатель за границы?
public function valid(): bool

Ваш класс должен реализовать эти методы и тогда вы получите возможность итерировать объекты этого класса с помощью цикла foreach в соответствии с реализованным алгоритмом.

«Указатель», который упоминается здесь в описании методов интерфейса Iterator — чистая абстракция, в отличие от реально существующего внутреннего указателя массивов. Только от вас зависит, как именно вы реализуете эту абстракцию, важен только результат — например последовательный вызов методов rewind() и current() обязан вернуть значение первого элемента.

Пример Итератора

Теперь перейдем непосредственно к примеру. При создании сайтов, а точнее, при написании их программной части, часто используем класс, представляющий хранилище каких-либо настроек, которые можно добавлять и затем получать при необходимости. Такой класс может называться, например, Registry. Вот код: (1)

<?php

  // класс хранилища данных
  class Registry
  {
    // массив, в котором хранятся настройки
    private $options = [];

    // метод для добавления настройки в хранилище
    public function set($option, $value)
    {
        $this->options[$option] = $value;
    }

    // метод для получения настройки из хранилища
    public function get($option) {
    {
        return $this->options[$option];
    }
  }
  
?>

Как видно, это простой класс для работы с настройками приложения. Однако, что, если вдруг нам нужно будет пройтись по всем настройкам в хранилище и сделать с ними какую-либо операцию. Но ведь у класса Registry нет открытых свойств, что делать в этом случае?

Первое, что приходит в голову это добавить новый открытый метод getOptions() который будет возвращать массив настроек целиком, однако это плохая идея, так как любое случайное изменение в нем может повлиять на все приложение. Поэтому нам нужно другое решение, и это решение — итераторы. В классе Registry мы реализуем интерфейс Iterator. Код:

<?php

// класс хранилища данных, реализующий интерфейс Iterator
class Registry implements Iterator
{
    // массив, в котором хранятся настройки
    private $options = [];

    // возвращает текущий элемент
    public function current()
    {
      return current($this -> options);
    }

    // возвращает ключ текущего элемента
    public function key()
    {
      return key($this -> options);
    }

    // передвигаемся вперед на один элемент

    public function next()
    {
      next($this -> options);
    }

    // возвращает указатель на начало массива
    // фактически мы начинаем считать заново с нуля
    public function rewind()
    {
      reset($this -> options);
    }

    // проверяет, достигли ли мы конца массива
    public function valid()
    {
      return current($this -> options) !== false;
    }

    // метод для добавления настройки в хранилище
    public function set($option, $value)
    {
      $this -> options[$option] = $value;
      return $this;
    }

    // метод для получения настройки из хранилища
    public function get($option)
    {       
        return $this -> options[$option];
    }
}

?>

Запуск:

<?php

 // создали объект
 $reg = new Registry();

 // добавили настройки
 $reg -> set("DS",DIRECTORY_SEPARATOR)
    -> set("APP_HOME", '.')
    -> set("AUTO_RELOAD",true)

 // и прошлись по настройкам.
 // обратите внимание - никаких массивов, только объект $reg
 foreach ( $reg as $option => $value ) {
    echo $option, " = ", $value, PHP_EOL;
 }

?>

Вывод: ( см. https://3v4l.org/fMaTi )

DS = /
APP_HOME = .
AUTO_RELOAD = 1

Таким образом, мы можем итерировать объекты в PHP, так как будто, они являются массивами.

Поэтому итераторы в PHP — это очень мощная технология, и в стандартной библиотеке PHP есть десятки уже готовых итераторов, которые позволяют буквально в пару строк кода выполнить далеко нетривиальные задачи, такие как, например, рекурсивный обход каталога, фильтрация списка на основании некоторого условия или регулярного выражения и т.д.


Источники:

(1) https://myrusakov.ru/php-iterators-intro.html

(2) https://habr.com/ru/post/324934/

Tags

Нет комментариев

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

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

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.