AST + Better Reflection (PHP Benelux 2016 Unconference)

47
AST + Better Reflection James Titcumb PHP Benelux 2016 Unconference

Transcript of AST + Better Reflection (PHP Benelux 2016 Unconference)

AST + Better ReflectionJames Titcumb

PHP Benelux 2016 Unconference

Reflection

© 1937 Disney’s Snow White - disneyscreencaps.com

● Structure● Metadata● Values● Type introspection● Modification

Reflection

How does it work?

GET_REFLECTION_OBJECT_PTR(ce);

lc_name = zend_str_tolower_dup(name, name_len);

if ((ce == zend_ce_closure && (name_len == sizeof(ZEND_INVOKE_FUNC_NAME)-1)

&& memcmp(lc_name, ZEND_INVOKE_FUNC_NAME, sizeof(ZEND_INVOKE_FUNC_NAME)-1) == 0)

|| zend_hash_str_exists(&ce->function_table, lc_name, name_len)) {

efree(lc_name);

RETURN_TRUE;

} else {

efree(lc_name);

RETURN_FALSE;

}

Okay. What now?

github.com/ /BetterReflection

Better Reflection!

What?

Why?

How?

Magic of the AST

source: http://goo.gl/HORwLQ

WTF is AST?

use PhpParser\ParserFactory;

$parser = (new ParserFactory)

->create(ParserFactory::PREFER_PHP7);

print_r($parser->parse(

file_get_contents('ast-demo-src.php')

));

<?php

echo "Hello world";

AST

`-- Echo statement

`-- String, value "Hello world"

<?php

echo "Hello " . "world";

AST

`-- Echo statement

`-- Concat

|-- Left

| `-- String, value "Hello "

`-- Right

`-- String, value "world"

AST to Reflection

Benefits?

ReflectionProperty->getDocBlockTypes()

Types!

Using Better Reflection

$reflection = new ReflectionClass(

'BetterReflectionTest\Fixture\ExampleClass'

);

$this->assertSame(

'ExampleClass',

$reflection->getShortName()

);

$reflection = ReflectionClass::createFromName(

'BetterReflectionTest\Fixture\ExampleClass'

);

$this->assertSame(

'ExampleClass',

$reflection->getShortName()

);

// In ReflectionClass :

public static function createFromName($className)

{

return ClassReflector::buildDefaultReflector()->reflect($className);

}

// In ClassReflector :

public static function buildDefaultReflector()

{

return new self(new AggregateSourceLocator([

new PhpInternalSourceLocator(),

new EvaledCodeSourceLocator(),

new AutoloadSourceLocator(),

]));

}

AutoloadSourceLocator

ReflectionClass::createFromName(new MyClass)

replace stream wrapper

disable error handling

call “class_exists”

restore stream wrapper

restore error handling

store attempted filename load

DO NOT LOAD FILE!

return stored filename

Read file and parse AST!

(don’t try this at home kids!)

Some voodoo...

class MyClass

{

public function foo()

{

return 5;

}

}

// Create the reflection first

// ***BEFORE*** class is loaded

$classInfo = ReflectionClass::createFromName('MyClass');

// Override the body...!

$methodInfo = $classInfo->getMethod('foo');

$methodInfo->setBody(function () {

return 4;

});

// Save the class and require it

$classCode = (new CodePrinter())->prettyPrint([$classInfo->getAst()]);

$tmpFile = tempnam(sys_get_temp_dir(), 'br-monkey-patching');

file_put_contents($tmpFile, '<?php ' . $classCode);

require_once($tmpFile);

unlink($tmpFile);

// Now create an instance, and call the function...

$c = new MyClass();

var_dump($c->foo()); // will be 4!!!

What’s in it for me?

The future...

● DONE Currently: rewriting the internals○ SourceLocators “might” return some code ?!

● DONE Returning AST/Code for method/fn

Ideas/plans - DONE

● WIP More compatibility core reflection● WIP Reflect core fns with default params

○ This is not currently possible with core reflection● WIP Modification & exporting● WIP Reflecting on closures

Ideas/plans - WIP

● Adding a getColumn() function● PHP 7 compatibility● @inheritDoc traversal (to inherit type hints)● Make it faster

Ideas/plans

¯\_(ツ)_/¯

github.com/ /BetterReflection

Better Reflection

Any questions? :)

https://joind.in/talk/9a5d8James Titcumb @asgrim