21
22

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

ArrayAccess,IteratorAggregateインタフェースで配列操作可能なオブジェクトを作成する

Posted at

Goal

  • 配列の添字にアクセスしたときに特定のロジックを実行させたい
  • $array->get(0)ではなく$array[0]と書きたい
  • foreachもサポートしたい
  • 具体的な利用例としては、ActiveRecordの遅延評価みたいなことをやりたい

Code

phpの配列はオブジェクトではないため、配列操作時に特定のロジックをフックさせるようなことができないですが、ArrayAccessインタフェースを実装すると、配列操作のインタフェースを備えたオブジェクトを作成することができます.
http://php.net/manual/ja/class.arrayaccess.php

<?php

class PersonList implements ArrayAccess {
  private $container = [];

  public function __construct(array $values=[]) {
    $this->container = $values;
  }

  public function offsetGet($index) {
    echo "Hello!";
    return isset( $this->container[$index] ) ? $this->container[$index] : null;;
  }

  public function offsetExists($index) {
    return isset($this->container[$index]);
  }

  public function offsetSet($index, $value) {
    if (is_null($index)) {
      $this->container[] = $value;
    } else {
      $this->container[$index] = $value;
    }
  }

  public function offsetUnset($index) {
    unset($this->container[$offset]);
  }
}

$persons = new PersonList(['Newton', 'Gauss', 'Boltzmann']);
echo $persons[0] . PHP_EOL;
  // Hello!!Newton

上記の例は、PHPドキュメントのサンプルコードほぼそのままですが、$persons[0]のように添字へのアクセスがおこなわれたときにoffsetGetメソッドがよばれるため、そのメソッドに任意の処理を追加すれば配列操作をフックすることができます.

<?php

class PersonList implements ArrayAccess, IteratorAggregate {
  private $container = [];

  public function __construct(array $values=[]) {
    $this->container = $values;
  }

  private function load() {
    if ( count( $this->container ) <= 0 ) {
      $this->container = ['Newton', 'Gauss', 'Boltzmann'];
    }
  }

  public function offsetGet($index) {
    $this->load();
    return isset( $this->container[$index] ) ? $this->container[$index] : null;;
  }

  public function offsetExists($index) {
    return isset($this->container[$index]);
  }

  public function offsetSet($index, $value) {
    if (is_null($index)) {
      $this->container[] = $value;
    } else {
      $this->container[$index] = $value;
    }
  }

  public function offsetUnset($index) {
    unset($this->container[$offset]);
  }

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

$persons = new PersonList();
// 添字アクセス
echo $persons[0] . PHP_EOL;
foreach ( $persons as $person ) {
  // foreachアクセス
  echo $person . PHP_EOL;
}

// ■ 添字アクセス結果
// Newton
// ■ foreachアクセス結果
// Newton
// Gauss
// Boltzmann

上記の例では、IteratorAggregateを使ってforeachをサポートしています. foreachで走査開始されたタイミングと添字アクセスが発生したタイミングで動的にデータをロードしています.
http://php.net/manual/ja/class.iteratoraggregate.php

PS

これと同じことはArrayObjectクラスを使うとできますが、独自の実装を追加する場合は継承する必要があるので基本はインタフェースを使うこちらの方法を使うべきだと思います.
インタフェースを使う方法でArrayObjectのフルの機能をサポートするには、IteratorAggregate, ArrayAccess, Serializable, Countableインタフェースを実装する必要があるので、てっとりばやく配列操作のインタフェースを作りたければArrayObjectを継承する方法でもよさそうです.
ArrayObject クラス
PHPで配列の要素アクセスをフックする

Environment

$ uname -a
Linux *** 2.6.32-431.el6.x86_64 #1 SMP Fri Nov 22 03:15:09 UTC 2013 x86_64 x86_64 x86_64 GNU/Linux

$ cat /etc/issue
CentOS release 6.5 (Final)
Kernel \r on an \m

$ /usr/local/php-5.5.4/bin/php -v
PHP 5.5.4 (cli) (built: Mar 11 2014 11:06:57)
Copyright (c) 1997-2013 The PHP Group
Zend Engine v2.5.0, Copyright (c) 1998-2013 Zend Technologies

$ /usr/local/php-5.6.0/bin/php -v
PHP 5.6.0 (cli) (built: Aug 29 2014 06:35:44)
Copyright (c) 1997-2014 The PHP Group
Zend Engine v2.6.0, Copyright (c) 1998-2014 Zend Technologies
21
22
2

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
21
22

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?