Iterator w języku PHP
Iterator to behawioralny wzorzec projektowy pozwalający sekwencyjnie przechodzić od elementu do elementu jakiegoś zbioru bez konieczności eksponowania jego formy.
Dzięki Iteratorowi klienci mogą przeglądać kolejne elementy różnych kolekcji w podobny sposób, za pośrednictwem jednego interfejsu.
Złożoność:
Popularność:
Przypadki użycia: Wzorzec Iterator jest bardzo rozpowszechniony w kodzie PHP. Wiele frameworków i bibliotek pozwala za jego pomocą poruszać się po elementach ich kolekcji.
PHP posiada wbudowany interfejs Iterator na podstawie którego można budować własne iteratory kompatybilne z resztą kodu PHP.
Identyfikacja: Iterator łatwo rozpoznać po obecności metod nawigacyjnych (takich jak następny, poprzedni i innych). Kod klienta stosujący iteratory może nie mieć bezpośredniego dostępu do badanej kolekcji.
Przykład koncepcyjny
Poniższy przykład ilustruje strukturę wzorca Iterator ze szczególnym naciskiem na następujące kwestie:
- Z jakich składa się klas?
- Jakie role pełnią te klasy?
- W jaki sposób elementy wzorca są ze sobą powiązane?
Poznawszy strukturę wzorca będzie ci łatwiej zrozumieć następujący przykład, oparty na prawdziwym przypadku użycia PHP.
index.php: Przykład koncepcyjny
<?php
namespace RefactoringGuru\Iterator\Conceptual;
/**
* Concrete Iterators implement various traversal algorithms. These classes
* store the current traversal position at all times.
*/
class AlphabeticalOrderIterator implements \Iterator
{
/**
* @var WordsCollection
*/
private $collection;
/**
* @var int Stores the current traversal position. An iterator may have a
* lot of other fields for storing iteration state, especially when it is
* supposed to work with a particular kind of collection.
*/
private $position = 0;
/**
* @var bool This variable indicates the traversal direction.
*/
private $reverse = false;
public function __construct($collection, $reverse = false)
{
$this->collection = $collection;
$this->reverse = $reverse;
}
public function rewind()
{
$this->position = $this->reverse ?
count($this->collection->getItems()) - 1 : 0;
}
public function current()
{
return $this->collection->getItems()[$this->position];
}
public function key()
{
return $this->position;
}
public function next()
{
$this->position = $this->position + ($this->reverse ? -1 : 1);
}
public function valid()
{
return isset($this->collection->getItems()[$this->position]);
}
}
/**
* Concrete Collections provide one or several methods for retrieving fresh
* iterator instances, compatible with the collection class.
*/
class WordsCollection implements \IteratorAggregate
{
private $items = [];
public function getItems()
{
return $this->items;
}
public function addItem($item)
{
$this->items[] = $item;
}
public function getIterator(): Iterator
{
return new AlphabeticalOrderIterator($this);
}
public function getReverseIterator(): Iterator
{
return new AlphabeticalOrderIterator($this, true);
}
}
/**
* The client code may or may not know about the Concrete Iterator or Collection
* classes, depending on the level of indirection you want to keep in your
* program.
*/
$collection = new WordsCollection();
$collection->addItem("First");
$collection->addItem("Second");
$collection->addItem("Third");
echo "Straight traversal:\n";
foreach ($collection->getIterator() as $item) {
echo $item . "\n";
}
echo "\n";
echo "Reverse traversal:\n";
foreach ($collection->getReverseIterator() as $item) {
echo $item . "\n";
}
Output.txt: Wynik działania
Straight traversal:
First
Second
Third
Reverse traversal:
Third
Second
First
Przykład z prawdziwego życia
Ponieważ PHP posiada wbudowany interfejs Iterator, pozwalający na wygodną integrację z pętlami foreach, łatwo stworzyć własne iteratory mogące przechodzić sekwencyjnie po elementach niemal każdej struktury danych, jaką można sobie wyobrazić.
Poniższy przykład użycia wzorca Iterator daje łatwy dostęp do plików CSV.
index.php: Przykład z prawdziwego życia
<?php
namespace RefactoringGuru\Iterator\RealWorld;
/**
* CSV File Iterator.
*
* @author Josh Lockhart
*/
class CsvIterator implements \Iterator
{
const ROW_SIZE = 4096;
/**
* The pointer to the CSV file.
*
* @var resource
*/
protected $filePointer = null;
/**
* The current element, which is returned on each iteration.
*
* @var array
*/
protected $currentElement = null;
/**
* The row counter.
*
* @var int
*/
protected $rowCounter = null;
/**
* The delimiter for the CSV file.
*
* @var string
*/
protected $delimiter = null;
/**
* The constructor tries to open the CSV file. It throws an exception on
* failure.
*
* @param string $file The CSV file.
* @param string $delimiter The delimiter.
*
* @throws \Exception
*/
public function __construct($file, $delimiter = ',')
{
try {
$this->filePointer = fopen($file, 'rb');
$this->delimiter = $delimiter;
} catch (\Exception $e) {
throw new \Exception('The file "' . $file . '" cannot be read.');
}
}
/**
* This method resets the file pointer.
*/
public function rewind(): void
{
$this->rowCounter = 0;
rewind($this->filePointer);
}
/**
* This method returns the current CSV row as a 2-dimensional array.
*
* @return array The current CSV row as a 2-dimensional array.
*/
public function current(): array
{
$this->currentElement = fgetcsv($this->filePointer, self::ROW_SIZE, $this->delimiter);
$this->rowCounter++;
return $this->currentElement;
}
/**
* This method returns the current row number.
*
* @return int The current row number.
*/
public function key(): int
{
return $this->rowCounter;
}
/**
* This method checks if the end of file has been reached.
*
* @return bool Returns true on EOF reached, false otherwise.
*/
public function next(): bool
{
if (is_resource($this->filePointer)) {
return !feof($this->filePointer);
}
return false;
}
/**
* This method checks if the next row is a valid row.
*
* @return bool If the next row is a valid row.
*/
public function valid(): bool
{
if (!$this->next()) {
if (is_resource($this->filePointer)) {
fclose($this->filePointer);
}
return false;
}
return true;
}
}
/**
* The client code.
*/
$csv = new CsvIterator(__DIR__ . '/cats.csv');
foreach ($csv as $key => $row) {
print_r($row);
}
Output.txt: Wynik działania
Array
(
[0] => Name
[1] => Age
[2] => Owner
[3] => Breed
[4] => Image
[5] => Color
[6] => Texture
[7] => Fur
[8] => Size
)
Array
(
[0] => Steve
[1] => 3
[2] => Alexander Shvets
[3] => Bengal
[4] => /cats/bengal.jpg
[5] => Brown
[6] => Stripes
[7] => Short
[8] => Medium
)
Array
(
[0] => Siri
[1] => 2
[2] => Alexander Shvets
[3] => Domestic short-haired
[4] => /cats/domestic-sh.jpg
[5] => Black
[6] => Solid
[7] => Medium
[8] => Medium
)
Array
(
[0] => Fluffy
[1] => 5
[2] => John Smith
[3] => Maine Coon
[4] => /cats/Maine-Coon.jpg
[5] => Gray
[6] => Stripes
[7] => Long
[8] => Large
)