Mirror, mirror on the wall - Building a new PHP reflection library (Nomad PHP EU 2016)

89
@asgrim Mirror, mirror on the wall: Building a new PHP reflection library James Titcumb Nomad PHP Europe - November 2016

Transcript of Mirror, mirror on the wall - Building a new PHP reflection library (Nomad PHP EU 2016)

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

@asgrim

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

James TitcumbNomad PHP Europe - November 2016

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

@asgrim

Reflection

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

© 1937 Disney’s Snow White - disneyscreencaps.com

Page 6: Mirror, mirror on the wall - Building a new PHP reflection library (Nomad PHP EU 2016)

@asgrim

Page 7: Mirror, mirror on the wall - Building a new PHP reflection library (Nomad PHP EU 2016)

@asgrim

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 (Nomad PHP EU 2016)

@asgrim

● Structure● Metadata● Values● Type introspection● Modification

Reflection

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

@asgrim

How does it work?

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

@asgrim

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 (Nomad PHP EU 2016)

@asgrim

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;

}

ReflectionClass->hasMethod

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

@asgrim

Okay. What now?

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

@asgrim

github.com/ /BetterReflection

Better Reflection!

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

@asgrim

What?

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

@asgrim

Why?

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

@asgrim

Features!

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

@asgrim

How?

Page 19: Mirror, mirror on the wall - Building a new PHP reflection library (Nomad PHP EU 2016)

@asgrim

Page 20: Mirror, mirror on the wall - Building a new PHP reflection library (Nomad PHP EU 2016)

@asgrim

Page 21: Mirror, mirror on the wall - Building a new PHP reflection library (Nomad PHP EU 2016)

@asgrim

Reflector

Source Locator

PhpParser

Reflection

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

@asgrim

Core reflection$reflection = new ReflectionClass(

\My\ExampleClass::class

);

$this->assertSame(

'ExampleClass',

$reflection->getShortName()

);

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

@asgrim

Better Reflection$reflection = ReflectionClass::createFromName(

\My\ExampleClass::class

);

$this->assertSame(

'ExampleClass',

$reflection->getShortName()

);

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

@asgrim

createFromName// 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 (Nomad PHP EU 2016)

@asgrim

buildDefaultReflector// 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 (Nomad PHP EU 2016)

@asgrim

Reflector

Source Locator

PhpParser

Reflection

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

@asgrim

Source Locators

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

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

@asgrim

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 (Nomad PHP EU 2016)

@asgrim

However…

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

@asgrim

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 (Nomad PHP EU 2016)

@asgrim

What’s next?

Now we have CODE!

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

@asgrim

Magic superpowers

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

source: http://goo.gl/HORwLQ

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

@asgrim

So what is AST?

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

@asgrim

Reflector

Source Locator

PhpParser

Reflection

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

@asgrim

PHP Parser<?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 (Nomad PHP EU 2016)

@asgrim

ast-demo-src.php

<?php

echo "Hello world";

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

@asgrim

AST representation

Echo statement

`-- String, value "Hello world"

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

@asgrim

ast-demo-src.php

<?php

echo "Hello " . "world";

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

@asgrim

AST representation

Echo statement

`-- Concat

|-- Left

| `-- String, value "Hello "

`-- Right

`-- String, value "world"

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

@asgrim

ast-demo-src.php

<?php

$a = 5;

$b = 3;

echo $a + ($b * 2);

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

@asgrim

AST representationAssign 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 (Nomad PHP EU 2016)

@asgrim

So what?

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

@asgrim

Reflector

Source Locator

PhpParser

Reflection

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

@asgrim

AST to Reflection

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

@asgrim

Benefits?

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

@asgrim

Example class<?php

class Foo

{

private $bar;

public function thing()

{

}

}

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

@asgrim

AST representationClass, 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 (Nomad PHP EU 2016)

@asgrim

php-ast extension

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

@asgrim

Here be dragons!

Some voodoo...

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

@asgrim

MyClass

class MyClass

{

public function foo()

{

return 5;

}

}

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

@asgrim

Create the reflection// 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 (Nomad PHP EU 2016)

@asgrim

Override the body// 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 (Nomad PHP EU 2016)

@asgrim

Register Better Reflection autoloaderuse BetterReflection\Util\Autoload\ClassLoader;

use BetterReflection\Util\Autoload\ClassLoaderMethod\EvalLoader;

use BetterReflection\Util\Autoload\ClassPrinter\PhpParserPrinter;

// Note - this part is WIP at the moment, still in PR :)

$loader = new ClassLoader(new EvalLoader(new PhpParserPrinter()));

$loader->addClass($classInfo);

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

@asgrim

Create the patched class

// 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 (Nomad PHP EU 2016)
Page 57: Mirror, mirror on the wall - Building a new PHP reflection library (Nomad PHP EU 2016)

@asgrim

astkit

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

@asgrim

astkit$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 (Nomad PHP EU 2016)

@asgrim

Difficulties...

Page 60: Mirror, mirror on the wall - Building a new PHP reflection library (Nomad PHP EU 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 (Nomad PHP EU 2016)

@asgrim

Type determination<?php

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

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

class Foo

{

public function something()

{

throw new InvalidArgumentException('Oh noes!');

}

}

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

@asgrim

Type determination<?php

namespace My\Package;

use Some\Package\InvalidArgumentException;

class Foo

{

public function something()

{

throw new InvalidArgumentException('Oh noes!');

}

}

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

@asgrim

Type determination

● FindParameterType● FindPropertyType● FindReturnType● FindTypeFromAst

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

@asgrim

Type determination$finder = new FindTypeFromAst();

$namespace = '';

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

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

}

$type = $finder(

$className,

$method->getLocatedSource(),

$namespace

);

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

@asgrim

DocBlock Parent Traversal Type Resolution

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

@asgrim

class Foo {

/**

* @return int

*/

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

}

class Bar extends Foo {

/**

* {@inheritDoc}

*/

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

}

DocBlock Parent Traversal Type Resolution

It’s an “int” return type!

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

@asgrim

interface Blammo {

/**

* @return string

*/

public function myMethod();

}

class Foo {

/**

* @return int

*/

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

}

class Bar extends Foo implements Blammo {

/**

* {@inheritDoc}

*/

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

}

DocBlock Parent Traversal Type Resolution

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

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

@asgrim

interface Blammo {

/**

* @return string

*/

public function myMethod();

}

class Foo {

/**

* @return int

*/

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

}

class Bar extends Foo implements Blammo {

/**

* {@inheritDoc}

*/

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

}

DocBlock Parent Traversal Type Resolution

Return type: int|string

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

@asgrim

Loading Modified Reflections

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

@asgrim

Loading Modified Reflections

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

$methodInfo->setBodyFromClosure(function () {

// Nasty, evil, malicious code here ???

});

Page 71: Mirror, mirror on the wall - Building a new PHP reflection library (Nomad PHP EU 2016)

@asgrim

Reflecting Internal Functions

Page 72: Mirror, mirror on the wall - Building a new PHP reflection library (Nomad PHP EU 2016)

@asgrim

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

@asgrim

Reflecting Closures

Page 74: Mirror, mirror on the wall - Building a new PHP reflection library (Nomad PHP EU 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 75: Mirror, mirror on the wall - Building a new PHP reflection library (Nomad PHP EU 2016)

@asgrim

(at least for now)

Out of scope

Page 76: Mirror, mirror on the wall - Building a new PHP reflection library (Nomad PHP EU 2016)

@asgrim

It’s not fast :(

Page 77: Mirror, mirror on the wall - Building a new PHP reflection library (Nomad PHP EU 2016)

@asgrim

Page 78: Mirror, mirror on the wall - Building a new PHP reflection library (Nomad PHP EU 2016)

@asgrim

Reflecting from STDIN

Page 79: Mirror, mirror on the wall - Building a new PHP reflection library (Nomad PHP EU 2016)

@asgrim

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

@asgrim

HHVM

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

@asgrim

Reflection(Zend)Extension

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

@asgrim

Instantiation & Invocation

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

@asgrim

Use Cases

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

@asgrim

API diff tool

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

@asgrim

What’s next?

Page 86: Mirror, mirror on the wall - Building a new PHP reflection library (Nomad PHP EU 2016)

@asgrim

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

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

@asgrim

github.com/ /BetterReflection

Better Reflection

Page 89: Mirror, mirror on the wall - Building a new PHP reflection library (Nomad PHP EU 2016)

Any questions? :)

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