/ Gists / 15. Iterator
On gists

15. Iterator

PHP Patterns

pic.md Raw #

CsvIterator.php Raw #

<?php

/**
 * 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);
}

BookIterator.php Raw #

<?php

class Book
{
    private string $author;
    private string $title;

    public function __construct(string $title, string $author)
    {
        $this->author = $author;
        $this->title = $title;
    }

    public function getAuthor(): string
    {
        return $this->author;
    }

    public function getTitle(): string
    {
        return $this->title;
    }

    public function getAuthorAndTitle(): string
    {
        return $this->getTitle().' by '.$this->getAuthor();
    }
}


use Countable;
use Iterator;

class BookList implements Countable, Iterator
{
    /**
     * @var Book[]
     */
    private array $books = [];
    private int $currentIndex = 0;

    public function addBook(Book $book)
    {
        $this->books[] = $book;
    }

    public function removeBook(Book $bookToRemove)
    {
        foreach ($this->books as $key => $book) {
            if ($book->getAuthorAndTitle() === $bookToRemove->getAuthorAndTitle()) {
                unset($this->books[$key]);
            }
        }

        $this->books = array_values($this->books);
    }

    public function count(): int
    {
        return count($this->books);
    }

    public function current(): Book
    {
        return $this->books[$this->currentIndex];
    }

    public function key(): int
    {
        return $this->currentIndex;
    }

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

    public function rewind()
    {
        $this->currentIndex = 0;
    }

    public function valid(): bool
    {
        return isset($this->books[$this->currentIndex]);
    }
}


// USAGE

        $book = new Book('Clean Code', 'Robert C. Martin');
        $book2 = new Book('Professional Php Design Patterns', 'Aaron Saray');

        $bookList = new BookList();
        $bookList->addBook($book);
        $bookList->addBook($book2);
        $bookList->removeBook($book);

        $books = [];
        foreach ($bookList as $book) {
            $books[] = $book->getAuthorAndTitle();
        }

        $this->assertSame(
            ['Professional Php Design Patterns by Aaron Saray'],
            $books
        );

EmployeeIterator.php Raw #

<?php

class Employee {

    public $name;
    public $position;

    public function __construct (string $name, string $position) {
        $this->name = $name;
        $this->position = $position;
    }
}


use SplObjectStorage, IteratorAggregate, Countable;

class CompanyEmployeeTeam implements IteratorAggregate, Countable {

    public $person;
    private $subordinates;

    public function __construct (Employee $person) {
        $this->subordinates = new SplObjectStorage();
        $this->person = $person;
    }

    public function addSubordinate (CompanyEmployeeTeam $subordinate): void {
        $this->subordinates->attach($subordinate);
    }

    public function removeSubordinate (CompanyEmployeeTeam $subordinate): void {
        $this->subordinates->detach($subordinate);
    }

    public function getSubordinates (): SplObjectStorage {
        return $this->subordinates;
    }

    public function getIterator (): EmployeeTeamIterator {
        return new EmployeeTeamIterator($this);
    }

    public function count (): int {
        return count(new EmployeeTeamIterator($this));
    }

    public function getName (): string {
        return $this->person->name;
    }

    public function getPosition (): string {
        return $this->person->position;
    }
}


use Iterator, Countable;

class EmployeeTeamIterator implements Iterator, Countable {

    private $position = 0;
    private $teamMembers = [];

    public function __construct (CompanyEmployeeTeam $employee) {
        $this->addTeam($employee);
        $this->position = 0;
    }

    protected function addTeam (CompanyEmployeeTeam $employee): void {
        foreach ($employee->getSubordinates() as $member) {
            array_push($this->teamMembers, $member);
            $this->addTeam($member);
        }
    }

    public function current (): CompanyEmployeeTeam {
        return $this->teamMembers[$this->position];
    }

    public function next (): void {
        ++$this->position;
    }

    public function key (): int {
        return $this->position;
    }

    public function valid (): bool {
        return isset($this->teamMembers[$this->position]);
    }

    public function rewind (): void {
        $this->position = 0;
    }

    public function count (): int {
        return count($this->teamMembers);
    }
}


// USAGE

$john = new CompanyEmployeeTeam(new Employee("John", 'Director'));
$tom = new CompanyEmployeeTeam(new Employee("Tom", 'Developer'));
$johny = new CompanyEmployeeTeam(new Employee("Johny", 'Developer'));
$ceo = new CompanyEmployeeTeam(new Employee("Mark", "CEO"));

$john->addSubordinate($tom);
$john->addSubordinate($johny);
$ceo->addSubordinate($john);

showTeam($ceo);

$jan = new CompanyEmployeeTeam(new Employee("Jan", 'Developer'));
$ceo->addSubordinate($jan);

showTeam($ceo);

$ceo->removeSubordinate($jan);

showTeam($ceo);

function showTeam ($users) {
    foreach ($users as $user) {
        echo $user->getPosition() . ": " . $user->getName() . PHP_EOL;
    }
    echo "Number of subordinates of " . $users->getName() . ": " . count($users) . PHP_EOL;
}