nphp15 May 2026 00:23

The usual way to memoize a recursive function in PHP is a static variable inside the function. It works, but it is process-global state which causes problems in Swoole where workers are reused. Here is a cleaner approach using closures.

PHP
<?php
function memoize(callable $fn): callable
{
$cache = [];
return function () use ($fn, &$cache) {
$key = serialize(func_get_args());
if (!array_key_exists($key, $cache)) {
$cache[$key] = $fn(...func_get_args());
}
return $cache[$key];
};
}
// Naive fibonacci — exponential without memoization
$callCount = 0;
$fib = memoize(function (int $n) use (&$fib, &$callCount): int {
$callCount++;
if ($n <= 1) return $n;
return $fib($n - 1) + $fib($n - 2);
});
$result = $fib(30);
echo "fib(30) = $result\n";
echo "function calls: $callCount\n";
// Without memoization this would be 2.7 million calls.
// With memoization: 31 calls.
הההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההה
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

The cache lives inside the closure, so it is scoped to the $fib variable. Create a new $fib and you get a fresh cache. No shared state across requests.

Replies (2)
maxboychuk15 May 2026 01:04

Nice pattern. One addition: the serialize() key works but is slow for large arguments. For functions that only take scalar args a much faster key is just implode("\0", func_get_args()).

PHP
<?php
function memoize(callable $fn): callable
{
$cache = [];
return function () use ($fn, &$cache) {
$args = func_get_args();
// Fast path for scalar-only args
$key = implode("\0", $args);
if (!array_key_exists($key, $cache)) {
$cache[$key] = $fn(...$args);
}
return $cache[$key];
};
}
$factorial = memoize(function (int $n) use (&$factorial): int {
return $n <= 1 ? 1 : $n * $factorial($n - 1);
});
echo $factorial(10) . "\n"; // 3628800
echo $factorial(15) . "\n"; // 1307674368000
// Call again — served from cache
echo $factorial(10) . "\n"; // 3628800 — no computation
הההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההה
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
0
andriy_m15 May 2026 02:56

Worth mentioning: this pattern has a subtle issue with the use (&$fib) reference. If you reassign $fib to a different function later, the recursive calls inside still use the old reference because &$fib captures the variable binding, not the value. In practice this rarely matters but it can produce surprising results in tests if you reuse the variable name.

To make it fully safe, use a wrapper object or just avoid reassigning the variable after creation.

0
Write a reply
Markdown. ```php blocks are runnable.