Repository: Roave/BetterReflection (php)

Issue:
`ReflectionClass::hasMethod()`, `getMethod()` etc should be case insensitive
Only adapter is case insensitive now but there are strange bugs like https://github.com/Roave/BetterReflection/compare/master...kukulich:case-insensitive-methods (it returns two methods now).

I can prepare PR if you agree.

Pull request description:
Methods are case insensitive, and should be mapped and looked up without considering casing
Fixes #588 

Patch:
diff --git a/src/Reflection/Adapter/ReflectionClass.php b/src/Reflection/Adapter/ReflectionClass.php
index 9c1255bc..243f86e5 100644
--- a/src/Reflection/Adapter/ReflectionClass.php
+++ b/src/Reflection/Adapter/ReflectionClass.php
@@ -168,7 +168,7 @@ class ReflectionClass extends CoreReflectionClass
      */
     public function hasMethod($name)
     {
-        return $this->betterReflectionClass->hasMethod($this->getMethodRealName($name));
+        return $this->betterReflectionClass->hasMethod($name);
     }
 
     /**
@@ -176,20 +176,7 @@ class ReflectionClass extends CoreReflectionClass
      */
     public function getMethod($name)
     {
-        return new ReflectionMethod($this->betterReflectionClass->getMethod($this->getMethodRealName($name)));
-    }
-
-    private function getMethodRealName(string $name) : string
-    {
-        $realMethodNames = array_map(static function (BetterReflectionMethod $method) : string {
-            return $method->getName();
-        }, $this->betterReflectionClass->getMethods());
-
-        $methodNames = array_combine(array_map(static function (string $methodName) : string {
-            return strtolower($methodName);
-        }, $realMethodNames), $realMethodNames);
-
-        return $methodNames[strtolower($name)] ?? $name;
+        return new ReflectionMethod($this->betterReflectionClass->getMethod($name));
     }
 
     /**
diff --git a/src/Reflection/ReflectionClass.php b/src/Reflection/ReflectionClass.php
index 1d36c699..a23af6a1 100644
--- a/src/Reflection/ReflectionClass.php
+++ b/src/Reflection/ReflectionClass.php
@@ -89,7 +89,7 @@ class ReflectionClass implements Reflection
 
     /**
      * @var ReflectionMethod[]|null
-     * @psalm-var ?array<string, ReflectionMethod>
+     * @psalm-var ?array<lowercase-string, ReflectionMethod>
      */
     private $cachedMethods;
 
@@ -341,7 +341,7 @@ class ReflectionClass implements Reflection
      *
      * @return ReflectionMethod[] indexed by method name
      *
-     * @psalm-return array<string, ReflectionMethod>
+     * @psalm-return array<lowercase-string, ReflectionMethod>
      */
     private function getMethodsIndexedByName() : array
     {
@@ -352,7 +352,7 @@ class ReflectionClass implements Reflection
         $cachedMethods = [];
 
         foreach ($this->getAllMethods() as $method) {
-            $methodName = $method->getName();
+            $methodName = strtolower($method->getName());
 
             if (isset($cachedMethods[$methodName])) {
                 continue;
@@ -444,13 +444,14 @@ class ReflectionClass implements Reflection
      */
     public function getMethod(string $methodName) : ReflectionMethod
     {
-        $methods = $this->getMethodsIndexedByName();
+        $lowercaseMethodName = strtolower($methodName);
+        $methods             = $this->getMethodsIndexedByName();
 
-        if (! isset($methods[$methodName])) {
+        if (! isset($methods[$lowercaseMethodName])) {
             throw new OutOfBoundsException('Could not find method: ' . $methodName);
         }
 
-        return $methods[$methodName];
+        return $methods[$lowercaseMethodName];
     }
 
     /**


Test patch:
diff --git a/test/unit/Fixture/ClassWithCaseInsensitiveMethods.php b/test/unit/Fixture/ClassWithCaseInsensitiveMethods.php
new file mode 100644
index 00000000..b9942ee2
--- /dev/null
+++ b/test/unit/Fixture/ClassWithCaseInsensitiveMethods.php
@@ -0,0 +1,17 @@
+<?php
+
+namespace Roave\BetterReflectionTest\Fixture;
+
+class ParentForClassWithCaseInsensitiveMethods
+{
+    public function FOO()
+    {
+    }
+}
+
+class ClassWithCaseInsensitiveMethods extends ParentForClassWithCaseInsensitiveMethods
+{
+    public function foo()
+    {
+    }
+}
diff --git a/test/unit/Reflection/Adapter/ReflectionClassTest.php b/test/unit/Reflection/Adapter/ReflectionClassTest.php
index bfcf285c..04d7cc6f 100644
--- a/test/unit/Reflection/Adapter/ReflectionClassTest.php
+++ b/test/unit/Reflection/Adapter/ReflectionClassTest.php
@@ -190,50 +190,6 @@ class ReflectionClassTest extends TestCase
         self::assertFalse($reflectionClassAdapter->getParentClass());
     }
 
-    public function testHasMethodIsCaseInsensitive() : void
-    {
-        $betterReflectionMethod = $this->createMock(BetterReflectionMethod::class);
-        $betterReflectionMethod
-            ->method('getName')
-            ->willReturn('foo');
-
-        $betterReflectionClass = $this->createMock(BetterReflectionClass::class);
-        $betterReflectionClass
-            ->method('getMethods')
-            ->willReturn([$betterReflectionMethod]);
-        $betterReflectionClass
-            ->method('hasMethod')
-            ->with('foo')
-            ->willReturn(true);
-
-        $reflectionClassAdapter = new ReflectionClassAdapter($betterReflectionClass);
-
-        self::assertTrue($reflectionClassAdapter->hasMethod('foo'));
-        self::assertTrue($reflectionClassAdapter->hasMethod('FOO'));
-    }
-
-    public function testGetMethodIsCaseInsensitive() : void
-    {
-        $betterReflectionMethod = $this->createMock(BetterReflectionMethod::class);
-        $betterReflectionMethod
-            ->method('getName')
-            ->willReturn('foo');
-
-        $betterReflectionClass = $this->createMock(BetterReflectionClass::class);
-        $betterReflectionClass
-            ->method('getMethods')
-            ->willReturn([$betterReflectionMethod]);
-        $betterReflectionClass
-            ->method('getMethod')
-            ->with('foo')
-            ->willReturn($betterReflectionMethod);
-
-        $reflectionClassAdapter = new ReflectionClassAdapter($betterReflectionClass);
-
-        self::assertSame('foo', $reflectionClassAdapter->getMethod('foo')->getName());
-        self::assertSame('foo', $reflectionClassAdapter->getMethod('FOO')->getName());
-    }
-
     public function testGetMethodsFilter() : void
     {
         $publicBetterReflectionMethod = $this->createMock(BetterReflectionMethod::class);
diff --git a/test/unit/Reflection/ReflectionClassTest.php b/test/unit/Reflection/ReflectionClassTest.php
index 9a02929a..831c6963 100644
--- a/test/unit/Reflection/ReflectionClassTest.php
+++ b/test/unit/Reflection/ReflectionClassTest.php
@@ -48,6 +48,7 @@ use Roave\BetterReflectionTest\ClassWithInterfacesOther;
 use Roave\BetterReflectionTest\Fixture;
 use Roave\BetterReflectionTest\Fixture\AbstractClass;
 use Roave\BetterReflectionTest\Fixture\ClassForHinting;
+use Roave\BetterReflectionTest\Fixture\ClassWithCaseInsensitiveMethods;
 use Roave\BetterReflectionTest\Fixture\ClassWithMissingParent;
 use Roave\BetterReflectionTest\Fixture\ExampleClass;
 use Roave\BetterReflectionTest\Fixture\ExampleClassWhereConstructorIsNotFirstMethod;
@@ -208,6 +209,16 @@ class ReflectionClassTest extends TestCase
         self::assertCount($count, $classInfo->getImmediateMethods($filter));
     }
 
+    public function testCaseInsensitiveMethods() : void
+    {
+        $classInfo = (new ClassReflector(new SingleFileSourceLocator(
+            __DIR__ . '/../Fixture/ClassWithCaseInsensitiveMethods.php',
+            $this->astLocator
+        )))->reflect(ClassWithCaseInsensitiveMethods::class);
+
+        self::assertCount(1, $classInfo->getMethods());
+    }
+
     public function testGetMethodsReturnsInheritedMethods() : void
     {
         $classInfo = (new ClassReflector(new SingleFileSourceLocator(
@@ -674,6 +685,27 @@ PHP;
         self::assertTrue($classInfo->hasMethod('someMethod'));
     }
 
+    public function testHasMethodIsCaseInsensitive() : void
+    {
+        $reflector = new ClassReflector($this->getComposerLocator());
+        $classInfo = $reflector->reflect(ExampleClass::class);
+
+        self::assertTrue($classInfo->hasMethod('someMethod'));
+        self::assertTrue($classInfo->hasMethod('SOMEMETHOD'));
+        self::assertTrue($classInfo->hasMethod('somemethod'));
+    }
+
+    public function testGetMethodIsCaseInsensitive() : void
+    {
+        $reflector = new ClassReflector($this->getComposerLocator());
+        $classInfo = $reflector->reflect(ExampleClass::class);
+
+        $method1 = $classInfo->getMethod('someMethod');
+        $method2 = $classInfo->getMethod('SOMEMETHOD');
+
+        self::assertSame($method1, $method2);
+    }
+
     public function testGetDefaultProperties() : void
     {
         $classInfo = (new ClassReflector(new SingleFileSourceLocator(