SplObjectStorageとは
PHPの標準ライブラリ(SPL)には、さまざまなデータ構造が用意されています。その中の一つ、SplObjectStorageはオブジェクトをキーとして使えるコレクションです。
通常のPHP配列ではオブジェクトをキーに使えません。文字列や整数しかキーとして扱えないため、「このオブジェクトに関連するデータを紐づけたい」というケースで工夫が必要になります。SplObjectStorageはその課題をエレガントに解決します。
主な特徴は以下のとおりです。
- オブジェクトをキーとして扱える
- 各オブジェクトに付加データ(info)を関連付けられる
- オブジェクトの重複を自動排除する(同一オブジェクトは1つしか保持しない)
Countable・Iterator・ArrayAccessインターフェースを実装しており、使いやすい
基本的な使い方
オブジェクトの追加・削除・確認
<?php
$storage = new SplObjectStorage();
$obj1 = new stdClass();
$obj2 = new stdClass();
$obj3 = new stdClass();
// オブジェクトを追加
$storage->attach($obj1);
$storage->attach($obj2);
// 含まれているか確認
var_dump($storage->contains($obj1)); // bool(true)
var_dump($storage->contains($obj3)); // bool(false)
// 要素数
echo $storage->count(); // 2
// オブジェクトを削除
$storage->detach($obj1);
echo $storage->count(); // 1
attach()で追加、detach()で削除、contains()で存在確認と、シンプルなAPIで操作できます。
付加データ(info)を使う
SplObjectStorageの強みは、オブジェクトに追加情報を紐づけられる点です。
<?php
$storage = new SplObjectStorage();
$userA = new stdClass();
$userB = new stdClass();
// 第2引数にinfoを指定
$storage->attach($userA, ['name' => '田中', 'role' => 'admin']);
$storage->attach($userB, ['name' => '佐藤', 'role' => 'editor']);
// infoを取得するにはオブジェクトを現在位置に設定してから
$storage->rewind();
while ($storage->valid()) {
$obj = $storage->current();
$info = $storage->getInfo();
echo $info['name'] . ' は ' . $info['role'] . "\n";
$storage->next();
}
// 出力:
// 田中 は admin
// 佐藤 は editor
getInfo()で現在のオブジェクトに紐づいたデータを取得できます。イテレーション中に両方まとめて取り出せるのが便利です。
ArrayAccessとして使う
SplObjectStorageはArrayAccessを実装しているため、配列のような記法でも操作できます。
<?php
$storage = new SplObjectStorage();
$task = new stdClass();
// 配列記法でinfoをセット
$storage[$task] = ['title' => 'レポート作成', 'done' => false];
// 配列記法でinfoを取得
$info = $storage[$task];
echo $info['title']; // レポート作成
// 存在確認
var_dump(isset($storage[$task])); // bool(true)
// 削除
unset($storage[$task]);
var_dump(isset($storage[$task])); // bool(false)
直感的な記法で読みやすいコードになります。
実践的な活用例
イベントリスナー管理
SplObjectStorageはリスナーオブジェクトの管理に活用できます。同一オブジェクトを重複登録しないという性質が特に役立ちます。
<?php
interface Listener
{
public function handle(string $event): void;
}
class LogListener implements Listener
{
public function handle(string $event): void
{
echo "[LOG] イベント受信: {$event}\n";
}
}
class EventEmitter
{
private SplObjectStorage $listeners;
public function __construct()
{
$this->listeners = new SplObjectStorage();
}
public function addListener(Listener $listener): void
{
// 重複していても1つしか登録されない
$this->listeners->attach($listener);
}
public function emit(string $event): void
{
foreach ($this->listeners as $listener) {
$listener->handle($event);
}
}
public function removeListener(Listener $listener): void
{
$this->listeners->detach($listener);
}
}
$emitter = new EventEmitter();
$listener = new LogListener();
$emitter->addListener($listener);
$emitter->addListener($listener); // 重複しても問題なし
$emitter->emit('user.created');
// [LOG] イベント受信: user.created (1回だけ出力)
配列で管理する場合はin_array()による重複チェックが必要ですが、SplObjectStorageならそのままattach()するだけで重複を排除できます。
オブジェクトへのメタデータ付与
グラフ探索やゲームの状態管理など、「オブジェクトに訪問済みフラグや重みを付けたい」という場面でも活躍します。
<?php
class Node
{
public function __construct(public readonly string $name) {}
}
$visited = new SplObjectStorage();
$nodeA = new Node('A');
$nodeB = new Node('B');
$nodeC = new Node('C');
// 訪問済みとコストを記録
$visited->attach($nodeA, ['cost' => 0]);
$visited->attach($nodeB, ['cost' => 5]);
// Aは訪問済みか?
if ($visited->contains($nodeA)) {
$visited->rewind();
// ArrayAccess記法でinfoを確認
echo 'コスト: ' . $visited[$nodeA]['cost'] . "\n"; // コスト: 0
}
// Cは未訪問
var_dump($visited->contains($nodeC)); // bool(false)
まとめ
SplObjectStorageはシンプルながらも実用的なデータ構造です。
| 操作 | メソッド |
|---|---|
| 追加 | attach($obj, $info) |
| 削除 | detach($obj) |
| 存在確認 | contains($obj) |
| info取得 | getInfo() |
| 要素数 | count() |
「オブジェクトの集合を管理したい」「オブジェクトにデータを紐づけたい」「重複を自動的に排除したい」という要件があるとき、まずSplObjectStorageを検討してみてください。PHPの標準ライブラリには、知るだけで設計がスッキリするクラスが多く揃っています。ぜひ活用してみてください。