Date post: | 19-Jan-2017 |
Category: |
Technology |
Upload: | james-titcumb |
View: | 211 times |
Download: | 2 times |
Mirror, mirror on the wall: Building a new PHP reflection library
James TitcumbDutch PHP Conference 2016
James Titcumbwww.jamestitcumb.comwww.roave.comwww.phphants.co.ukwww.phpsouthcoast.co.uk@asgrim
Who is this guy?
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!¯\_(ツ)_/¯
● https://github.com/nikic/PHP-Parser● https://github.com/nikic/php-ast● https://github.com/sgolemon/astkit● https://gist.github.
com/sgolemon/f65fafadd90ed26f05be
Resources
github.com/ /BetterReflection
Better Reflection
Any questions? :)
https://joind.in/talk/eb95dJames Titcumb @asgrim