sun/staticreflection

Static PHP class code reflection for post-discovery scenarios.

1.0.0 2014-06-26 13:08 UTC

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

  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)