Giter VIP home page Giter VIP logo

betterreflection's People

Contributors

asgrim avatar backendtea avatar clxmstaab avatar danielbadura avatar dantleech avatar dependabot-preview[bot] avatar dependabot[bot] avatar forrest79 avatar github-actions[bot] avatar herndlm avatar janedbal avatar janlanger avatar kukulich avatar lctrs avatar majkl578 avatar mapkuff avatar michaeljoelphillips avatar moufmouf avatar muglug avatar ocramius avatar ondrejmirtes avatar orklah avatar paul-thebaud avatar paxal avatar popsul avatar renovate[bot] avatar sasezaki avatar staabm avatar tomasvotruba avatar vlastavesely avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

betterreflection's Issues

Cannot determine default value for internal classes

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.

Implement Method->getPrototype() fully

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

Implement "autoloading" source locator

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.

TL;DR

  • Create new AutoloadingSourceLocator that uses core refleciton API to locate source code filename
  • Cleanup constructors so they are empty
  • Create StaticReflector that uses AutoloadingSourceLocator
  • Update Reflection* classes to use AutoloadingSourceLocator
  • Create 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.
  • This allows us to do the following (i.e. replicate almost exactly the core reflection API instantiation technique):
$reflectionClass = new \BetterReflection\Reflection\ReflectionClass('My\Foo\Bar');

Implement ReflectionObject

It's not supported currently because it requires an instance of an existing class, which means we can't currently implement...

Replace getClassesFromFile with a better API for getAllClasses

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

Can compile predefined constants

We can actually reflect on probably "most" constants, certainly internal constants.

Simply check defined('blah') first, and then replace the value :)

Investigate PHP 7 compatibility

How do we fare in PHP 7? Particularly interesting things at the moment:

  • PHP7's AST ext (may require significant rewrites, also loses PHP 5 compat...) probably not, requires an ext currently
  • How does ReflectionParameter fare when dealing with STHs (e.g. int, string etc.) - I believe this should "just work"?
  • Can we grab the RTH from methods using PhpParser? (Need to check PHP7 compat of that) - then we can implement this alongside #10

Reflection* classes should be uncloneable

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

LocatedSource should check file exists / is file

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

isOptional determination is a mess

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.

Implement accessors without instantiating

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 #114
  • ReflectionClass::setStaticPropertyValue - see #114
  • ReflectionMethod::getClosure - see #354
  • ReflectionMethod::invoke - see #355
  • ReflectionMethod::invokeArgs - see #355
  • ReflectionMethod::setAccessible - see #355
  • ReflectionFunction::getClosure - see #357
  • ReflectionFunction::invoke - see #357
  • ReflectionFunction::invokeArgs - see #357
  • ReflectionProperty::setAccessible - see #343
  • ReflectionProperty::setValue - see #343

TraversableSourceLocator or something

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

Create ReflectionFunctions/ReflectionClasses from internal definitions

We currently can't create ReflectionFunctions and ReflectionClasses 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

  • Internal classes (see #80)
  • Internal functions

Improve the unit test suite further

  • Improve what tests are running
  • Improve coverage by writing more tests
  • Use @covers annotations to limit cover leakage

Acceptable coverage in:

  • Identifier (see #32) - 94%
  • NodeCompiler (see #32) - 82%
  • Reflection (see #65 and 59)
  • Reflector (see #65)
  • SourceLocator (see #32) - 97%
  • TypesFinder (see #28) - 100%

Discover return types from DocBlocks

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_]

Improve how locateIdentifiersByType works under the hood

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

Resolve imported classes for phpdoc blocks

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?

When more than one unique symbol is found in a source locator, decide what to do

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:

  • throw an exception if an API such as getClass($name) is used (expecting 1 result, found 2)
  • return all reflections if an API such as getClassesByName($name) is used (2 results)

This is yet to be decided.

Context should be required parameter in ResolveTypes type finder

Currently the signature is:

public function __invoke($stringTypes, Context $context = null)

However $context should not be optional, so change to:

public function __invoke($stringTypes, Context $context)

However, this implementation depends on #29 and #30 before we can remove the optional-ness.

Extend core \ReflectionClass etc.

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

FindPropertyType could fail when using StringSourceLocator

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)

Add ability to create a ReflectionFunction from a closure

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

Need to become "mostly" compatible with PHP's Reflection API

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:

  • Extension-related things 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 #15
  • Methods ReflectionMethod->invoke as we are not actually loading the classes, we cannot really invoke methods (yes, it's technically possible, but not logical) - see #14
  • State modifications like ReflectionProperty->setAccessible and ReflectionProperty->setValue these require instances of classes, but we never instantiate classes - see #14
  • Instance-requiring ReflectionObject simply because it requires an instance, we cannot implement it, because that would require the class to be loaded - see #16
  • ReflectionFunctionAbstract->isClosure can't be implemented currently - see #37
  • Internal reflection is not currently possible - see #38

Compatibility summary

  • ReflectionClass - PR #59
  • ReflectionFunctionAbstract - PR #40
  • ReflectionMethod - PR #58
  • ReflectionParameter - PR #56
  • ReflectionFunction - PR #40
  • ReflectionProperty - PR #9

Implement lazy/runtime reflection

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.

  • ReflectionProperty - #82
  • ReflectionParameter - #83
  • ReflectionMethod - #88
  • ReflectionFunctionAbstract - #84
  • ReflectionFunction (does not have anything to make lazy currently) - #84
  • ReflectionClass #89

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.