Completed
Pull Request — master (#27)
by David
11:28
created

MissingTypeHintInMethodRule::isInherited2()   B

Complexity

Conditions 6
Paths 14

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 22
rs 8.9457
c 0
b 0
f 0
nc 14
cc 6
nop 2
1
<?php
2
3
4
namespace TheCodingMachine\PHPStan\Rules\TypeHints;
5
6
use PHPStan\Analyser\Scope;
7
use PHPStan\Broker\Broker;
8
use PHPStan\Reflection\ClassReflection;
9
use PHPStan\Reflection\ParametersAcceptorSelector;
10
use PHPStan\Reflection\ParametersAcceptorWithPhpDocs;
11
use PHPStan\Reflection\Php\PhpMethodReflection;
12
use Roave\BetterReflection\Reflection\ReflectionFunction;
13
use Roave\BetterReflection\Reflection\ReflectionFunctionAbstract;
14
use Roave\BetterReflection\Reflection\ReflectionMethod;
15
use Roave\BetterReflection\Reflection\ReflectionParameter;
16
use PhpParser\Node;
17
use PHPStan\Reflection\MethodReflection;
18
19
class MissingTypeHintInMethodRule extends AbstractMissingTypeHintRule
20
{
21
    private const RETURN_BLACKLIST = [
22
        '__construct' => true,
23
        '__destruct' => true,
24
        '__call' => true,
25
        '__callStatic' => true,
26
        '__get' => true,
27
        '__set' => true,
28
        '__isset' => true,
29
        '__unset' => true,
30
        '__sleep'  => true,
31
        '__wakeup'  => true,
32
        '__toString'  => true,
33
        '__invoke'  => true,
34
        '__set_state' => true,
35
        '__clone' => true,
36
        '__debugInfo' => true
37
    ];
38
39
    public function getNodeType(): string
40
    {
41
        return Node\Stmt\ClassMethod::class;
42
    }
43
44
    /**
45
     * @param Node\Stmt\ClassMethod $node
46
     * @return bool
47
     */
48
    public function isReturnIgnored(Node $node): bool
49
    {
50
        return isset(self::RETURN_BLACKLIST[$node->name->name]);
0 ignored issues
show
Bug introduced by
Accessing name on the interface PhpParser\Node suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
51
    }
52
53
    protected function getReflection(Node\FunctionLike $function, Scope $scope, Broker $broker) : ParametersAcceptorWithPhpDocs
54
    {
55
        if (!$scope->isInClass()) {
56
            throw new \PHPStan\ShouldNotHappenException();
57
        }
58
        $nativeMethod = $scope->getClassReflection()->getNativeMethod($function->name->name);
0 ignored issues
show
Bug introduced by
Accessing name on the interface PhpParser\Node\FunctionLike suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
59
        if (!$nativeMethod instanceof PhpMethodReflection) {
60
            throw new \PHPStan\ShouldNotHappenException();
61
        }
62
        /** @var \PHPStan\Reflection\ParametersAcceptorWithPhpDocs $parametersAcceptor */
63
        return ParametersAcceptorSelector::selectSingle($nativeMethod->getVariants());
64
    }
65
66
    protected function shouldSkip(Node\FunctionLike $function, Scope $scope): bool
67
    {
68
        // We should skip if the method is inherited!
69
        if (!$scope->isInClass()) {
70
            throw new \PHPStan\ShouldNotHappenException();
71
        }
72
        $nativeMethod = $scope->getClassReflection()->getNativeMethod($function->name->name);
0 ignored issues
show
Bug introduced by
Accessing name on the interface PhpParser\Node\FunctionLike suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
73
74
        return $this->isInherited2($nativeMethod, $scope->getClassReflection());
75
76
    }
77
78
    private function isInherited2(MethodReflection $method, ClassReflection $class = null): bool
79
    {
80
        if ($class === null) {
81
            $class = $method->getDeclaringClass();
82
        }
83
        $interfaces = $class->getInterfaces();
84
        foreach ($interfaces as $interface) {
85
            if ($interface->hasMethod($method->getName())) {
86
                return true;
87
            }
88
        }
89
90
        $parentClass = $class->getParentClass();
91
        if ($parentClass !== false) {
92
            if ($parentClass->hasMethod($method->getName())) {
93
                return true;
94
            }
95
            return $this->isInherited2($method, $parentClass);
96
        }
97
98
        return false;
99
    }
100
}
101