Skip to Content
Course content

153: SplObjectStorage and WeakMap in PHP

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Imagine you're running a high-end coat check at a gala. When a guest hands you their coat, you don't sew a name tag directly into the fabric of the coat—that would be intrusive and probably ruin the garment. Instead, you give the coat a ticket and record some information about it (like which wardrobe rack it's on) in your ledger. The coat remains unchanged, but you have a way to associate extra data with that specific physical object.

In PHP, this is exactly the problem SplObjectStorage and WeakMap solve. Usually, if you want to associate data with an object, you'd either add a property to the class or use an array. But arrays only take strings or integers as keys. You can't use an object as an array key. These two tools allow you to use the object itself as the key.

Giving Objects Metadata Without Touching the Class

Let's say you're building a game engine. You have GameCharacter objects, but you have a separate CombatManager that needs to track which characters are currently "stunned" and for how many turns. You don't want to add a $stunTurns property to the GameCharacter class because that's a combat-specific detail, not a core character attribute.

This is where SplObjectStorage shines. It's essentially a set that can also store data associated with each element.


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

$combatManager = new SplObjectStorage();

$hero = new GameCharacter("Alistair");
$goblin = new GameCharacter("Grog");

// Attach data (stun turns) to the object
$combatManager[$hero] = 2; 
$combatManager[$goblin] = 1;

if (isset($combatManager[$hero])) {
    echo "{$hero->name} is stunned for {$combatManager[$hero]} turns!";
}

I love this approach because it keeps your domain models clean. The GameCharacter doesn't need to know it's being tracked by a combat system; it just exists.

The Memory Leak Trap and the WeakMap Solution

Here is the catch with SplObjectStorage: it creates a "strong reference" to the object. As long as that object is inside your storage, PHP's garbage collector cannot delete it from memory, even if you unset() the original variable. In a long-running script (like a ReactPHP server or a complex worker), this is a recipe for a memory leak.

Enter WeakMap. A WeakMap works almost identically to SplObjectStorage, but it holds a "weak reference." If the object is no longer referenced anywhere else in your code, PHP will reclaim that memory and automatically remove the entry from the WeakMap.

I typically use WeakMap for caching calculated values. For instance, if you have a heavy computation based on an object, you can cache the result without preventing the object from being destroyed when it's no longer needed.


class HeavyDocument {
    public function __construct(public string $content) {}
}

$cache = new WeakMap();

$doc = new HeavyDocument("A very long legal text...");

// Calculate a hash and store it in the WeakMap
$cache[$doc] = md5($doc->content);

unset($doc); 
// Now, the HeavyDocument object is gone from memory, 
// and the entry in $cache is automatically deleted.

Which one should you actually use?

If you need a collection that behaves like a set (checking if an object exists in a group) and you intend for that collection to "own" the objects, go with SplObjectStorage. It also provides a useful internal pointer for iterating through objects.

If you are attaching metadata, caches, or "sidecar" data to objects that are managed elsewhere in your application, always use WeakMap. It's the safer choice for memory management and avoids the headache of manually cleaning up your maps when objects are destroyed.




📋 Practical Task

Exercise: Implementing a Non-Intrusive Entity Permissions Cache

You are building a system where User objects are passed around, and you need to track their temporary "session permissions" without modifying the User class itself. Because users log in and out frequently, you must ensure that when a User object is destroyed, their permissions cache is automatically cleared to prevent memory leaks.

Your Task:

  • Create a User class with a $username property.
  • Create a PermissionManager class.
  • Inside PermissionManager, implement a private WeakMap to store permissions.
  • Implement a method assignPermission(User $user, string $permission) that stores the permission string associated with that user.
  • Implement a method hasPermission(User $user): bool that checks if the user has a specific permission.
  • In your test code, create a user, assign a permission, verify it exists, then unset() the user and explain (via a comment) why a WeakMap was necessary here instead of an array or SplObjectStorage.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.