ReflectorNodeVisitor   F
last analyzed

Complexity

Total Complexity 84

Size/Duplication

Total Lines 311
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 31

Test Coverage

Coverage 97.77%

Importance

Changes 3
Bugs 1 Features 0
Metric Value
wmc 84
c 3
b 1
f 0
lcom 1
cbo 31
dl 0
loc 311
rs 1.3043
ccs 219
cts 224
cp 0.9777

19 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
D enterNode() 0 30 20
B leaveNode() 0 12 8
A addAliases() 0 6 2
A addClass() 0 14 3
A addInterface() 0 8 2
A addTrait() 0 4 1
B addTraitUse() 0 13 5
A addTraitUseAlias() 0 8 2
A addTraitUsePrecedence() 0 6 2
B addProperty() 0 25 5
B addMethod() 0 29 4
A addConstant() 0 12 2
A addFunction() 0 15 1
B addFunctionLikeParameters() 0 26 6
A addStaticVariable() 0 11 3
A addYieldNode() 0 4 1
C resolveValue() 0 38 13
A buildClassLikeReflection() 0 20 3

How to fix   Complexity   

Complex Class

Complex classes like ReflectorNodeVisitor often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ReflectorNodeVisitor, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Benoth\StaticReflection\Parser;
4
5
use Benoth\StaticReflection\Reflection\ReflectionClass;
6
use Benoth\StaticReflection\Reflection\ReflectionConstant;
7
use Benoth\StaticReflection\Reflection\ReflectionFunction;
8
use Benoth\StaticReflection\Reflection\ReflectionFunctionAbstract;
9
use Benoth\StaticReflection\Reflection\ReflectionInterface;
10
use Benoth\StaticReflection\Reflection\ReflectionMethod;
11
use Benoth\StaticReflection\Reflection\ReflectionParameter;
12
use Benoth\StaticReflection\Reflection\ReflectionProperty;
13
use Benoth\StaticReflection\Reflection\ReflectionTrait;
14
use PhpParser\Node as NodeInterface;
15
use PhpParser\Node\Expr as AbstractExprNode;
16
use PhpParser\Node\Expr\Yield_ as YieldNode;
17
use PhpParser\Node\Expr\YieldFrom as YieldFromNode;
18
use PhpParser\Node\Scalar\MagicConst;
19
use PhpParser\Node\Stmt as AbstractNode;
20
use PhpParser\Node\Stmt\Class_ as ClassNode;
21
use PhpParser\Node\Stmt\ClassConst as ClassConstNode;
22
use PhpParser\Node\Stmt\ClassMethod as ClassMethodNode;
23
use PhpParser\Node\Stmt\Function_ as FunctionNode;
24
use PhpParser\Node\Stmt\Interface_ as InterfaceNode;
25
use PhpParser\Node\Stmt\Namespace_ as NamespaceNode;
26
use PhpParser\Node\Stmt\Property as PropertyNode;
27
use PhpParser\Node\Stmt\Static_ as StaticNode;
28
use PhpParser\Node\Stmt\Trait_ as TraitNode;
29
use PhpParser\Node\Stmt\TraitUse as TraitUseNode;
30
use PhpParser\Node\Stmt\TraitUseAdaptation\Alias as TraitUseAliasNode;
31
use PhpParser\Node\Stmt\TraitUseAdaptation\Precedence as TraitUsePrecedenceNode;
32
use PhpParser\Node\Stmt\Use_ as UseNode;
33
use PhpParser\NodeVisitorAbstract;
34
35
class ReflectorNodeVisitor extends NodeVisitorAbstract
36
{
37
    protected $context;
38
39 1128
    public function __construct(ParsingContext $context)
40
    {
41 1128
        $this->context = $context;
42 1128
    }
43
44 1101
    public function enterNode(NodeInterface $node)
45
    {
46
        // @todo Handle global constants
47
48 1101
        if ($node instanceof NamespaceNode) {
49 1047
            $this->context->enterNamespace((string) $node->name);
50 1101
        } elseif ($node instanceof UseNode) {
51 258
            $this->addAliases($node);
52 1101
        } elseif ($node instanceof FunctionNode) {
53 39
            $this->addFunction($node);
54 1101
        } elseif ($node instanceof ClassNode) {
55 996
            $this->addClass($node);
56 1101
        } elseif ($node instanceof InterfaceNode) {
57 291
            $this->addInterface($node);
58 1101
        } elseif ($node instanceof TraitNode) {
59 291
            $this->addTrait($node);
60 1101
        } elseif ($this->context->getReflection() && $node instanceof TraitUseNode) {
61 258
            $this->addTraitUse($node);
62 1101
        } elseif ($this->context->getReflection() && $node instanceof ClassConstNode) {
63 741
            $this->addConstant($node);
64 1101
        } elseif ($this->context->getReflection() && $node instanceof PropertyNode) {
65 786
            $this->addProperty($node);
66 1101
        } elseif ($this->context->getReflection() && $node instanceof ClassMethodNode) {
67 1062
            $this->addMethod($node);
68 1101
        } elseif ($this->context->getFunctionLike() && $node instanceof StaticNode) {
69 450
            $this->addStaticVariable($node);
70 1101
        } elseif ($this->context->getFunctionLike() && ($node instanceof YieldNode || $node instanceof YieldFromNode)) {
71 450
            $this->addYieldNode($node);
72 450
        }
73 1101
    }
74
75 1101
    public function leaveNode(NodeInterface $node)
76
    {
77 1101
        if ($node instanceof FunctionNode || $node instanceof ClassMethodNode) {
78 1101
            $this->context->leaveFunctionLike();
79 1101
        }
80
81 1101
        if ($node instanceof NamespaceNode) {
82 1047
            $this->context->leaveNamespace();
83 1101
        } elseif ($node instanceof FunctionNode || $node instanceof ClassNode || $node instanceof InterfaceNode || $node instanceof TraitNode) {
84 1101
            $this->context->leaveReflection();
85 1101
        }
86 1101
    }
87
88 258
    protected function addAliases(UseNode $node)
89
    {
90 258
        foreach ($node->uses as $use) {
91 258
            $this->context->addAlias($use->alias, (string) $use->name);
92 258
        }
93 258
    }
94
95 996
    protected function addClass(ClassNode $node)
96
    {
97 996
        $class = $this->buildClassLikeReflection($node);
98 996
        $class->setAbstract((bool) $node->isAbstract());
0 ignored issues
show
Bug introduced by
The method setAbstract does only exist in Benoth\StaticReflection\Reflection\ReflectionClass, but not in Benoth\StaticReflection\...lection\ReflectionTrait.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
99 996
        $class->setFinal((bool) $node->isFinal());
0 ignored issues
show
Bug introduced by
The method setFinal does only exist in Benoth\StaticReflection\...lection\ReflectionTrait, but not in Benoth\StaticReflection\...ion\ReflectionInterface.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
100
101 996
        if ($node->extends) {
102 312
            $class->setParent((string) $node->extends);
0 ignored issues
show
Bug introduced by
The method setParent does only exist in Benoth\StaticReflection\Reflection\ReflectionClass, but not in Benoth\StaticReflection\...lection\ReflectionTrait.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
103 312
        }
104
105 996
        foreach ($node->implements as $interface) {
106 708
            $class->addInterface((string) $interface);
0 ignored issues
show
Bug introduced by
The method addInterface does only exist in Benoth\StaticReflection\...ion\ReflectionInterface, but not in Benoth\StaticReflection\Reflection\ReflectionTrait.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
107 996
        }
108 996
    }
109
110 291
    public function addInterface(InterfaceNode $node)
111
    {
112 291
        $interface = $this->buildClassLikeReflection($node);
113
114 291
        foreach ($node->extends as $extend) {
115 258
            $interface->addInterface((string) $extend);
0 ignored issues
show
Bug introduced by
The method addInterface does only exist in Benoth\StaticReflection\...ion\ReflectionInterface, but not in Benoth\StaticReflection\Reflection\ReflectionTrait.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
116 291
        }
117 291
    }
118
119 291
    public function addTrait(TraitNode $node)
120
    {
121 291
        $this->buildClassLikeReflection($node);
122 291
    }
123
124 258
    public function addTraitUse(TraitUseNode $node)
125
    {
126 258
        foreach ($node->traits as $trait) {
127 258
            $this->context->getReflection()->addTrait((string) $trait);
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Benoth\StaticReflection\Reflection\Reflection as the method addTrait() does only exist in the following sub-classes of Benoth\StaticReflection\Reflection\Reflection: Benoth\StaticReflection\Reflection\ReflectionClass, Benoth\StaticReflection\Reflection\ReflectionTrait. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
128 258
            foreach ($node->adaptations as $adaptation) {
129 258
                if ($adaptation instanceof TraitUseAliasNode) {
130 258
                    $this->addTraitUseAlias($adaptation, $trait);
131 258
                } elseif ($adaptation instanceof TraitUsePrecedenceNode) {
132 258
                    $this->addTraitUsePrecedence($adaptation);
133 258
                }
134 258
            }
135 258
        }
136 258
    }
137
138
    /**
139
     * @param NodeInterface\Name $trait
140
     */
141 258
    public function addTraitUseAlias(TraitUseAliasNode $adaptation, $trait)
142
    {
143 258
        if (is_null($adaptation->trait)) {
144
            $this->context->getReflection()->addTraitMethodAlias((string) $trait->toString(), (string) $adaptation->method, (string) $adaptation->newName);
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Benoth\StaticReflection\Reflection\Reflection as the method addTraitMethodAlias() does only exist in the following sub-classes of Benoth\StaticReflection\Reflection\Reflection: Benoth\StaticReflection\Reflection\ReflectionClass, Benoth\StaticReflection\Reflection\ReflectionTrait. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
145
        } else {
146 258
            $this->context->getReflection()->addTraitMethodAlias((string) $adaptation->trait->toString(), (string) $adaptation->method, (string) $adaptation->newName);
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Benoth\StaticReflection\Reflection\Reflection as the method addTraitMethodAlias() does only exist in the following sub-classes of Benoth\StaticReflection\Reflection\Reflection: Benoth\StaticReflection\Reflection\ReflectionClass, Benoth\StaticReflection\Reflection\ReflectionTrait. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
147
        }
148 258
    }
149
150 258
    public function addTraitUsePrecedence(TraitUsePrecedenceNode $node)
151
    {
152 258
        foreach ($node->insteadof as $insteadof) {
153 258
            $this->context->getReflection()->addTraitMethodPrecedences((string) $node->trait->toString(), (string) $insteadof->toString(), (string) $node->method);
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Benoth\StaticReflection\Reflection\Reflection as the method addTraitMethodPrecedences() does only exist in the following sub-classes of Benoth\StaticReflection\Reflection\Reflection: Benoth\StaticReflection\Reflection\ReflectionClass, Benoth\StaticReflection\Reflection\ReflectionTrait. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
154 258
        }
155 258
    }
156
157 786
    public function addProperty(PropertyNode $node)
158
    {
159 786
        foreach ($node->props as $prop) {
160 786
            $property = new ReflectionProperty($prop->name);
161 786
            $property->setStartLine((int) $node->getAttribute('startLine'));
162 786
            $property->setEndLine((int) $node->getAttribute('endLine'));
163 786
            $property->setDocComment((string) $node->getDocComment());
164 786
            $property->setStatic((bool) $node->isStatic());
165
166 786
            if (!is_null($prop->default)) {
167 786
                $value = $this->resolveValue($prop->default);
168 786
                $property->setDefault(strtolower(gettype($value)), $value);
169 786
            }
170
171 786
            if ($node->isPrivate()) {
172 786
                $property->setVisibility(ReflectionProperty::IS_PRIVATE);
173 786
            } elseif ($node->isProtected()) {
174 786
                $property->setVisibility(ReflectionProperty::IS_PROTECTED);
175 786
            } else {
176 786
                $property->setVisibility(ReflectionProperty::IS_PUBLIC);
177
            }
178
179 786
            $this->context->getReflection()->addProperty($property);
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Benoth\StaticReflection\Reflection\Reflection as the method addProperty() does only exist in the following sub-classes of Benoth\StaticReflection\Reflection\Reflection: Benoth\StaticReflection\Reflection\ReflectionClass, Benoth\StaticReflection\Reflection\ReflectionTrait. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
180 786
        }
181 786
    }
182
183 1062
    public function addMethod(ClassMethodNode $node)
184
    {
185 1062
        $method = new ReflectionMethod($node->name);
186 1062
        $method->setStartLine((int) $node->getAttribute('startLine'));
187 1062
        $method->setEndLine((int) $node->getAttribute('endLine'));
188 1062
        $method->setDocComment((string) $node->getDocComment());
189 1062
        $method->setReturnsByRef((bool) $node->returnsByRef());
190 1062
        $method->setFinal((bool) $node->isFinal());
191 1062
        $method->setStatic((bool) $node->isStatic());
192 1062
        $this->addFunctionLikeParameters($method, $node->params);
193
194
        // All functions in an interface are implicitly abstract. There is no need to use the abstract keyword when declaring the function.
195 1062
        if ($this->context->getReflection() instanceof ReflectionInterface) {
196 291
            $method->setAbstract(true);
197 291
        } else {
198 1029
            $method->setAbstract((bool) $node->isAbstract());
199
        }
200
201 1062
        if ($node->isPrivate()) {
202 867
            $method->setVisibility(ReflectionMethod::IS_PRIVATE);
203 1062
        } elseif ($node->isProtected()) {
204 819
            $method->setVisibility(ReflectionMethod::IS_PROTECTED);
205 819
        } else {
206 969
            $method->setVisibility(ReflectionMethod::IS_PUBLIC);
207
        }
208
209 1062
        $this->context->getReflection()->addMethod($method);
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Benoth\StaticReflection\Reflection\Reflection as the method addMethod() does only exist in the following sub-classes of Benoth\StaticReflection\Reflection\Reflection: Benoth\StaticReflection\Reflection\ReflectionClass, Benoth\StaticReflection\...ion\ReflectionClassLike, Benoth\StaticReflection\...ion\ReflectionInterface, Benoth\StaticReflection\Reflection\ReflectionTrait. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
210 1062
        $this->context->enterFunctionLike($method);
211 1062
    }
212
213 741
    public function addConstant(ClassConstNode $node)
214
    {
215 741
        foreach ($node->consts as $const) {
216 741
            $constant = new ReflectionConstant($const->name);
217 741
            $constant->setStartLine((int) $node->getAttribute('startLine'));
218 741
            $constant->setEndLine((int) $node->getAttribute('endLine'));
219 741
            $constant->setDocComment((string) $node->getDocComment());
220 741
            $constant->setValue($this->resolveValue($const->value));
221
222 741
            $this->context->getReflection()->addConstant($constant);
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Benoth\StaticReflection\Reflection\Reflection as the method addConstant() does only exist in the following sub-classes of Benoth\StaticReflection\Reflection\Reflection: Benoth\StaticReflection\Reflection\ReflectionClass, Benoth\StaticReflection\...ion\ReflectionInterface. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
223 741
        }
224 741
    }
225
226 39
    public function addFunction(FunctionNode $node)
227
    {
228 39
        $function = new ReflectionFunction((string) $node->namespacedName);
0 ignored issues
show
Bug introduced by
The property namespacedName does not seem to exist in PhpParser\Node\Stmt\Function_.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
229 39
        $function->setFilename((string) $this->context->getFilePath());
230 39
        $function->setStartLine((int) $node->getAttribute('startLine'));
231 39
        $function->setEndLine((int) $node->getAttribute('endLine'));
232 39
        $function->setAliases($this->context->getAliases());
233 39
        $function->setDocComment((string) $node->getDocComment());
234 39
        $this->addFunctionLikeParameters($function, $node->params);
235
236 39
        $this->context->enterReflection($function);
237 39
        $this->context->enterFunctionLike($function);
238
239 39
        return $function;
240
    }
241
242 1101
    protected function addFunctionLikeParameters(ReflectionFunctionAbstract $reflection, array $params)
243
    {
244 1101
        foreach ($params as $param) {
245 963
            $parameter = new ReflectionParameter($param->name);
246 963
            $parameter->setStartLine((int) $param->getAttribute('startLine'));
247 963
            $parameter->setEndLine((int) $param->getAttribute('endLine'));
248 963
            $parameter->setType((string) $param->type);
249 963
            $parameter->setByRef((bool) $param->byRef);
250 963
            $parameter->setVariadic((bool) $param->variadic);
251
252 963
            if (!is_null($param->default)) {
253 774
                $parameter->setRequired(false);
254
255 774
                if ($param->default->getType() === 'Expr_ConstFetch' && !in_array($param->default->name->toString(), ['null', 'false', 'true'])) {
256 174
                    $parameter->setDefaultValueConstantName($param->default->name->toString());
257 774
                } elseif ($param->default->getType() === 'Expr_ClassConstFetch') {
258 174
                    $parameter->setDefaultValueConstantName($param->default->class->toString().'::'.$param->default->name);
259 174
                }
260
261 774
                $value = $this->resolveValue($param->default);
262 774
                $parameter->setDefault(strtolower(gettype($value)), $value);
263 774
            }
264
265 963
            $reflection->addParameter($parameter);
266 1101
        }
267 1101
    }
268
269 450
    public function addStaticVariable(StaticNode $node)
270
    {
271 450
        foreach ($node->vars as $var) {
272 450
            $value = null;
273 450
            if (!is_null($var->default)) {
274 450
                $value = $this->resolveValue($var->default);
275 450
            }
276
277 450
            $this->context->getFunctionLike()->addStaticVariable($var->name, $value);
278 450
        }
279 450
    }
280
281 450
    public function addYieldNode(AbstractExprNode $node)
0 ignored issues
show
Unused Code introduced by
The parameter $node is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
282
    {
283 450
        $this->context->getFunctionLike()->setGenerator(true);
284 450
    }
285
286 819
    protected function resolveValue(NodeInterface $nodeValue)
287
    {
288 819
        if ($nodeValue instanceof MagicConst) {
289 174
            return $nodeValue->getName();
290
        }
291
292 819
        $subModules = $nodeValue->getSubNodeNames();
293 819
        if (!is_array($subModules) || empty($subModules)) {
294
            return;
295
        }
296
297 819
        $subModule = reset($subModules);
298 819
        $value     = $nodeValue->$subModule;
299 819
        if (is_object($value) && method_exists($value, 'toString')) {
300 774
            $value = $value->toString();
301 819
        } elseif (is_object($value) && property_exists($value, 'value')) {
302
            $value = $value->value;
303
        }
304
305 819
        if (is_array($value)) {
306 819
            $newValues = [];
307 819
            foreach ($value as $key => $node) {
308 450
                $newValues[$this->resolveValue($node->key)] = $this->resolveValue($node->value);
309 819
            }
310
311 819
            return $newValues;
312
        }
313
314 819
        if ($value === 'true') {
315 708
            return true;
316 819
        } elseif ($value === 'false') {
317 741
            return false;
318 819
        } elseif ($value === 'null') {
319 774
            return;
320
        }
321
322 786
        return $value;
323
    }
324
325 1062
    protected function buildClassLikeReflection(AbstractNode $node)
326
    {
327 1062
        if ($node instanceof InterfaceNode) {
328 291
            $class = new ReflectionInterface((string) $node->namespacedName);
0 ignored issues
show
Bug introduced by
The property namespacedName does not seem to exist in PhpParser\Node\Stmt\Interface_.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
329 1062
        } elseif ($node instanceof TraitNode) {
330 291
            $class = new ReflectionTrait((string) $node->namespacedName);
0 ignored issues
show
Bug introduced by
The property namespacedName does not seem to exist in PhpParser\Node\Stmt\Trait_.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
331 291
        } else {
332 996
            $class = new ReflectionClass((string) $node->namespacedName);
0 ignored issues
show
Bug introduced by
The property namespacedName does not seem to exist in PhpParser\Node\Stmt.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
333
        }
334
335 1062
        $class->setFilename((string) $this->context->getFilePath());
336 1062
        $class->setStartLine((int) $node->getAttribute('startLine'));
337 1062
        $class->setEndLine((int) $node->getAttribute('endLine'));
338 1062
        $class->setAliases($this->context->getAliases());
339 1062
        $class->setDocComment((string) $node->getDocComment());
340
341 1062
        $this->context->enterReflection($class);
342
343 1062
        return $class;
344
    }
345
}
346