Wallogit.com
2017 © Pedro Peláez
Static PHP class code reflection for post-discovery scenarios.
Static PHP class code reflection for post-discovery scenarios., (*1)
This utility library for PHP frameworks allows to reflect the file header of a PHP class without loading its code into memory, if its filesystem location is known already (e.g., via discovery/classmap)., (*2)
Static reflection is useful to filter a large list of previously discovered class files for common aspects like interfaces or base classes., (*3)
ReflectionClass provides the same API as the native \ReflectionClass., (*4)
Native PHP Reflection can easily grow out of control, because it not only loads the reflected class, but also all dependent classes and interfaces. PHP code cannot be unloaded. A high memory consumption may cause the application to exceed PHP's memory limit. — Static reflection avoids to (auto-)load all dependencies and ancestor classes of each reflected class into memory., (*5)
In the worst/ideal use-case, you're only generating a list of available classes, without using them immediately (e.g., for user selection or swappable plugin implementations)., (*6)
Example xhprof diff result:, (*7)
1,538 candidate classes, of which 180 interfaces, traits, abstract and other helper classes are filtered out:, (*8)
| \ReflectionClass | ReflectionClass | Diff | Diff% | |
|---|---|---|---|---|
| Number of Function Calls | 64,747 | 202,783 | 138,036 | 213.2% |
| Incl. Wall Time (microsecs) | 2,514,801 | 3,272,539 | 757,738 | 30.1% |
| Incl. CPU (microsecs) | 2,480,415 | 3,120,020 | 639,605 | 25.8% |
| Incl. MemUse (bytes) | 108,805,120 | 10,226,160 | -98,578,960 | -90.6% |
| Incl. PeakMemUse (bytes) | 108,927,216 | 10,347,608 | -98,579,608 | -90.5% |
Prerequisite: Some discovery produces a classmap:, (*9)
{
"Sun\StaticReflection\ReflectionClass":
"./src/ReflectionClass.php",
"Sun\Tests\StaticReflection\ReflectionClassTest":
"./tests/src/ReflectionClassTest.php",
"Sun\Tests\StaticReflection\Fixtures\Example":
"./tests/fixtures/Example.php",
"Sun\Tests\StaticReflection\Fixtures\Base\ImportedInterface":
"./tests/fixtures/Base/ImportedInterface.php"
...
}
→ You have a classname => pathname map., (*10)
Filter all discovered class files:, (*11)
use Sun\StaticReflection\ReflectionClass;
$list = array();
foreach ($classmap as $classname => $pathname) {
$class = new ReflectionClass($classname, $pathname);
// Only include tests.
if (!$class->isSubclassOf('PHPUnit_Framework_TestCase')) {
continue;
}
// …optionally prepare them for a listing/later selection:
$doc_comment = $class->getDocComment();
$list[$classname] = array(
'summary' => $doc_comment->getSummary(),
'covers' => $doc_comment->getAnnotations()['coversDefaultClass'][0],
);
}
echo json_encode($list, JSON_PRETTY_PRINT);
{
"Sun\Tests\StaticReflection\ReflectionClassTest": {
"summary": "Tests ReflectionClass.",
"covers": "\Sun\StaticReflection\ReflectionClass"
}
}
→ You filtered the list of available classes, without loading all code into memory., (*12)
Why this matters:, (*13)
array_walk($classmap, function (&$pathname, $classname) {
$pathname = class_exists($classname, FALSE) || interface_exists($classname, FALSE);
});
echo json_encode($classmap, JSON_PRETTY_PRINT);
{
"Sun\Tests\StaticReflection\ReflectionClassTest": false,
"Sun\Tests\StaticReflection\Fixtures\Example": false,
"Sun\Tests\StaticReflection\Fixtures\ExampleInterface": true,
"Sun\Tests\StaticReflection\Fixtures\Base\Example": true,
...
}
→ Only the ancestors of each class/interface were loaded. The statically reflected classes themselves did not get loaded., (*14)
ProTip™ - ReflectionClass::isSubclassOfAny(), (*15)
To filter for a set of common parent classes/interfaces, check the
statically reflected information first. Only proceed to isSubclassOf() in
case you need to check further; e.g.:, (*16)
// Static reflection.
if (!$class->isSubclassOfAny(array('Condition\FirstFlavor', 'Condition\SecondFlavor'))) {
continue;
}
// Native reflection of ancestors (if the reflected class has any).
if (!$class->isSubclassOf('Condition\BaseFlavor')) {
continue;
}
Only one class/interface/trait per file (PSR-2, PSR-0/PSR-4), which must be defined first in the file., (*17)
implementsInterface($interface) returns TRUE even if $interface is a
class., (*18)
\ReflectionClass::IS_IMPLICIT_ABSTRACT is not supported, since methods are
not analyzed. (only the file header is analyzed), (*19)
\ReflectionClass::$name is read-only and thus not available. Use
getName() instead., (*20)
Calling any other \ReflectionClass methods that are not implemented (yet)
causes a fatal error., (*21)
The parent \ReflectionClass class might be lazily instantiated on-demand
in the future (PRs welcome). ReflectionClass does implement all methods
that can be technically supported already., (*22)
Static/Reflection:, (*23)
PHPDoc tags/annotations:, (*24)
MIT — Copyright (c) 2014 Daniel F. Kudwien (sun), (*25)