
PHP로 작성된 반복자
반복자는 복잡한 데이터 구조의 내부 세부 정보를 노출하지 않고 해당 구조를 차례대로 순회할 수 있도록 하는 행동 디자인 패턴입니다.
반복자 덕분에 클라이언트들은 단일 반복기 인터페이스를 사용하여 유사한 방식으로 다른 컬렉션들의 요소들을 탐색할 수 있습니다.
복잡도:
인기도:
적용 예시들: 이 패턴은 PHP에 자주 사용됩니다. 많은 프레임워크들과 라이브러리들은 이 패턴을 컬렉션을 탐색하는 표준 방법을 제공하기 위해 사용합니다.
PHP 언어에는 내장된 반복자 인터페이스가 있습니다. 이 인터페이스는 나머지 PHP 코드와 호환되는 사용자 정의된 반복자를 구축하는 데 사용할 수 있습니다.
식별법: 반복자는 그의 탐색 메서드들(예: next
, previous
등)로 쉽게 인식할 수 있습니다. 또 반복자를 사용하는 클라이언트 코드는 반복자가 순회하는 컬렉션을 직접 접근하지 못할 수도 있습니다.
개념적인 예시
이 예시는 반복자 디자인 패턴의 구조를 보여주고 다음 질문에 중점을 둡니다:
- 패턴은 어떤 클래스들로 구성되어 있나요?
- 이 클래스들은 어떤 역할을 하나요?
- 패턴의 요소들은 어떻게 서로 연관되어 있나요?
이 패턴의 구조를 배우면 실제 PHP 사용 사례를 기반으로 하는 다음 예시를 더욱 쉽게 이해할 수 있을 것입니다.
index.php: 개념적인 예시
<?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: 실행 결과
Straight traversal: First Second Third Reverse traversal: Third Second First
구상적인 실제 예시
PHP에는 이미 foreach 루프와의 편리한 통합을 제공하는 내장된 반복자 인터페이스가 있기 때문에 상상할 수 있는 거의 모든 종류의 데이터 구조를 순회하기 위한 자체 반복자를 쉽게 만들 수 있습니다.
이 반복자 패턴의 예시에서는 사용자가 CSV 파일에 쉽게 접근할 수 있도록 합니다.
index.php: 개념적인 예시
<?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: 실행 결과
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 )