はじめに
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
{
}
概要
抽象インターフェースですので、メソッドはありません。
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インターフェースなどとセットで実装されるとよいでしょう。