A memoization function for PHP
Published on 2024-06-01 • Modified on 2024-06-01
This snippet shows how to memoize a pure function result on an object. The code comes from this snippet, thanks to the author, calebporzio. I have slightly modified it and provided a complete working example with time execution traces to show that is works correclty. Check out the links below for more explanations.
<?php
declare(strict_types=1);
namespace App\Controller\Snippet;
use App\Dto\DummyMemoizable;
use Symfony\Component\Stopwatch\Stopwatch;
/**
* I am using a PHP trait to isolate each snippet in a file.
* This code should be called from a Symfony controller extending AbstractController (as of Symfony 4.2)
* or Symfony\Bundle\FrameworkBundle\Controller\Controller (Symfony <= 4.1).
* Services are injected in the main controller constructor.
*/
trait Snippet303Trait
{
public function snippet303(): void
{
$obj1 = new DummyMemoizable();
/** @var DummyMemoizable $memo */
$memo = $this->memoize($obj1);
/**
* The compute() function of the DummyMemoizable() object is:
*
* $str = $parameter1. $parameter2;
* usleep(10000); // = 10 milliseconds
*
* return $str;
*/
$timer = new Stopwatch();
// standard call without memoization
$event1 = 'std-call';
$timer->start($event1);
$res1 = $obj1->compute('foo', 1);
$timer->stop($event1);
echo \sprintf('Standard call: %s (%d ms)', $res1, $timer->getEvent($event1)->getDuration()).PHP_EOL;
// use memoization, the result is slow and stored
$event2 = 'memo-first-call';
$timer->start($event2);
$res2 = $memo->compute('foo', 1);
$timer->stop($event2);
echo \sprintf('Memo first call: %s (%d ms)', $res2, $timer->getEvent($event2)->getDuration()).PHP_EOL;
// with the same arguments, the result is taken from the cache
$event3 = 'memo-cached-call';
$timer->start($event3);
$res3 = $memo->compute('foo', 1);
$timer->stop($event3);
echo \sprintf('Memo cached call: %s (%d ms)', $res3, $timer->getEvent($event3)->getDuration()).PHP_EOL;
// with different arguments a new specific cache is stored
$event4 = 'memo-new-call';
$timer->start($event4);
$res4 = $memo->compute('bar', 2);
$timer->stop($event4);
echo \sprintf('Memo new call with other parameters: %s (%d ms)', $res4, $timer->getEvent($event4)->getDuration()).PHP_EOL;
// That's it! 😁
}
public function memoize(object $target): object
{
static $memo = new \WeakMap();
/** @phpstan-ignore-next-line */
return new class($target, $memo) {
public function __construct(
protected object $target,
protected \WeakMap $memo,
) {
}
public function __call(string $method, mixed $params): mixed
{
$this->memo[$this->target] ??= [];
$signature = $method.crc32(json_encode($params, JSON_THROW_ON_ERROR));
return $this->memo[$this->target][$signature]
??= $this->target->$method(...$params);
}
};
}
}
Run this snippet More on Stackoverflow Read the doc More on the web Random snippet