asmblah / heap-walker
PHP userland heap walker
Installs: 6 343
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 3
Forks: 0
Open Issues: 0
Type:project
Requires (Dev)
- phpspec/prophecy-phpunit: ^2.0
- phpunit/phpunit: ^9.5
- vimeo/psalm: ^4.10
This package is auto-updated.
Last update: 2024-10-30 01:40:01 UTC
README
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.
Usage
Install
composer require --dev asmblah/heap-walker
Use
<?php // ... $heapWalk = new HeapWalk(); // Find all instances of Item and how to reach them. $pathSets = $heapWalk->getInstancePathSets([Item::class]); // Inspect the result as needed.
Full example
<?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');
Caveats & limitations
-
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.