WeakMapとは何か
PHP8.0で新たに導入された WeakMap は、オブジェクトをキーとして値を格納できるコレクションです。一見すると SplObjectStorage と似ていますが、決定的な違いがあります。
それが「弱い参照(Weak Reference)」です。
通常の配列や SplObjectStorage にオブジェクトを格納すると、そのオブジェクトへの強い参照が保持されます。つまり、元の変数が unset されてもガベージコレクタ(GC)はオブジェクトを解放できません。
WeakMap の場合、キーとして登録したオブジェクトは弱い参照で保持されます。他に参照がなくなった時点でGCが自動的にオブジェクトを解放し、WeakMap からも自動的にエントリが削除されます。
<?php
$map = new WeakMap();
$obj = new stdClass();
$obj->name = 'PHP';
$map[$obj] = 'WeakMapの値';
echo count($map); // 1
unset($obj); // オブジェクトへの参照がなくなる
echo count($map); // 0 ← 自動的に削除された!
このように、メモリリークを気にせずオブジェクトに関連データを紐付けられるのが WeakMap の最大の特徴です。
WeakMapの基本操作
WeakMap はオブジェクトのように添字アクセス([])が使えます。また、isset・unset にも対応しています。
<?php
class User
{
public function __construct(public readonly string $name) {}
}
$map = new WeakMap();
$alice = new User('Alice');
$bob = new User('Bob');
// 値のセット
$map[$alice] = ['role' => 'admin', 'score' => 100];
$map[$bob] = ['role' => 'editor', 'score' => 80];
// 値の取得
echo $map[$alice]['role']; // admin
// キーの存在確認
var_dump(isset($map[$bob])); // bool(true)
// エントリの削除
unset($map[$bob]);
var_dump(isset($map[$bob])); // bool(false)
// イテレート
foreach ($map as $user => $data) {
echo "{$user->name}: {$data['role']}\n";
}
// Alice: admin
WeakMap はキーがオブジェクトのみという制約があります。文字列や整数をキーにしようとすると TypeError が発生します。
<?php
$map = new WeakMap();
$map['string_key'] = 'value'; // TypeError: WeakMap key must be an object
実践的なユースケース
① キャッシュ・メモ化への活用
オブジェクトに対する計算結果を一時的にキャッシュしたい場合に有効です。オブジェクトが破棄されればキャッシュも自動的に消えるため、メモリが無駄に膨らみません。
<?php
class ProductRepository
{
private WeakMap $cache;
public function __construct()
{
$this->cache = new WeakMap();
}
public function getDiscountedPrice(object $product): int
{
if (isset($this->cache[$product])) {
echo "キャッシュから取得\n";
return $this->cache[$product];
}
// 重い計算の代わりにシンプルな例
$price = $product->price * 0.8;
$this->cache[$product] = (int)$price;
return $this->cache[$product];
}
}
class Product
{
public function __construct(public int $price) {}
}
$repo = new ProductRepository();
$item = new Product(1000);
echo $repo->getDiscountedPrice($item) . "\n"; // 800
echo $repo->getDiscountedPrice($item) . "\n"; // キャッシュから取得 → 800
② デコレータパターンへの活用
既存のオブジェクトを変更せずに追加情報を付与したいときにも便利です。
<?php
class Request
{
public function __construct(public readonly string $path) {}
}
class RequestMetaStore
{
private WeakMap $store;
public function __construct()
{
$this->store = new WeakMap();
}
public function attach(Request $request, array $meta): void
{
$this->store[$request] = $meta;
}
public function get(Request $request): array
{
return $this->store[$request] ?? [];
}
}
$store = new RequestMetaStore();
$request = new Request('/api/users');
$store->attach($request, ['user_id' => 42, 'ip' => '127.0.0.1']);
print_r($store->get($request));
// Array ( [user_id] => 42 [ip] => 127.0.0.1 )
SplObjectStorageとの違いまとめ
| 特徴 | WeakMap | SplObjectStorage |
|---|---|---|
| PHP導入バージョン | PHP 8.0 | PHP 5.1 |
| 参照の種類 | 弱い参照 | 強い参照 |
| GCによる自動削除 | ✅ される | ❌ されない |
| イテレート | ✅ 可能 | ✅ 可能 |
| キーの型 | オブジェクトのみ | オブジェクトのみ |
| メモリリークのリスク | 低い | 高くなりやすい |
まとめ
WeakMap はPHP8で追加された、メモリリークを防ぎながらオブジェクトにデータを紐付けられる強力なコレクションです。
- オブジェクトへの参照がなくなると自動でエントリが削除される
- キャッシュ・メモ化・デコレータなどのパターンに最適
SplObjectStorageの強い参照による問題を解決できる
長期間動作するアプリケーションやフレームワークの内部実装では、メモリ管理が非常に重要です。ぜひ WeakMap を活用して、安全で効率的なPHPコードを書いてみてください。