LoginSignup
6
7

More than 3 years have passed since last update.

PHP オブジェクトを配列のように扱うためのインターフェース ItetatorAggregateやArrayAccessなど

Posted at

はじめに

PHPには、いくつかの定義済みインターフェースがあります。その中でも特に、以下のインターフェースを実装することで、オブジェクトを配列のように振る舞わせることが可能です。

  • Traversable
  • Iterator
  • IteratorAggregate
  • ArrayAccess
  • Countable

これらのインターフェースを満足させる方法を見ていきます。

Traversable

Traversableインターフェースは、foreach文でループ可能にするためのインターフェースです。しかし、Traversableはこれは内部エンジンのインターフェイスであり、PHP スクリプト内で実装することはできません。かわりにTraversableを実装したIteratorかIteratorAggregateを使用する必要があります。

<?php

// Fatal error: Class SomeClass must implement interface Traversable as part of either Iterator or IteratorAggregate in Unknown on line 0
class SomeClass implements Traversable
{

}

概要

抽象インターフェースですので、メソッドはありません。
php
Traversable {
}

Iterable型

arrayまたはTraversableを実装したクラスは、疑似型Iterableとして扱われます。
Iterable型は例えば関数の引数の型指定として利用できます。関数の引数の型にIterable型が指定されている場合、安全にforeach文で利用することができます。


function foo(iterable $iterable) {
    foreach ($iterable as $value) {
        // ...
    } 
}

また、関数is_iterableは変数の型がIterableかチェックします。

Iterator

前述の通り、Iteratorインターフェースは抽象インターフェースTraversableを継承しており、このインターフェースを実装したクラスはforeach文でループ可能であることを表します。
Iteratorインターフェースを満足させるために、以下の5つのメソッドを実装する必要があります。

概要

Iterator extends Traversable {
/* メソッド */
abstract public current ( ) : mixed
abstract public key ( ) : scalar
abstract public next ( ) : void
abstract public rewind ( ) : void
abstract public valid ( ) : bool
}

Iteratorを継承したクラスの例

<?php

class MyIterator implements Iterator
{
  private $position = 0;
  private $values = [];

  public function __construct(...$values)
  {
    $this->values = $values;
  }

  // 参照している要素を先頭へ戻す
  public function rewind()
  {
    $this->position = 0;
  }

  // 現在参照している要素を返す
  public function current()
  {
    return $this->values[$this->position];
  }

  // 現在参照している要素のキーを返す
  public function key()
  {
    return $this->position;
  }

  // 次の要素を参照する
  public function next()
  {
    $this->position++;
  }

  // 現在参照している値が有効かどうか
  public function valid()
  {
    return isset($this->values[$this->position]);
  }
}

$iterator = new MyIterator('apple', 'banana', 'chocolate');

foreach ($iterator as $key => $value) {
  echo $key . ' ' . $value . PHP_EOL;
}

// 0 apple
// 1 banana
// 2 chocolate

IteratorAggregate

Iteratorインターフェースを実装するためには必要なメソッドが多く、少々面倒です。
IteratorAggregateは同じくTraversableインターフェースを継承していますが、こちらはItetatorに処理を委譲します。
そのため、実装するめそっでゃは別のイテレータを返すgetIterator()のみとシンプルになっています。

概要

IteratorAggregate extends Traversable {
/* メソッド */
abstract public getIterator ( ) : Traversable
}

ItetatorAggregateを実装したクラスの例

<?php

class MyIterator implements IteratorAggregate
{
  private $values = [];

  public function __construct(...$values)
  {
    $this->values = $values;
  }

  public function getIterator() 
  {
    return new ArrayIterator($this->values);
  }
}

$iterator = new MyIterator('apple', 'banana', 'chocolate');

foreach ($iterator as $key => $value) {
  echo $key . ' ' . $value . PHP_EOL;
}

// 0 apple
// 1 banana
// 2 chocolate

getIterator()の戻り値は、Traversableインターフェース型である必要があります。
この例ではシンプルなイテレータであるArrayIteratorを使用しましたが、SPLには、様々なイテレータが定義されています。

  • AppendIterator
  • ArrayIterator
  • CachingIterator
  • CallbackFilterIterator
  • DirectoryIterator
  • EmptyIterator
  • FilesystemIterator
  • FilterIterator
  • GlobIterator
  • InfiniteIterator
  • IteratorIterator
  • LimitIterator
  • MultipleIterator
  • NoRewindIterator
  • ParentIterator
  • RecursiveArrayIterator
  • RecursiveCachingIterator
  • RecursiveCallbackFilterIterator
  • RecursiveDirectoryIterator
  • RecursiveFilterIterator
  • RecursiveIteratorIterator
  • RecursiveRegexIterator
  • RecursiveTreeIterator
  • RegexIterator

https://www.php.net/manual/ja/spl.iterators.php

ArrayAccess

ArrayAccessインターフェースは、オブジェクトに対して配列形式でアクセスを可能にするインターフェースです。つまり、$obj['foo']のように要素を取得したり、$obj['foo']のように要素に値を設定できるということです。

ArrayAccessインターフェースを満足させるために、以下の4つのメソッドを実装する必要があります。

  • offsetGet
    • 角括弧[]で要素を取得する際に呼ばれる
  • offsetSet()
    • 角括弧[]で要素に値を設定する歳に呼ばれる
  • offsetExists()
    • isset()empty()に渡された時に呼ばれる
  • offsetUnset()
    • unset()に渡された時に呼ばれる

実装

ArrayAccess {
/* メソッド */
abstract public offsetExists ( mixed $offset ) : bool
abstract public offsetGet ( mixed $offset ) : mixed
abstract public offsetSet ( mixed $offset , mixed $value ) : void
abstract public offsetUnset ( mixed $offset ) : void
}

ArrayAccessを実装したクラスの例

<?php

class Cart implements ArrayAccess
{
  private $items = [];

  public function __construct(array $items)
  {
    $this->items = $items;
  }

  public function offsetGet($offset)
  {
    echo 'offsetGet called!'. PHP_EOL;
    return isset($this->items[$offset]) ? $this->items[$offset] : null;
  }

  public function offsetSet($offset, $item)
  {
    echo 'offsetSet called!'. PHP_EOL;
      if (is_null($offset)) {
        $this->items[] = $item;
    } else {
        $this->items[$offset] = $item;
    }
  }

  public function offsetExists($offset)
  {
    echo 'offsetExists called!' . PHP_EOL;
    return isset($this->items[$offset]);
  }

  public function offsetUnset($offset)
  {
    echo 'offsetUnset called!' . PHP_EOL;
    unset($this->items[$offset]);
  }
}

$items = [
  'apple' => 150,
  'banana' => 100,
  'chocolate' => 200
];

$cart = new Cart($items);

echo $cart['apple'] . PHP_EOL;
$cart['doughnut'] = 120;
echo isset($cart['strawberry']) . PHP_EOL;
unset($cart['banana']);

print_r($cart);

実行結果

offsetGet called!
150
offsetSet called!
offsetExists called!
bool(false)
offsetUnset called!
Cart Object
(
    [items:Cart:private] => Array
        (
            [apple] => 150
            [chocolate] => 200
            [doughnut] => 120
        )

)

Countable

Countableインターフェースを実装したクラスは、関数count()で使用することができます。また、is_countableに渡すとtrueを返します。

Countableインターフェースを満足させるために、count()メソッドを実装する必要があります。

Countable {
/* メソッド */
abstract public count ( ) : int
}

Countableを実装したクラスの例

<?php

class MyCountable implements Countable
{
  private $values = [];

  public function __construct(...$values)
  {
    $this->values = $values;
  }

  public function count()
  {
    return count($this->values); 
  }
}

$countable = new MyCountable('apple', 'banana', 'chocolate');

echo count($countable);
var_dump(is_countable($countable));

実行結果

3
bool(false)

実際には、ArrayAccessインターフェースやIteratorAggregateインターフェースなどとセットで実装されるとよいでしょう。

参考

PHP.net
パーフェクトPHP
PHP本格入門[下]

6
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
7