roave / betterreflection Goto Github PK
View Code? Open in Web Editor NEW:crystal_ball: Better Reflection is a reflection API that aims to improve and provide more features than PHP's built-in reflection API.
License: MIT License
:crystal_ball: Better Reflection is a reflection API that aims to improve and provide more features than PHP's built-in reflection API.
License: MIT License
This issue is a result of the discussion here: https://github.com/Roave/BetterReflection/pull/9/files#r33761101
There are potential thorny issues surrounding recursion / not being able to access the Reflector
instance etc. here, that prevent us from implementing getClass
in a clean way.
Comment from @Ocramius (see here)
You can create an accessor even without the instance of the object or internal reflection.
See http://ocramius.github.io/blog/accessing-private-php-class-members-without-reflection/
This would mean we could support the following methods:
ReflectionClass::getStaticPropertyValue
- see #114ReflectionClass::setStaticPropertyValue
- see #114ReflectionMethod::getClosure
- see #354ReflectionMethod::invoke
- see #355ReflectionMethod::invokeArgs
- see #355ReflectionMethod::setAccessible
- see #355ReflectionFunction::getClosure
- see #357ReflectionFunction::invoke
- see #357ReflectionFunction::invokeArgs
- see #357ReflectionProperty::setAccessible
- see #343ReflectionProperty::setValue
- see #343From discussion on https://github.com/asgrim/better-reflection/pull/3/files#r33560669
getClassesFromFile
without parameter? Seems a bit weird. The API is probably also redundant, now that you have the locator:$allInString = (new Reflector(new FilenameSourceLocator('path/to/file.php'))->getAllClasses(); $allInFile = (new Reflector(new StringSourceLocator($code))->getAllClasses(); $allAutoloadable = (new Reflector(new ComposerSourceLocator($classLoader))->getAllClasses();
class FindPropertyType
{
/**
* Given a property, attempt to find the type of the property
*
* @param PropertyNode $node
* @param ReflectionProperty $reflectionProperty
* @return Type[]
*/
public function __invoke(PropertyNode $node, ReflectionProperty $reflectionProperty)
{
$contextFactory = new ContextFactory();
$context = $contextFactory->createFromReflector($reflectionProperty);
Currently, the $contextFactory->createFromReflector
depends on the $reflectionProperty
returning a filename from which the factory calls file_get_contents
to tokenize and find the imports. However, with StringSourceLocator
there is no file, and as such this will fail.
We need to fix this in the same way as #45 has been implemented (to call createFromNamespace
instead), as we will be able to access the LocatedSource
(which should always have non-empty valid PHP code to create context from)
Using the DocBlocks, we can at least attempt to resolve the return types for functions and methods.
/**
* @return int|null
*/
public function aMethodReturningInt() { /* ... */ }
Implement a new method getDocBlockReturnTypes()
which would return an array of Type
objects. In this case, it would return something like a [Types\Integer,Types\Null_]
At the moment if the node is an array, we just return an empty array []
as the value. We should traverse the nodes and determine the real values in the array.
Depends on ReflectionClass being implemented fully, in #7
I'm not 100% sure how this should be implemented, as ReflectionMethod::getPrototype() docs are pretty vague, and also the fixture below, from which I tested against core reflection and created a test case (see below) seems inconsistent.
From what I understand, ClassB::foo
prototype is ClassA::foo
, yet ClassC::foo
which directly implements FooInterface
descends into FooInterface::foo
as the prototype.
It looks like traits are ignored for deciding the prototypes, which makes things slightly easier at least. Either way, we depend on ReflectionClass::getInterfaces()
and ReflectionClass::getParentClass()
to be implemented before we can do this (see #7).
Fixture, PrototypeTree.php
<?php
interface FooInterface {
public function foo($a, $b);
}
abstract class ClassA implements FooInterface {
abstract public function foo($a, $b);
}
class ClassB extends ClassA {
public function foo($a, $b = 123) {}
}
class ClassC implements FooInterface {
public function foo($a, $b = 123) {}
}
interface BarInterface {
public function bar();
}
trait MyTrait {
abstract public function bar();
}
class ClassT {
use MyTrait;
public function bar() {}
}
Failing test case:
public function testGetPrototype()
{
$fixture = __DIR__ . '/../Fixture/PrototypeTree.php';
$reflector = new ClassReflector(new SingleFileSourceLocator($fixture));
$b = $reflector->reflect('ClassB')->getMethod('foo')->getPrototype();
$this->assertInstanceOf(ReflectionMethod::class, $b);
$this->assertSame('ClassA', $b->getDeclaringClass()->getName());
$c = $reflector->reflect('ClassC')->getMethod('foo')->getPrototype();
$this->assertInstanceOf(ReflectionMethod::class, $c);
$this->assertSame('FooInterface', $c->getDeclaringClass()->getName());
$this->setExpectedException(MethodPrototypeNotFound::class);
$t = $reflector->reflect('ClassT')->getMethod('bar')->getPrototype();
}
Currently ~150 tests are marked incomplete for the following reason:
152) BetterReflectionTest\SourceLocator\PhpInternalSourceLocatorTest::testCanReflectInternalClasses with data set "DateTimeInterface" ('DateTimeInterface')
Can't reflect class "DateTimeInterface" due to an internal reflection exception: "Cannot determine default value for internal functions". Consider adding a stub class
/home/james/workspace/better-reflection/test/unit/SourceLocator/PhpInternalSourceLocatorTest.php:61
This should be considered a bug and incompatibility.
We currently can't create ReflectionFunction
s and ReflectionClass
es from internal functions/class, as the source code is not available. The only way I see is to use the core reflection API, which is a bit of a fudge, and would rely on #36 anyway (would it even be possible to get type hints? maybe not)
Todo
At the moment, it's a right mess. We could do with refactoring this out, perhaps some static helper classes etc. can be used to simplify logic in the __toString()
methods.
Currently, we read the AST and populate the Reflection*
objects at reflection-creation-time. This could potentially cause issues at some point, where somewhere down the line, an incomplete Reflection*
could be used (case in point, see #27 - had to move the declaringClass
to before the parent setup so that getDeclaringClass()
was available when examining the method).
We could potentially simply store the AST at creation-time, and then when methods are called at run-time, perform required reflections as and when.
https://github.com/Roave/BetterReflection/pull/95/files#diff-c3ee776df4185cfa0fffb53df92d78c3R126
It's a massive block simply full of if
s... meaning complexity of this method is very large.
Could use strategy pattern to reduce this perhaps?
Adapter for core Reflection classes, e.g.
<?php
namespace BetterReflection\CompatibleReflection;
use \ReflectionClass as CoreReflectionClass;
use BetterReflection\Reflection\ReflectionClass as BetterReflectionClass;
class ReflectionClass extends CoreReflectionClass
{
/**
* @var BetterReflectionClass
*/
private $betterReflection;
public function __construct()
{
throw new \Exception("Cannot instantiate like this...");
}
public static function createFromNode(
ClassNode $node,
NamespaceNode $namespace = null,
$filename = null
) {
$reflection = new self();
$reflection->betterReflection = BetterReflectionClass::createFromNode($node, $namespace, $filename);
}
// Implement all the core methods
}
Alternatively, just make \BetterReflection\Reflection\ReflectionClass
extend \ReflectionClass
and ensure every method is implemented...
Why? So that something that is (incorrectly) type hinting for \ReflectionClass
instead of \Reflector
can use this library...
At the moment, no context happens in the FindTypeFromAst
type finder.
This needs to be implemented!
This prevents us from writing the same assertFalse(class_exists($yadda, false))
over and over and over again at the end of each test.
LocatedSource should check file exists / is file when constructing. Current behaviour just checks it's a string|null
:
public function __construct($source, $filename)
{
if (!is_string($source) || empty($source)) {
throw new \InvalidArgumentException(
'Source code must be a non-empty string'
);
}
if (!is_string($filename) && null !== $filename) {
throw new \InvalidArgumentException(
'Filename must be a string or null'
);
}
$this->source = $source;
$this->filename = $filename;
}
@Ocramius would like to use BetterReflection in https://github.com/Ocramius/ProxyManager. See if this is possible, and see what we need to do to make it compatible.
If you have a parameter with a default value of a constant (e.g. self::WHATEVER
), we currently do not evaluate the value of self::WHATEVER
, we just return self::WHATEVER
as a string. This is incorrect, we need to (wherever possible) determine the actual value.
Build is failing #379.2
There was 1 failure:
1) BetterReflectionTest\Reflection\ReflectionFunctionAbstractTest::testPopulateFunctionAbstractThrowsExceptionWithInvalidNode
Failed asserting that exception of type "TypeError" matches expected exception "InvalidArgumentException".
Fix urgently, this made it into master when #110 merged by mistake (to fix #108).
Reflection* classes should prevent being able to be cloned.
--TEST--
Reflection class can not be cloned
--CREDITS--
Stefan Koopmanschap <[email protected]>
TestFest PHP|Tek
--SKIPIF--
<?php
if (!extension_loaded('reflection')) print 'skip';
?>
--FILE--
<?php
require 'vendor/autoload.php';
use BetterReflection\Reflector\ClassReflector;
use BetterReflection\SourceLocator\SingleFileSourceLocator;
$reflector = new ClassReflector(new SingleFileSourceLocator(__FILE__));
class Foo {
}
$rc = $reflector->reflect("Foo");
$rc2 = clone($rc);
?>
--EXPECTF--
Fatal error: Uncaught Error: Trying to clone an uncloneable object of class ReflectionClass in %s:%d
Stack trace:
#0 {main}
thrown in %s on line %d
Is this possible?
Failure, possibly due to the fragile AutoloadSourceLocator behaviour:
https://travis-ci.org/Roave/BetterReflection/jobs/70188098#L285
At the moment, no context happens in the FindParameterType
type finder.
This needs to be implemented!
@covers
annotations to limit cover leakageWe can actually reflect on probably "most" constants, certainly internal constants.
Simply check defined('blah')
first, and then replace the value :)
How do we fare in PHP 7? Particularly interesting things at the moment:
ReflectionParameter
fare when dealing with STHs (e.g. int
, string
etc.) - I believe this should "just work"?http://php.net/manual/en/reflectionclass.getmethods.php
http://php.net/manual/en/reflectionclass.getproperties.php
The getMethods()
and getProperties()
both do not actually filter at all - they need to accept an integer parameter.
// Fetch all private methods
$classInfo->getMethods(ReflectionMethod::IS_PRIVATE);
Currently, because we don't load "live" (loaded-into-memory) code, we can't create a ReflectionFunction
using a closure (because we actually don't have source code for it, thus can't create the AST from it). #36 may help to implement this, but because we don't have source code for closures, I'm not even sure if this is possible (without using core Reflection API and hacking it in...?)
In order to automatically reflect using the exact same API as the core Reflection*
classes, we need to be able to essentially ignore the whole SourceLocator
concept, despite it's flexibility.
So, I think it's possible to implement a new AutoloadingSourceLocator
that takes no constructor dependencies, which, funnily enough, would probably need to rely on the internal ReflectionClass
API, e.g.:
// if it's a class we're loading...
$reflectionClass = new \ReflectionClass($identifier->getName());
$filename = $reflectionClass->getFileName();
$source = file_get_contents($filename);
// use these to make the LocatedSource...
Additionally, we'd need to create a "static" reflector, which would be something like:
class StaticReflector
{
public static function reflect(...)
{
return (new Reflector(new AutoloadingSourceLocator()))->reflect($identifier);
}
}
Then we can use this reflector in the constructors or something to reflect (still a bit hazy on how to actually implement this). The downside of this is it means we do have to LOAD the classes. So I also propose making a LoadSafeSourceLocator
interface, which doesn't have any methods, but purely indicates that it "by design" does NOT load up classes into memory. Then in the ..\Reflector\Generic
class, we can check if $reflection implements LoadSafeSourceLocator
, and ensure that the class is not loaded beforehand, and is not loaded after reflection, to ensure the contract is obeyed.
AutoloadingSourceLocator
that uses core refleciton API to locate source code filenameStaticReflector
that uses AutoloadingSourceLocator
Reflection*
classes to use AutoloadingSourceLocator
LoadSafeSourceLocator
interface, any source locator that implements this interface MUST ensure the class does not get loaded ever. These would be the Composer
, SingleFile
and String
source locators, as these do not load file. AutoloadingSourceLocator
would load the file, so MUST NOT implement the LoadSafeSourceLocator
interface.$reflectionClass = new \BetterReflection\Reflection\ReflectionClass('My\Foo\Bar');
Currently they don't... so make it so.
Yep.
Note; probably getStartColumn
and getEndColumn
will be implemented
It does work but there is a mess of code in ReflectionFunctionAbstract::setNodeOptionalFlag
/**
* We must determine if params are optional or not ahead of time, but we
* must do it in reverse...
*/
private function setNodeOptionalFlag()
{
$overallOptionalFlag = true;
$lastParamIndex = (count($this->node->params) - 1);
for ($i = $lastParamIndex; $i >= 0; $i--) {
$hasDefault = ($this->node->params[$i]->default !== null);
// When we find the first parameter that does not have a default,
// flip the flag as all params for this are no longer optional
// EVEN if they have a default value
if (!$hasDefault) {
$overallOptionalFlag = false;
}
$this->node->params[$i]->isOptional = $overallOptionalFlag;
}
}
A simple solution would be to say something like (pseudo code) if parameterindex > numrequiredparams, this param is optional
- however, on checking getNumberOfRequiredParameters()
this has a cyclic dependency on isOptional()
calls... which means they wouldn't be resolved at creation-time. Solve this somehow.
For compatibility, we could run tests vs the internal reflection to ensure the same results are achieved for maximum awesometowns.
Reported initially in Roave/StrictPhp#27
We can move this functionality from Sevenificator into Better Reflection, meaning that both Sevenificator and StrictPhp won't need to maintain separate implementations
e.g.
namespace RstGroup;
use Doctrine\DBAL\Logging\DebugStack;
use Psr\Log\LoggerInterface;
class SqlLogger extends DebugStack
{
/**
* @var LoggerInterface
*/
protected $logger;
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
}
The reflection should auto-resolve LoggerInterface
to the imported Psr\Log\LoggerInterface
. If the class cannot be resolved... throw exception maybe?
The type used for ReflectionFunctionAbstract#$node
is too generic and should be restricted to ClassMethod|Function_
e.g. stuff like ["foo"."bar" => 2 * 3]
They are constant expressions, but currently not supported.
It's not supported currently because it requires an instance of an existing class, which means we can't currently implement...
Following code is perfectly valid in BetterReflection
, but the outcome is rather unexpected:
class Foo {}
class Foo {}
function bar() {}
function bar() {}
const BAZ = 'baz';
const BAZ = 'baz';
Things that better reflection could do:
getClass($name)
is used (expecting 1 result, found 2)getClassesByName($name)
is used (2 results)This is yet to be decided.
Some conceptual "naming" issue that @Ocramius has... StringSourceLocator
doesn't "locate" source, but still returns a LocatedSource
object.
Solutions for this would be to either create a Source
interface that LocatedSource
and something else (that the StringSourceLocator
would return) implement, or create an abstract parent.
Just need to clarify naming and intent
For reference, original discussion at #32 (comment)
BetterReflection/src/Reflection/ReflectionProperty.php
Lines 108 to 110 in 83a9b07
<?php
class Foo {
public static $bar = 'tab';
}
$r = new ReflectionProperty('Foo', 'bar');
var_dump($r->isDefault());
Support "required" type enforcement on parameters for methods and functions... At the moment we only analyse docblock "hints".
Add ability to traverse up DocBlocks which have @inheritdoc annotation
The end goal of this is to come up with something that can locate from multiple files / sources. @Ocramius suggested using something traversable, so you pass in for example a DirectoryIterator
which the locate would use to find the class. I like the flexibility of this approach, but the only question mark for me at the moment is how to determine the filename, for example if an ArrayIterator
was given (which was an array of source code items etc.)
Whenever a method/function argument is defined as variadic, it is becomes by default.
Ref:
Should be:
public function isOptional()
{
return ((bool)$this->node->isOptional) || $this->isVariadic();
}
At the moment, AbstractSourceLocator->locateIdentifiersByType()
uses a dodgy "hack" that means the identifier name (the string *
) is never actually used, but it seems quite abusive. Find a way around this which means we are just searching by IdentifierType
rather than having to provide only an Identifier
Compatibility is being tracked in compatibility.md. We need to aim for all features it is possible to implement. Unfortunately we cannot implement "all" the functionality, for example:
ReflectionExtension
- I don't think we have a way to access the information required by this, nor to tie it all up with our userland reflection (I may be wrong?) - see #15ReflectionMethod->invoke
as we are not actually loading the classes, we cannot really invoke methods (yes, it's technically possible, but not logical) - see #14ReflectionProperty->setAccessible
and ReflectionProperty->setValue
these require instances of classes, but we never instantiate classes - see #14ReflectionObject
simply because it requires an instance, we cannot implement it, because that would require the class to be loaded - see #16ReflectionFunctionAbstract->isClosure
can't be implemented currently - see #37A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.