Walks as much of the userland heap as possible, looking for instances of the given FQCN (Fully-Qualified Class Name).
This helps figure out where a given object is being referred to from, in order to help solve memory leaks.
composer require --dev asmblah/heap-walker
<?php
// ...
$heapWalk = new HeapWalk();
// Find all instances of Item and how to reach them.
$pathSets = $heapWalk->getInstancePathSets([Item::class]);
// Inspect the result as needed.
<?php
use Asmblah\HeapWalk\HeapWalk;
use Asmblah\HeapWalk\Result\Path\InstancePathSet;
require_once __DIR__ . '/vendor/autoload.php';
class Item
{
public $description;
public function __construct($description)
{
$this->description = $description;
}
}
class Bag
{
// Note that visibility is ignored.
private static $items = [];
public static function init()
{
self::$items[] = new Item('a cabbage');
}
}
Bag::init();
$heapWalk = new HeapWalk();
// Find all instances of Item and how to reach them.
$pathSets = $heapWalk->getInstancePathSets([Item::class]);
// Inspect the result as needed.
assert(count($pathSets) === 1);
assert($pathSets[0] instanceof InstancePathSet);
assert(count($pathSets[0]->getPaths()) === 1);
assert($pathSets[0]->getPaths()[0]->toString() === 'Bag::$items[0]');
assert($pathSets[0]->getPaths()[0]->getEventualValue() instanceof Item);
assert($pathSets[0]->getPaths()[0]->getEventualValue()->description === 'a cabbage');
-
Scopes other than the global one are not fully inspected; only their arguments are captured through use of
debug_backtrace()
. -
The local scope of paused Generators is not accessible. For example, if a paused generator has an iterator variable
$i
declared inside with no other references to it existing, it will not be discovered. -
If a captured object is a descendant of another uncaptured object, then recursion handling will (currently) mean that only the first path to the captured object via the uncaptured one will be recorded. e.g. a service in Symfony container will only be shown via
$kernel->bundles->...->container[...]
and not also via$kernel->container[...]
. -
Internal references between PHP objects, that are not exposed to userland, are not discoverable. For example, a
PDOStatement
has an internal strong reference to itsPDOConnection
, but there is no way to access thePDOConnection
from thePDOStatement
. It should be possible to use the uopz extension to hookPDOConnection->prepare(...)
and link back to it from thePDOStatement
(in a future plugin for this tool, for example), however this must be done carefully to avoid preventing thePDOConnection
from being GC'd.