ZetCode

PHP WeakMap

最后修改于 2025 年 3 月 11 日

在本文中,我们探讨了 PHP 中的 WeakMapWeakMap 是一种特殊类型的映射,它允许对象用作键,而不会阻止它们被垃圾回收。这对于管理对象引用而不会导致内存泄漏很有用。

PHP WeakMap 的主要特性

WeakMap 在需要将元数据与对象关联而不影响其生命周期的场景中特别有用。

WeakMap 的基本用法

以下示例展示了如何使用 WeakMap 缓存用户会话数据。

main.php
<?php

declare(strict_types=1);

$sessionCache = new WeakMap();

$userSession = new stdClass();
$userSession->id = "sess123";
$sessionCache[$userSession] = ["last_login" => "2025-03-11"];

var_dump($sessionCache[$userSession]); // Shows cached session data

在这个实际例子中,我们使用 WeakMap 来存储用户会话对象的会话数据。会话对象充当键,包含元数据(例如上次登录时间)的数组是值。

这种方法在 Web 应用程序中很有用,您可能希望临时缓存与会话相关的数据,而无需无限期地保持会话活动。当会话对象不再在其他地方被引用时,它会被垃圾回收,并且 WeakMap 条目会自动消失。

$ php main.php
array(1) { ["last_login"]=> string(10) "2025-03-11" }

使用 WeakMap 进行垃圾回收

此示例使用 WeakMap 跟踪临时数据库连接。

main.php
<?php

declare(strict_types=1);

$connectionMap = new WeakMap();

function createConnection(): stdClass {
    $conn = new stdClass();
    $conn->id = "conn" . rand(1000, 9999);
    global $connectionMap;
    $connectionMap[$conn] = ["opened" => time()];
    return $conn;
}

$conn = createConnection();
var_dump($connectionMap[$conn]); // Shows connection metadata

unset($conn); // Connection is eligible for garbage collection
var_dump($connectionMap); // Empty WeakMap after GC

在这里,我们使用 WeakMap 模拟数据库连接管理器来跟踪连接元数据,例如何时打开。连接对象在函数内部创建并存储在 WeakMap 中。

当我们调用 unset($conn) 时,连接对象失去了最后一个强引用。然后 PHP 的垃圾收集器可以回收它,并且由于 WeakMap 使用弱引用,该条目会自动删除。这非常适合管理在不再需要时应自然清理的资源。

在实际应用程序中,这可以帮助跟踪数据库连接或 API 客户端,而不会在连接被放弃时导致内存泄漏。

$ php main.php
array(1) { ["opened"]=> int(1741742400) }
object(WeakMap)#1 (0) {}

将 WeakMap 与多个对象一起使用

此示例管理与请求对象关联的多个记录器。

main.php
<?php

declare(strict_types=1);

$logMap = new WeakMap();

$request1 = new stdClass();
$request1->id = "req1";
$request2 = new stdClass();
$request2->id = "req2";

$logMap[$request1] = ["log" => "Request 1 started"];
$logMap[$request2] = ["log" => "Request 2 started"];

var_dump($logMap[$request1]); // Shows log for request 1
var_dump($logMap[$request2]); // Shows log for request 2

unset($request1); // Request 1 can be garbage collected
var_dump($logMap); // Only request 2 remains

在这种情况下,我们使用 WeakMap 将日志条目与 HTTP 请求对象关联起来。每个请求对象都是一个键,其值是一个包含日志消息的数组。

当调用 unset($request1) 时,第一个请求对象符合垃圾回收的条件。WeakMap 自动删除其条目,只留下第二个请求的日志。这演示了 WeakMap 如何动态地处理多个对象。

这在 Web 框架中很实用,您可能希望记录特定于请求的数据,而无需将日志的生命周期绑定到请求的生存期。

$ php main.php
array(1) { ["log"]=> string(16) "Request 1 started" }
array(1) { ["log"]=> string(16) "Request 2 started" }
object(WeakMap)#1 (1) { ... }

WeakMap 与自定义对象

此示例使用自定义 User 类缓存用户权限。

main.php
<?php

declare(strict_types=1);

class User {
    public function __construct(public string $name) {}
}

$permissionMap = new WeakMap();

$user = new User("Jane Doe");
$permissionMap[$user] = ["role" => "admin", "access" => ["read", "write"]];

var_dump($permissionMap[$user]); // Shows user permissions

unset($user); // User object is garbage collected
var_dump($permissionMap); // Empty after GC

在这里,我们定义了一个具有公共 name 属性的 User 类,并将其用作 WeakMap 中的键。该值是一个表示用户权限的数组,包括他们的角色和访问权限。

这是身份验证系统中权限缓存的实际用例。当 $user 对象被取消设置时,它会被垃圾回收,并且 WeakMap 会删除该条目。这确保了权限数据在不再需要用户对象后不会在内存中停留。

$ php main.php
array(2) { ["role"]=> string(5) "admin" ["access"]=> array(2) { [0]=> string(4) "read" [1]=> string(5) "write" } }
object(WeakMap)#1 (0) {}

WeakMap 与迭代

此示例迭代 WeakMap 以记录活动会话。

main.php
<?php

declare(strict_types=1);

$sessionMap = new WeakMap();

$session1 = new stdClass();
$session1->id = "sess1";
$session2 = new stdClass();
$session2->id = "sess2";

$sessionMap[$session1] = ["ip" => "192.168.1.1"];
$sessionMap[$session2] = ["ip" => "10.0.0.1"];

foreach ($sessionMap as $session => $data) {
    echo "Session {$session->id}: IP {$data['ip']}\n";
}

在此示例中,我们使用 WeakMap 存储活动会话的 IP 地址。foreach 循环遍历映射,打印每个会话的 ID 和 IP 地址。

这对于在服务器应用程序中调试或监视活动会话很有用。只要会话对象仍然被引用,迭代就会起作用。如果会话在循环之前被取消设置,它将不会出现,这要归功于 WeakMap 的自动清理。

$ php main.php
Session sess1: IP 192.168.1.1
Session sess2: IP 10.0.0.1

WeakMap 用于资源跟踪

此示例跟踪由文件处理器打开的文件句柄。

main.php
<?php

declare(strict_types=1);

class FileProcessor {
    public function __construct(public string $filename) {}
}

$resourceMap = new WeakMap();

$processor = new FileProcessor("data.txt");
$resourceMap[$processor] = ["handle" => fopen($processor->filename, "r")];

var_dump($resourceMap[$processor]); // Shows file handle resource

unset($processor); // Processor and handle can be garbage collected
var_dump($resourceMap); // Empty after GC

在这种情况下,我们使用 WeakMap 将文件句柄与 FileProcessor 对象关联起来。处理器对象表示正在处理的文件,并且映射将打开的文件资源存储为元数据。

这对于文件处理应用程序中的资源管理很有用。当 $processor 被取消设置时,它会被垃圾回收,并且 WeakMap 条目被删除。在实际应用程序中,您将显式关闭文件句柄,但这展示了 WeakMap 如何避免保留无效引用。

$ php main.php
array(1) { ["handle"]=> resource(5) of type (stream) }
object(WeakMap)#1 (0) {}

WeakMap 与对象池

此示例使用 WeakMap 管理可重用的数据库连接池。

main.php
<?php

declare(strict_types=1);

class DbConnection {
    public function __construct(public string $dsn) {}
}

$poolMap = new WeakMap();

function getConnection(string $dsn): DbConnection {
    $conn = new DbConnection($dsn);
    global $poolMap;
    $poolMap[$conn] = ["active" => true, "last_used" => time()];
    return $conn;
}

$conn1 = getConnection("mysql:host=localhost");
$conn2 = getConnection("mysql:host=remote");

var_dump($poolMap[$conn1]); // Shows connection 1 metadata
unset($conn2); // Connection 2 is removed from pool
var_dump(count($poolMap)); // Only 1 connection remains

在这里,我们使用 WeakMap 模拟数据库连接池。每个 DbConnection 对象都是一个键,其值跟踪其状态和上次使用时间。getConnection 函数创建并注册连接。

这是管理数据库连接或 API 客户端等资源池的实际用例。当 $conn2 被取消设置时,它符合垃圾回收的条件,并且 WeakMap 会相应地缩小。这确保池仅保存活动连接。

在生产系统中,您将添加逻辑以重用连接或限制池大小,但这说明了 WeakMap 如何帮助管理临时对象引用而无需手动清理。

$ php main.php
array(2) { ["active"]=> bool(true) ["last_used"]=> int(1741742400) }
int(1)

来源

PHP WeakMap - 文档

在本文中,我们展示了如何在 PHP 中使用 WeakMap 来管理对象引用。WeakMap 是一个强大的工具,用于将元数据与对象关联,而不会影响其生命周期。

作者

我叫 Jan Bodnar,是一位充满激情的程序员,拥有丰富的编程经验。自 2007 年以来,我一直在撰写编程文章。迄今为止,我撰写了 1,400 多篇文章和 8 本电子书。我拥有超过十年的编程教学经验。

列出 所有 PHP 教程