sun / staticreflection
Static PHP class code reflection for post-discovery scenarios.
Requires
- php: >=5.4.2
- ext-reflection: *
- ext-tokenizer: *
This package is auto-updated.
Last update: 2024-10-29 03:53:15 UTC
README
Static PHP class code reflection for post-discovery scenarios.
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).
Static reflection is useful to filter a large list of previously discovered class files for common aspects like interfaces or base classes.
ReflectionClass
provides the same API as the native \ReflectionClass
.
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.
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).
Example xhprof diff result:
1,538 candidate classes, of which 180 interfaces, traits, abstract and other helper classes are filtered out:
Usage Example
-
Prerequisite: Some discovery produces a classmap:
{ "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. -
Filter all discovered class files:
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.
-
Why this matters:
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.
-
ProTip™ -
ReflectionClass::isSubclassOfAny()
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.:// 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; }
Requirements
- PHP 5.4.2+
Limitations
-
Only one class/interface/trait per file (PSR-2, PSR-0/PSR-4), which must be defined first in the file.
-
implementsInterface($interface)
returnsTRUE
even if$interface
is a class. -
\ReflectionClass::IS_IMPLICIT_ABSTRACT
is not supported, since methods are not analyzed. (only the file header is analyzed) -
\ReflectionClass::$name
is read-only and thus not available. UsegetName()
instead. -
Calling any other
\ReflectionClass
methods that are not implemented (yet) causes a fatal error.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.
Notes
- StaticReflection may work around bytecode caches that strip off comments.
Inspirations
Static/Reflection:
- Doctrine's (Static) Reflection
- phpDocumentor's Reflection
- Zend Framework's Reflection
PHPDoc tags/annotations:
- PHPUnit's Util\Test
- Doctrine's Annotations
- phpDocumentor's ReflectionDocBlock + Descriptor
- Kuria's PhpDocComment
- Philip Graham's Annotations
License
MIT — Copyright (c) 2014 Daniel F. Kudwien (sun)