You may have heard me mention Better Reflection a few times already. In fact, I've just done a talk this morning at Dutch PHP Conference 2016 (the slides are on Joind.in now!). If you missed my talk, or weren't able to make the conference... what's the big deal, you might ask. Let me summarise what this is all about, and why this library can help you.

Reflection is part of the core of PHP, and is a built-in set of classes that allow you to introspect your code, and put it under the magnifying glass, so to speak. Surprisingly, there's some limitations as to what you can do; primarily the reflection API is a read-only API, and will give you information only about what is written in code. Additionally, you can only reflect on code that is already loaded - because the way reflection works is to examine the class "blueprint" to get the metadata about methods, properties, parameters, functions, and with PHP 7 also types.

Many of us will have seen, or used, reflection in unit tests. Regardless of your views on testing private properties and methods, there is code out there that does, and these would be tested using reflection. It'd look a little like this:

public function testSomething()
{
    $myObj = new Thing();

    $propReflection = new \ReflectionProperty($myObj, 'foo');
    $propReflection->setAccessible(true);
    $propReflection->setValue($myObj, 'whatever');

    // ... whatever ... 
}

But there's much more to just this simple use case than you think, and it can prove very powerful. This is where Better Reflection comes into play. Better Reflection is a userland library, and therefore doesn't have access (without using PHP's built-in reflection) to everything going on under the hood. Therefore, we've taken a different approach. In Better Reflection, we use the AST generated by Nikita Popov's PHP-Parser library, and use the metadata and more surrounding this to create a mostly-compatible reflection library with some extra bonus features. Let's have a look at a few of these key features...

Reflect without loading code

Because we use the PHP-Parser technique to load the code into AST and analyse like this, we can examine and reflect on code that has not actually been loaded into PHP yet. We can do this a number of different ways, by using these things we called Source Locators. The concept is quite simple; the Source Locators contain the information on how to find the code, and will load the AST. You can use the simple external API, which uses some default Source Locators (which are opinionated and make assumptions about your code), or you can explicitly define the instructions on how to find the code for Better Reflection to load.

This is what it might look like using the defaults:

use BetterReflection\Reflection\ReflectionClass;

$reflection = ReflectionClass::createFromName(\My\ExampleClass::class);

Very simple, right? And if your code doesn't conform to the assumptions we make (i.e. we assume that your autoloader, loads classes from a file - for example if you use Composer to autoload classes, the defaults will work fine):

use BetterReflection\Reflector\ClassReflector;
use BetterReflection\SourceLocator\Type\StringSourceLocator;

$source = <<<EOF
<?php
class MyClassInString {}
EOF;

$reflector = new ClassReflector(new StringSourceLocator($source));

$classInfo = $reflector->reflect(MyClassInString::class);

Yes indeed, this code example shows reflecting code contained in just an arbitrary string!

Monkey patching - modifying code before it runs

One of our planned features in Better Reflection 1.1.0 will be monkey patching, or the ability to modify code before it runs. There are some cases where this will be useful - for example you're using a library which doesn't quite do something, or you'd like to hook into certain places, or modify behaviour arbitrarily.

It's a little long-winded at the moment, and you should use this with caution! Let's say we want to reflect on, and modify the behaviour of this class:

class MyClass
{
    public function foo()
    {
        return 5;
    }
}

By using this code, we can modify the behaviour of the above function to make foo() return 4 instead of 5...

use BetterReflection\Reflection\ReflectionClass;
use PhpParser\PrettyPrinter\Standard as CodePrinter;

// Create the reflections in the normal way
$classInfo = ReflectionClass::createFromName('MyClass');
$methodInfo = $classInfo->getMethod('foo');

// Override the method body with a closure
$methodInfo->setBodyFromClosure(function () {
    return 4;
});

// Bring the class into PHP space
$printer = new CodePrinter();
$classCode = $printer->prettyPrint([
    $classInfo->getAst(),
]);
eval($classCode);

// Instantiate, and execute the resulting class
$c = new MyClass();
var_dump($c->foo()); // will be 4!!!

Essentially, you're replacing the whole body of the function here, but it's also possible to grab the AST from the method, perform your own modifications accordingly, and then call setBodyFromAst() to override the body. You'll also probably notice the rather inelegant method of loading the code; essentially using eval() here, but we still haven't come up with a better, safer, cleaner, and nicer way of doing this (suggestions or ideas are welcome!!).

Better type introspection

As well as using the type information now present in PHP 7, we also read the docblocks and expose methods that allow us to see what these types are for parameters and return types. Naturally, this relies on your docblocks being correct... But, it makes this kind of type introspection possible, even in PHP 5.6 and below code.

More stuff...!

There's plenty of other features already written, as well as planned to be written in future versions of Better Reflection. I'd love to get your feedback on using this library. Does it not do something that you'd like? Would you like to help? Let me know!