あっぽログ
← 記事一覧に戻る

PHPのWeakMapを使いこなす:オブジェクトをキーにした安全なメモリ管理を実現する

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 はオブジェクトのように添字アクセス([])が使えます。また、issetunset にも対応しています。

<?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との違いまとめ

特徴WeakMapSplObjectStorage
PHP導入バージョンPHP 8.0PHP 5.1
参照の種類弱い参照強い参照
GCによる自動削除✅ される❌ されない
イテレート✅ 可能✅ 可能
キーの型オブジェクトのみオブジェクトのみ
メモリリークのリスク低い高くなりやすい

まとめ

WeakMap はPHP8で追加された、メモリリークを防ぎながらオブジェクトにデータを紐付けられる強力なコレクションです。

  • オブジェクトへの参照がなくなると自動でエントリが削除される
  • キャッシュ・メモ化・デコレータなどのパターンに最適
  • SplObjectStorage の強い参照による問題を解決できる

長期間動作するアプリケーションやフレームワークの内部実装では、メモリ管理が非常に重要です。ぜひ WeakMap を活用して、安全で効率的なPHPコードを書いてみてください。

← 記事一覧に戻る