Skip to content

sun/staticreflection

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

56 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

StaticReflection Build Status Code Coverage Code Quality

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:

\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%

Usage Example

  1. 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.

  2. 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.

  3. 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.

  4. 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

  1. Only one class/interface/trait per file (PSR-2, PSR-0/PSR-4), which must be defined first in the file.

  2. implementsInterface($interface) returns TRUE even if $interface is a class.

  3. \ReflectionClass::IS_IMPLICIT_ABSTRACT is not supported, since methods are not analyzed. (only the file header is analyzed)

  4. \ReflectionClass::$name is read-only and thus not available. Use getName() instead.

  5. 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:

PHPDoc tags/annotations:

License

MIT — Copyright (c) 2014 Daniel F. Kudwien (sun)

About

Static PHP class code reflection for post-discovery scenarios.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages