📌

PHP SPLの標準Interfaceを真剣に考える

2022/07/04に公開

ArrayAccessとかIteratorとか

関連記事:
WSLにDocker代替のPodmanを入れてみる: ポッドマンが倒せない(1)
WSL2上のリモートPodmanにWindowsから接続: ポッドマンが倒せない(2)
WSLサポートしたpodmanV4: ポッドマンが倒せない(3)
Distrodを使ってWSLでsystemdを動かす

------------------- ↓ 前書きはここから ↓-------------------

かなり古くからありながらあまり使われていないSPL。
PDOのようにフレームワークの中核にあるのを除けば、
他は知らない人がほとんどだと思う。

つい先日、設計の不備をカバーするためにIteratorをちゃんと考える機会があった
PHPのforeachが使い勝手も性能的にも良すぎるので、
Iteratorなんているのか?
って思っていたぐらいだったが、
複雑怪奇なループが出たときに対応が難しい。
Iteratorから組めばスマートなコーディングも可能だとわかった。

一つ一つ掘り下げるのは別記事でやるとして、
今回はこういうのがあるよと言う紹介をしてみよう。

ヾ(・ω<)ノ" 三三三● ⅱⅲ コロコロ♪

------------------- ↓ 本題はここから ↓-------------------

SPLインタフェース

Interfaceというとメソッドとかプロパティとかの型だけ決めて、
実装者と利用者を分ける目的で使うと思うが、
PHPのインタフェースにはそれとは別に、
固有機能付与される。

なので目的に合致するものは積極的に使うのがいいだろう。

ArrayAccess(ArrayObject, ArrayIterator)

特殊能力: オブジェクトを配列として扱える

PDOを除けば次点で使われているであろうArrayAccessインタフェース
直接コードに書かなくてもフレームワークに実装されて間接的に使ってる人がほとんどだと思う。
Laravelを使ってる方は一度Collectionのソースを見て欲しい。

class Collection implements ArrayAccess, CanBeEscapedWhenCastToString, Enumerable
{
}

普通PHPのオブジェクトは配列として扱えない。
例えば空オブジェクトであるstdClassでも

~/stdclass.php
<?php
$obj = new stdClass();
$obj->abc = 'def';
$obj->bcd = 'efg';
echo $obj['abc'];

実行すると

php stdclass.php
PHP Fatal error:  Uncaught Error: Cannot use object of type stdClass as array in /home/dozo/stdclass.php:6

オブジェクトだから当たり前だけどFatalで落ちる。
普通のクラス定義でも同じなのだが、
ArrayAccessを使ってみると

~/arrayobject.php
<?php
class stdObject implements ArrayAccess
{
    protected $items = [];
    public function __construct($data = [])
    {
        $this->items = $data;
    }
    public function offsetExists($offset){}
    public function offsetGet($offset){}
    public function offsetSet($offset, $value){}
    public function offsetUnset($offset){}
}

$obj = new stdObject([
    'abc' => 'def',
    'bcd' => 'efg',
]);
echo $obj['abc'];

実行すると

php arrayobject.php
def

('ω') キモイ

offset~~はちゃんと定義するが、
ページが長くなるのでカット。

ArrayObjectやArrayIteratorはその拡張クラス。
正直この二つは使い勝手が悪いので、
ArrayAccessから自身で組む方が良いと思う。

JsonSerializable

特殊能力: JSON変換のタイミングで実行するメソッドを保有

SPLではないがext_jsonに付属するので、
最早標準と言っていいインタフェース
JSONデータを作るときに配列を作ってjson_encodeをする人がほとんどがと思う。
一応オブジェクトもJSON化できるが、
JsonSerializableを使うと、
クラスの内部からかつ各クラス毎に出力内容を制御できる

objToJson.php
<?php
class Abc implements JsonSerializable {
  public function jsonSerialize()
  {
    $this->abc = 'def';
    $this->bcd = 'efg';
    $this->nest = new Def();
    return $this;
  }
}

class Def implements JsonSerializable {
  public function jsonSerialize()
  {
    return ['cde' => 'def'];
  }
}

$obj = new Abc();
echo json_encode($obj);

実行すると

php objToJson.php
{"abc":"def","bcd":"efg","nest":{"cde":"def"}}

正規化したデータベースを反映して出すなど、
結構重宝する

IteratorAggregate(Iterator)

特殊能力: foreachで分解する内容を制御できる

配列やオブジェクトをforeachに掛けると、
最初から最後まで一定の法則でデータを取り出し、
その中で処理を行うわけだが、
1レコード目は省くとか、
中のデータをちょっと変えるとか、
&とかで配列自体を差し替えるとか、
細かい事象が発生する場合がある。

基本的にはべた書きでいいのだが、
あまりにも複雑になりすぎるときは
Iteratorを検討するといい

ただIterator自体は使いにくいので、
IteratorAggregate + Generatorを推したい

iteratorAggregete.php
<?php

class Abc implements IteratorAggregate{
  public function getIterator()
  {
    $key = 0;
    $item = ['abc' => 'def'];
    yield $key => $item;
    $key++;
    $item = ['bcd' => 'efg'];
    yield $key => $item;
  }
}

foreach (new Abc() as $key => $item) {
  echo "$key:".var_export($item, true)."\n";
}

実行すると

php iteratorAggregete.php
0:array (
  'abc' => 'def',
)
1:array (
  'bcd' => 'efg',
)

------------------- ↓ 後書きはここから ↓-------------------

思ったより長くなってしまった

Discussion