[ PHP ] Iterator インターフェースの活用 ( ArrayIterator, FilterIterator )

Pocket

ここでは、PHP において Iterator インターフェースを実装サンプルおよび活用方法について掲載しています。

スポンサーリンク

foreach による配列の繰り返し処理

PHP では配列などの要素全てに対して繰り返しの処理には、以下のように foreach 文を使用します。

// 繰り返し処理を行う配列
$nums = array("one" => 1, "two" => 2, "three" => 3);

foreach ($nums as $key => $value) {
    echo "Key: $key; Value: $value\n";
}

// 結果
//===============================================================
// Key: one; Value: 1
// Key: two; Value: 2
// Key: three; Value: 3
Iterator インターフェースによる繰り返し処理

次のサンプルは、前述のサンプルで示した配列に対する繰り返し処理を Iterator インターフェースを実装したオブジェクトに対して繰り返し処理を行うように修正したものになります。詳細はコメントを参照ください。

class myArray implements Iterator {

    private $_pos = 0;  // 現在の参照位置
    private $_data;     // データ配列

    // 配列に要素を追加
    public function add($item) { $this->_data[] = $item; }

    // 指し示している要素位置を先頭に戻す(インターフェースの実装)
    function rewind() { $this->_pos = 0; }

    // 現在指し示している要素を返す(インターフェースの実装)
    function current() { return $this->_data[$this->_pos]; }

    // 現在の要素位置を返す(インターフェースの実装)
    function key() { return $this->_pos; }

    // 次の要素位置を指し示すようにする(インターフェースの実装)
    function next() { ++$this->_pos; }

    // 現在位置が有効かどうかを調べる(インターフェースの実装)
    function valid() { return isset($this->_data[$this->_pos]); }
}

// 要素を追加
$it = new myArray();
$it->add("one");
$it->add("two");
$it->add("three");

foreach($it as $key => $value) {
    echo 'key:' . $key . '  value:' . $value . '\n';
}

// 結果(要素追加順に繰り返す)
//===============================================================
// key:0 value:one
// key:1 value:two
// key:2 value:three

foreach 文を記述することによって、Iterator インターフェースのメソッドが動的に呼び出され処理されています。言い換えると foreach 文を使用して呼び出す場合には Iterator インターフェースを実装すればよいことになります。また、インターフェースの実装はユーザーが決定するので、最後尾から先頭にかけて繰り返し処理を行うように変更することもできるようになります。変更部分のみ掲載しています。

    // 指し示している要素位置を最後尾にする
    function rewind() { $this->_pos = count($this->_data) - 1; }

    // 次(前)の要素位置を指し示すようにする
    function next() { --$this->_pos; }

// 結果(最後尾から先頭へ繰り返し。要素追加順の逆)
//===============================================================
// key:2 value:three
// key:1 value:two
// key:0 value:one
ArrayIterator ( GoF Iterator パターン )

ArrayIterator は、ArrayObject クラスで管理するデータを操作するイテレータです。次に示すサンプルコードは、IteratorAggregate インターフェイスを実装し、データ ( 配列 ) とその操作を管理するイテレータを個々のクラスに分割して作成しています。GoF デザインパターンの Iterator パターンのサンプルになります。詳細はコメントを参照ください。

// 個人クラス
class person
{
    private $_name; // 名前
    private $_age;  // 年齢

    // コンストラクタ
    public function __construct($name, $age)
    {
        $this->_name  = $name;
        $this->_age   = $age;
    }

    public function getInfo()
    {
        return $this->_name . ':' . $this->_age . 'years old';
    }
}

// 大衆クラス
class people implements IteratorAggregate
{
    private $_people;

    public function __construct()
    {
        $this->_people = new ArrayObject();
    }
    
    // person オブジェクトを配列に追加する
    public function add(person $person)
    {
        $this->_people[] = $person;
    }

    // IteratorAggregate インターフェースの実装
    public function getIterator()
    {
        // ArrayIterator クラスオブジェクト
        return $this->_people->getIterator();
        // または
        //return new ArrayIterator($this->_people);
    }
}


$people = new people();

$people->add(new person('masao', 18));
$people->add(new person('oyaji'  , 70));
$people->add(new person('okan'   , 60));
$people->add(new person('musuko' , 10));

// イテレータ(ArrayIterator)を取得する
$iterator = $people->getIterator();

// foreach を使用して反復処理を行う場合
foreach ($iterator as $person) {
    echo $person->getInfo() . '\n';
}
echo '\n';

// 上記の foreach 文で参照位置が終端まで来ているので
// 参照位置を初期化(先頭に戻す)する。忘れやすいので注意!!
$iterator->rewind();

// イテレータを操作して反復処理を行う場合
while($iterator->valid()) {
    $person = $iterator->current();
    echo $person->getInfo() . '\n';
    $iterator->next();
}
FilterIterator でデータのフィルタリング

FilterIterator は、反復処理で不要なデータをフィルタリングする機能を提供するクラスです。派生クラスでは少なくとも FilterIterator::accept() 抽象メソッドを実装する必要があります。

前述のサンプルコードを修正して反復処理で不要なデータをフィルタリングするようにします。大きな相違点は AgeFilterIterator クラスを新規に作成し、IteratorAggregate::getIterator() で AgeFilterIterator クラスのインスタンスをリターンしている部分です。詳細はコメントを参照ください。

// 個人クラス
class person
{
    private $_name;
    private $_age;

    // コンストラクタ
    public function __construct($name, $age)
    {
        $this->_name  = $name;
        $this->_age   = $age;
    }

    public function getInfo()
    {
        return $this->_name . ':' . $this->_age . 'years old';
    }

    public function getAge()
    {
        return $this->_age;
    }
}

// 大衆クラス
class people implements IteratorAggregate
{
    private $_people;

    public function __construct()
    {
        $this->_people = new ArrayObject();
    }

    public function add(person $person)
    {
        $this->_people[] = $person;
    }

    // IteratorAggregate インターフェースの実装
    public function getIterator()
    {
        // フィルタイテレータを返す(AgeFilterIterator )
        return new AgeFilterIterator($this->_people->getIterator());
    }
}

// 年齢で反復処理を行うデータをフィルタリングするイテレータ
class AgeFilterIterator extends FilterIterator
{
    public function __construct($iterator)
    {
        parent::__construct($iterator);
    }

    // FilterIterator::accept() 抽象メソッドの実装
    public function accept()
    {
        // 未成年である場合は true(処理対象とする)
        $person = $this->current();
        return ($person->getAge() < 20) ? true : false;
    }
}

// フィルタリング例
$people = new people();
$people->add(new person('masao', 18));
$people->add(new person('oyaji'  , 70));
$people->add(new person('okan'   , 60));
$people->add(new person('musuko' , 10));

// AgeFilterIterator を取得する
$iterator = $people->getIterator();

// foreach を使用して反復処理を行う場合
foreach ($iterator as $person) {
    echo $person->getInfo() . '\n';
}
echo '\n';

// 上記の foreach 文で参照位置が終端まで来ているので
// 参照位置を初期化(先頭に戻す)する
$iterator->rewind();

/*
 * イテレータを操作して反復処理を行う場合
 * $iterator->accept() の呼び出しは不要(内部で処理される)
 */
while($iterator->valid()) {
    $person = $iterator->current();
    echo $person->getInfo() . '\n';
    $iterator->next();
}

//表示結果(20歳以下を通すフィルタあり)
//============================
// masao:18years old
// musuko:10years old
//
// masao:18years old
// musuko:10years old
参考
スポンサーリンク


Pocket

Leave a Comment

Your email address will not be published. Required fields are marked *