Repository: Roave/BetterReflection (php)

Issue:
Wrong ReflectionMethod::getPrototype() behaviour for constructors
Constructors in PHP have special handling - their signature isn't enforced for overriding constructors in child classes unless the constructor in parent class (or interface) is abstract.

See: https://3v4l.org/dgHmi

BetterReflection does not reflect this: https://gist.github.com/ondrejmirtes/27d06ccc206dbf8dc0c08884205b0636

Additionally, I suspect that Adapter\ReflectionMethod::getPrototype() will never actually throw ReflectionException as the runtime reflection does (https://github.com/Roave/BetterReflection/blob/48b8cb8cbfae53481137079010a65b52474ba9d0/src/Reflection/Adapter/ReflectionMethod.php#L378-L384)
Wrong ReflectionMethod::getPrototype() behaviour when traits are involved
In this case, `Foo::doFoo()` prototype should be FooInterface, but BetterReflection says that the method does not have a prototype.

```php
interface FooInterface
{
    
    public function doFoo(): void;
    
}

trait FooTrait
{
    
    public function doFoo(): void
    {
        
    }
    
}

class Foo implements FooInterface
{
    
    use FooTrait;
    
}
```

Reproduction: https://gist.github.com/ondrejmirtes/b8b451fdb8249c45b68a40beccee4554

Pull request:
`ReflectionMethod::getPrototype()` should report implementing vlass prototype, and skip constructors
Fixes #640 
Fixes #639 

Patch:
diff --git a/src/Reflection/ReflectionMethod.php b/src/Reflection/ReflectionMethod.php
index f52d20a4..313ec970 100644
--- a/src/Reflection/ReflectionMethod.php
+++ b/src/Reflection/ReflectionMethod.php
@@ -103,7 +103,7 @@ class ReflectionMethod extends ReflectionFunctionAbstract
      */
     public function getPrototype() : self
     {
-        $currentClass = $this->getDeclaringClass();
+        $currentClass = $this->getImplementingClass();
 
         while ($currentClass) {
             foreach ($currentClass->getImmediateInterfaces() as $interface) {
@@ -121,6 +121,10 @@ class ReflectionMethod extends ReflectionFunctionAbstract
             $prototype = $currentClass->getMethod($this->getName())->findPrototype();
 
             if ($prototype !== null) {
+                if ($this->isConstructor() && ! $prototype->isAbstract()) {
+                    break;
+                }
+
                 return $prototype;
             }
         }


Test patch:
diff --git a/test/unit/Fixture/PrototypeTree.php b/test/unit/Fixture/PrototypeTree.php
index a938c494..4f5a49f8 100644
--- a/test/unit/Fixture/PrototypeTree.php
+++ b/test/unit/Fixture/PrototypeTree.php
@@ -143,3 +143,50 @@ namespace Boom {
     class A implements Foo {}
     class B extends A implements Boo {}
 }
+
+namespace Construct {
+    class Foo
+    {
+        public function __construct(int $i)
+        {
+        }
+    }
+
+    class Bar extends Foo
+    {
+        public function __construct(string $s)
+        {
+        }
+    }
+
+    abstract class Lorem
+    {
+        abstract public function __construct(int $i);
+    }
+
+    class Ipsum extends Lorem
+    {
+        public function __construct(int $i)
+        {
+        }
+    }
+}
+
+namespace Traits {
+    interface FooInterface
+    {
+        public function doFoo(): void;
+    }
+
+    trait FooTrait
+    {
+        public function doFoo(): void
+        {
+        }
+    }
+
+    class Foo implements FooInterface
+    {
+        use FooTrait;
+    }
+}
diff --git a/test/unit/Reflection/ReflectionMethodTest.php b/test/unit/Reflection/ReflectionMethodTest.php
index 3da716ee..a2428755 100644
--- a/test/unit/Reflection/ReflectionMethodTest.php
+++ b/test/unit/Reflection/ReflectionMethodTest.php
@@ -315,6 +315,9 @@ class ReflectionMethodTest extends TestCase
             ['Foom\A', 'foo', 'Foom\Foo'],
             ['ClassE', 'boo', 'ClassC'],
             ['ClassF', 'zoo', 'ClassD'],
+            ['Construct\Bar', '__construct', null],
+            ['Construct\Ipsum', '__construct', 'Construct\Lorem'],
+            ['Traits\Foo', 'doFoo', 'Traits\FooInterface'],
         ];
     }