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

Post on 19-Jan-2017

208 views 2 download

Transcript of 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

Reflection

@asgrim

© 1937 Disney’s Snow White - disneyscreencaps.com

Mostly this...public function testSomething()

{

$myObj = new Thing();

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

$propReflection->setAccessible(true);

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

// ... whatever ...

}

● Structure● Metadata● Values● Type introspection● Modification

Reflection

How does it work?

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...

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;

}

Okay. What now?

github.com/ /BetterReflection

Better Reflection!

What?

Why?

Features!

How?

Reflector

Source Locator

PhpParser

Reflection

$reflection = new ReflectionClass(

\My\ExampleClass::class

);

$this->assertSame(

'ExampleClass',

$reflection->getShortName()

);

$reflection = ReflectionClass::createFromName(

\My\ExampleClass::class

);

$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(),

]));

}

Reflector

Source Locator

PhpParser

Reflection

Source Locators

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

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

However…

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!

What’s next?

Now we have CODE!

Magic superpowers

source: http://goo.gl/HORwLQ

So what is AST?

Reflector

Source Locator

PhpParser

Reflection

<?php

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";

Echo statement

`-- String, value "Hello world"

<?php

echo "Hello " . "world";

Echo statement

`-- Concat

|-- Left

| `-- String, value "Hello "

`-- Right

`-- String, value "world"

<?php

$a = 5;

$b = 3;

echo $a + ($b * 2);

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

So what?

Reflector

Source Locator

PhpParser

Reflection

AST to Reflection

Benefits?

<?php

class Foo

{

private $bar;

public function thing()

{

}

}

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]

php-ast extension

Here be dragons!

Some voodoo...

class MyClass

{

public function foo()

{

return 5;

}

}

// Create the reflection first

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

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

// Or use specific source locators as already shown

// Override the body...!

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

$methodInfo->setBodyFromClosure(function () {

return 4;

});

// Bring the code into context now

$printer = new CodePrinter();

$classCode = $printer->prettyPrint([

$classInfo->getAst(),

]);

eval($classCode);

// Now create an instance, and call the

// method on this...

$c = new MyClass();

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

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

Difficulties...

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 )

}

<?php

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

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

class Foo

{

public function something()

{

throw new InvalidArgumentException('Oh noes!');

}

}

Type determination

<?php

namespace My\Package;

use Some\Package\InvalidArgumentException;

class Foo

{

public function something()

{

throw new InvalidArgumentException('Oh noes!');

}

}

Type determination

Type determination

● FindParameterType● FindPropertyType● FindReturnType● FindTypeFromAst

$finder = new FindTypeFromAst();

$namespace = '';

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

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

}

$type = $finder(

$className,

$method->getLocatedSource(),

$namespace

);

Type determination

DocBlock Parent Traversal Type

Resolution

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!

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: ¯\_(ツ)_/¯

Evaluating Modified Reflections

Evaluating Modified Reflections

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

$methodInfo->setBodyFromClosure(function () {

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

});

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

$classInfo->getAst(),

]);

eval($classCode);

Reflecting Internal Functions

Reflecting Closures

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

(at least for now)

Out of scope

It’s not fast :(

Reflecting from STDIN

HHVM

Reflection(Zend)Extension

Instantiation & Invocation

Use Cases

API diff tool

What’s next?

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

github.com/ /BetterReflection

Better Reflection

Any questions? :)

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