Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

88
Mirror, mirror on the wall: Building a new PHP reflection library James Titcumb Dutch PHP Conference 2016

Transcript of Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

Page 1: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

Mirror, mirror on the wall: Building a new PHP reflection library

James TitcumbDutch PHP Conference 2016

Page 3: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

Reflection

@asgrim

Page 4: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)
Page 5: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

© 1937 Disney’s Snow White - disneyscreencaps.com

Page 6: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)
Page 7: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

Mostly this...public function testSomething()

{

$myObj = new Thing();

$propReflection = new \ReflectionProperty($myObj, 'foo');

$propReflection->setAccessible(true);

$propReflection->setValue($myObj, 'whatever');

// ... whatever ...

}

Page 8: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

● Structure● Metadata● Values● Type introspection● Modification

Reflection

Page 9: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

How does it work?

Page 10: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

zend_object (zend_types.h)● zend_class_entry *ce (zend.h)

○ zval* static_members_table○ HashTable function_table○ HashTable properties_info○ HashTable constants_table○ zend_class_entry** interfaces○ zend_class_entry** traits○ (…other stuff…)

Roughly...

Page 11: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

ReflectionClass->hasMethod

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;

}

Page 12: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

Okay. What now?

Page 13: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

github.com/ /BetterReflection

Better Reflection!

Page 14: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)
Page 15: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

What?

Page 16: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

Why?

Page 17: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

Features!

Page 18: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

How?

Page 19: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)
Page 20: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)
Page 21: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

Reflector

Source Locator

PhpParser

Reflection

Page 22: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

$reflection = new ReflectionClass(

\My\ExampleClass::class

);

$this->assertSame(

'ExampleClass',

$reflection->getShortName()

);

Page 23: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

$reflection = ReflectionClass::createFromName(

\My\ExampleClass::class

);

$this->assertSame(

'ExampleClass',

$reflection->getShortName()

);

Page 24: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

// In ReflectionClass :

public static function createFromName($className)

{

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

}

Page 25: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

// In ClassReflector :

public static function buildDefaultReflector()

{

return new self(new AggregateSourceLocator([

new PhpInternalSourceLocator(),

new EvaledCodeSourceLocator(),

new AutoloadSourceLocator(),

]));

}

Page 26: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

Reflector

Source Locator

PhpParser

Reflection

Page 27: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

Source Locators

● PhpInternalSourceLocator● EvaledCodeSourceLocator● AggregateSourceLocator● ClosureSourceLocator● ComposerSourceLocator● SingleFileSourceLocator● StringSourceLocator

Page 28: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

StringSourceLocatoruse 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);

Page 29: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

However…

Page 30: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

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!

Page 31: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

What’s next?

Now we have CODE!

Page 32: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

Magic superpowers

Page 33: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

source: http://goo.gl/HORwLQ

Page 34: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

So what is AST?

Page 35: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

Reflector

Source Locator

PhpParser

Reflection

Page 36: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

<?php

use PhpParser\ParserFactory;

$parser = (new ParserFactory)

->create(ParserFactory::PREFER_PHP7);

print_r($parser->parse(

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

));

Page 37: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

<?php

echo "Hello world";

Page 38: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

Echo statement

`-- String, value "Hello world"

Page 39: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

<?php

echo "Hello " . "world";

Page 40: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

Echo statement

`-- Concat

|-- Left

| `-- String, value "Hello "

`-- Right

`-- String, value "world"

Page 41: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

<?php

$a = 5;

$b = 3;

echo $a + ($b * 2);

Page 42: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

Assign statement

|-- Variable $a

`-- Integer, value 5

Assign statement

|-- Variable $b

`-- Integer, value 3

Echo statement

`-- Add operation

|-- Left

| `-- Variable $a

`-- Right

`-- Multiply operation

|-- Left

| `-- Variable $b

`-- Right

`-- Integer, value 2

Page 43: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

So what?

Page 44: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

Reflector

Source Locator

PhpParser

Reflection

Page 45: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

AST to Reflection

Page 46: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

Benefits?

Page 47: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

<?php

class Foo

{

private $bar;

public function thing()

{

}

}

Page 48: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

Class, name Foo

|-- Statements

| |-- Property, name bar

| | |-- Type [private]

| | `-- Attributes [start line: 7, end line: 9]

| `-- Method, name thing

| |-- Type [public]

| |-- Parameters [...]

| |-- Statements [...]

| `-- Attributes [start line: 7, end line: 9]

`-- Attributes [start line: 3, end line: 10]

Page 49: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

php-ast extension

Page 50: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

Here be dragons!

Some voodoo...

Page 51: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

class MyClass

{

public function foo()

{

return 5;

}

}

Page 52: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

// Create the reflection first

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

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

// Or use specific source locators as already shown

Page 53: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

// Override the body...!

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

$methodInfo->setBodyFromClosure(function () {

return 4;

});

Page 54: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

// Bring the code into context now

$printer = new CodePrinter();

$classCode = $printer->prettyPrint([

$classInfo->getAst(),

]);

eval($classCode);

Page 55: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

// Now create an instance, and call the

// method on this...

$c = new MyClass();

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

Page 56: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)
Page 57: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

astkit

Page 58: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

$if = AstKit::parseString(<<<EOD

if (true) {

echo "This is a triumph.\n";

} else {

echo "The cake is a lie.\n";

}

EOD

);

$if->execute(); // First run, program is as-seen above

$const = $if->getChild(0)->getChild(0);

// Replace the "true" constant in the condition with false

$const->graft(0, false);

// Can also graft other AstKit nodes, instead of constants

$if->execute(); // Second run now takes the else path

Page 59: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

Difficulties...

Page 60: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

ReflectionClass implements Reflector {

/* Constants */

const integer IS_IMPLICIT_ABSTRACT = 16 ;

const integer IS_EXPLICIT_ABSTRACT = 32 ;

const integer IS_FINAL = 64 ;

/* Properties */

public $name ;

/* Methods */

public __construct ( mixed $argument )

public static string export ( mixed $argument [, bool $return = false ] )

public mixed getConstant ( string $name )

public array getConstants ( void )

public ReflectionMethod getConstructor ( void )

public array getDefaultProperties ( void )

public string getDocComment ( void )

public int getEndLine ( void )

public ReflectionExtension getExtension ( void )

public string getExtensionName ( void )

public string getFileName ( void )

public array getInterfaceNames ( void )

public array getInterfaces ( void )

public ReflectionMethod getMethod ( string $name )

public array getMethods ([ int $filter ] )

public int getModifiers ( void )

public string getName ( void )

public string getNamespaceName ( void )

public object getParentClass ( void )

public array getProperties ([ int $filter ] )

public ReflectionProperty getProperty ( string $name )

public string getShortName ( void )

public int getStartLine ( void )

public array getStaticProperties ( void )

Reflection API is a big!public mixed getStaticPropertyValue ( string $name [, mixed &$def_value ] )

public array getTraitAliases ( void )

public array getTraitNames ( void )

public array getTraits ( void )

public bool hasConstant ( string $name )

public bool hasMethod ( string $name )

public bool hasProperty ( string $name )

public bool implementsInterface ( string $interface )

public bool inNamespace ( void )

public bool isAbstract ( void )

public bool isAnonymous ( void )

public bool isCloneable ( void )

public bool isFinal ( void )

public bool isInstance ( object $object )

public bool isInstantiable ( void )

public bool isInterface ( void )

public bool isInternal ( void )

public bool isIterateable ( void )

public bool isSubclassOf ( string $class )

public bool isTrait ( void )

public bool isUserDefined ( void )

public object newInstance ( mixed $args [, mixed $... ] )

public object newInstanceArgs ([ array $args ] )

public object newInstanceWithoutConstructor ( void )

public void setStaticPropertyValue ( string $name , string $value )

public string __toString ( void )

}

Page 61: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

<?php

namespace ??????????;

use ?????????????????????????????????????;

class Foo

{

public function something()

{

throw new InvalidArgumentException('Oh noes!');

}

}

Type determination

Page 62: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

<?php

namespace My\Package;

use Some\Package\InvalidArgumentException;

class Foo

{

public function something()

{

throw new InvalidArgumentException('Oh noes!');

}

}

Type determination

Page 63: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

Type determination

● FindParameterType● FindPropertyType● FindReturnType● FindTypeFromAst

Page 64: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

$finder = new FindTypeFromAst();

$namespace = '';

if ($method->getDeclaringClass()->inNamespace()) {

$namespace = $method->getDeclaringClass()->getNamespaceName();

}

$type = $finder(

$className,

$method->getLocatedSource(),

$namespace

);

Type determination

Page 65: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

DocBlock Parent Traversal Type

Resolution

Page 66: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

DocBlock Parent Traversal Type Resolution

class Foo {

/**

* @return int

*/

public function myMethod() { /* ... */ }

}

class Bar extends Foo {

/**

* {@inheritDoc}

*/

public function myMethod() { /* ... */ }

}

It’s an “int” return type!

Page 67: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

DocBlock Parent Traversal Type Resolutioninterface Blammo {

/**

* @return string

*/

public function myMethod();

}

class Foo {

/**

* @return int

*/

public function myMethod() { /* ... */ }

}

class Bar extends Foo implements Blammo {

/**

* {@inheritDoc}

*/

public function myMethod() { /* ... */ }

}

Return type: ¯\_(ツ)_/¯

Page 68: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

Evaluating Modified Reflections

Page 69: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

Evaluating Modified Reflections

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

$methodInfo->setBodyFromClosure(function () {

// Nasty, evil, malicious code here !!!

});

$classCode = (new CodePrinter())->prettyPrint([

$classInfo->getAst(),

]);

eval($classCode);

Page 70: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

Reflecting Internal Functions

Page 71: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)
Page 72: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

Reflecting Closures

Page 73: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

export

__toString

createFromName

createFromInstance

createFromNode

getShortName

getName

getNamespaceName

inNamespace

getMethods

getImmediateMethods

getMethod

hasMethod

getConstants

getConstant

hasConstant

getConstructor

getProperties

getProperty

hasProperty

getDefaultProperties

getFileName

getLocatedSource

Better Reflection API is BIGGERERgetStartLine

getEndLine

getParentClass

getDocComment

isInternal

isUserDefined

isAbstract

isFinal

getModifiers

isTrait

isInterface

getTraits

getTraitNames

getTraitAliases

getInterfaces

getImmediateInterfaces

getInterfaceNames

isInstance

isSubclassOf

implementsInterface

isInstantiable

isCloneable

isIterateable

__clone

getStaticPropertyValue

setStaticPropertyValue

getAst

setFinal

removeMethod

addMethod

addProperty

removeProperty

Page 74: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

(at least for now)

Out of scope

Page 75: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

It’s not fast :(

Page 76: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)
Page 77: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

Reflecting from STDIN

Page 78: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)
Page 79: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

HHVM

Page 80: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

Reflection(Zend)Extension

Page 81: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

Instantiation & Invocation

Page 82: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

Use Cases

Page 83: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

API diff tool

Page 84: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

What’s next?

Page 85: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

Your ideas welcome!¯\_(ツ)_/¯

Page 87: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

github.com/ /BetterReflection

Better Reflection

Page 88: Mirror, mirror on the wall: Building a new PHP reflection library (DPC 2016)

Any questions? :)

https://joind.in/talk/eb95dJames Titcumb @asgrim