Completed
Push — master ( 1fb800...df78b5 )
by Marco
11:03 queued 11s
created

ReflectionSourceStubber::addClassModifiers()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 13
rs 9.8333
c 0
b 0
f 0
cc 4
nc 4
nop 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Roave\BetterReflection\SourceLocator\SourceStubber;
6
7
use PhpParser\Builder;
8
use PhpParser\Builder\Class_;
9
use PhpParser\Builder\Declaration;
10
use PhpParser\Builder\Function_;
11
use PhpParser\Builder\FunctionLike;
12
use PhpParser\Builder\Interface_;
13
use PhpParser\Builder\Method;
14
use PhpParser\Builder\Param;
15
use PhpParser\Builder\Property;
16
use PhpParser\Builder\Trait_;
17
use PhpParser\BuilderFactory;
18
use PhpParser\BuilderHelpers;
19
use PhpParser\Comment\Doc;
20
use PhpParser\Node\Const_;
21
use PhpParser\Node\Name;
22
use PhpParser\Node\Name\FullyQualified;
23
use PhpParser\Node\NullableType;
24
use PhpParser\Node\Stmt\Class_ as ClassNode;
25
use PhpParser\Node\Stmt\ClassConst;
26
use PhpParser\Node\Stmt\TraitUse;
27
use PhpParser\Node\Stmt\TraitUseAdaptation;
28
use PhpParser\NodeAbstract;
29
use PhpParser\PrettyPrinter\Standard;
30
use ReflectionClass as CoreReflectionClass;
31
use ReflectionClassConstant;
32
use ReflectionFunction as CoreReflectionFunction;
33
use ReflectionFunctionAbstract as CoreReflectionFunctionAbstract;
34
use ReflectionMethod as CoreReflectionMethod;
35
use ReflectionParameter;
36
use ReflectionProperty as CoreReflectionProperty;
37
use ReflectionType as CoreReflectionType;
38
use Reflector as CoreReflector;
39
use function array_diff;
40
use function array_key_exists;
41
use function explode;
42
use function in_array;
43
44
/**
45
 * It generates a stub source from a given reflection instance.
46
 *
47
 * @internal
48
 */
49
final class ReflectionSourceStubber implements SourceStubber
50
{
51
    private const BUILDER_OPTIONS = ['shortArraySyntax' => true];
52
53
    /** @var BuilderFactory */
54
    private $builderFactory;
55
56
    /** @var Standard */
57
    private $prettyPrinter;
58
59
    public function __construct()
60
    {
61
        $this->builderFactory = new BuilderFactory();
62
        $this->prettyPrinter  = new Standard(self::BUILDER_OPTIONS);
63
    }
64
65
    public function generateClassStub(CoreReflectionClass $classReflection) : ?string
66
    {
67
        $classNode = $this->createClass($classReflection);
68
69
        if ($classNode instanceof Class_) {
70
            $this->addClassModifiers($classNode, $classReflection);
71
        }
72
73
        if ($classNode instanceof Class_ || $classNode instanceof Interface_) {
74
            $this->addExtendsAndImplements($classNode, $classReflection);
75
        }
76
77
        if ($classNode instanceof Class_ || $classNode instanceof Trait_) {
78
            $this->addProperties($classNode, $classReflection);
79
            $this->addTraitUse($classNode, $classReflection);
80
        }
81
82
        $this->addDocComment($classNode, $classReflection);
83
        $this->addConstants($classNode, $classReflection);
84
        $this->addMethods($classNode, $classReflection);
85
86
        if (! $classReflection->inNamespace()) {
87
            return $this->generateStub($classNode);
88
        }
89
90
        $namespaceNode = $this->builderFactory->namespace($classReflection->getNamespaceName());
91
        $namespaceNode->addStmt($classNode);
92
93
        return $this->generateStub($namespaceNode);
94
    }
95
96
    public function generateFunctionStub(CoreReflectionFunction $functionReflection) : ?string
97
    {
98
        $functionNode = $this->builderFactory->function($functionReflection->getName());
0 ignored issues
show
Bug introduced by Jaroslav Hanslík
Consider using $functionReflection->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
99
100
        $this->addDocComment($functionNode, $functionReflection);
101
        $this->addParameters($functionNode, $functionReflection);
102
103
        return $this->generateStub($functionNode);
104
    }
105
106
    /**
107
     * @return Class_|Interface_|Trait_
108
     */
109
    private function createClass(CoreReflectionClass $classReflection) : Declaration
110
    {
111
        if ($classReflection->isTrait()) {
112
            return $this->builderFactory->trait($classReflection->getShortName());
113
        }
114
115
        if ($classReflection->isInterface()) {
116
            return $this->builderFactory->interface($classReflection->getShortName());
117
        }
118
119
        return $this->builderFactory->class($classReflection->getShortName());
120
    }
121
122
    /**
123
     * @param Class_|Interface_|Trait_|Method|Property|Function_                                     $node
124
     * @param CoreReflectionClass|CoreReflectionMethod|CoreReflectionProperty|CoreReflectionFunction $reflection
125
     */
126
    private function addDocComment(Builder $node, CoreReflector $reflection) : void
127
    {
128
        if ($reflection->getDocComment() === false) {
0 ignored issues
show
Bug introduced by Jaroslav Hanslík
It seems like you code against a concrete implementation and not the interface Reflector as the method getDocComment() does only exist in the following implementations of said interface: ReflectionClass, ReflectionFunction, ReflectionFunctionAbstract, ReflectionMethod, ReflectionObject, ReflectionProperty, Roave\BetterReflection\R...Adapter\ReflectionClass, Roave\BetterReflection\R...pter\ReflectionFunction, Roave\BetterReflection\R...dapter\ReflectionMethod, Roave\BetterReflection\R...dapter\ReflectionObject, Roave\BetterReflection\R...pter\ReflectionProperty, Roave\BetterReflection\Reflection\ReflectionClass, Roave\BetterReflection\R...ReflectionClassConstant, Roave\BetterReflection\R...tion\ReflectionFunction, Roave\BetterReflection\R...lectionFunctionAbstract, Roave\BetterReflection\Reflection\ReflectionMethod, Roave\BetterReflection\Reflection\ReflectionObject, Roave\BetterReflection\R...tion\ReflectionProperty.

Let’s take a look at an example:

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

class MyUser implements 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 implementation 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 interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
129
            return;
130
        }
131
132
        $node->setDocComment(new Doc($reflection->getDocComment()));
0 ignored issues
show
Bug introduced by Jaroslav Hanslík
It seems like you code against a concrete implementation and not the interface Reflector as the method getDocComment() does only exist in the following implementations of said interface: ReflectionClass, ReflectionFunction, ReflectionFunctionAbstract, ReflectionMethod, ReflectionObject, ReflectionProperty, Roave\BetterReflection\R...Adapter\ReflectionClass, Roave\BetterReflection\R...pter\ReflectionFunction, Roave\BetterReflection\R...dapter\ReflectionMethod, Roave\BetterReflection\R...dapter\ReflectionObject, Roave\BetterReflection\R...pter\ReflectionProperty, Roave\BetterReflection\Reflection\ReflectionClass, Roave\BetterReflection\R...ReflectionClassConstant, Roave\BetterReflection\R...tion\ReflectionFunction, Roave\BetterReflection\R...lectionFunctionAbstract, Roave\BetterReflection\Reflection\ReflectionMethod, Roave\BetterReflection\Reflection\ReflectionObject, Roave\BetterReflection\R...tion\ReflectionProperty.

Let’s take a look at an example:

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

class MyUser implements 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 implementation 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 interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
Bug introduced by Jaroslav Hanslík
It seems like you code against a concrete implementation and not the interface PhpParser\Builder as the method setDocComment() does only exist in the following implementations of said interface: PhpParser\Builder\Class_, PhpParser\Builder\Declaration, PhpParser\Builder\FunctionLike, PhpParser\Builder\Function_, PhpParser\Builder\Interface_, PhpParser\Builder\Method, PhpParser\Builder\Namespace_, PhpParser\Builder\Property, PhpParser\Builder\Trait_.

Let’s take a look at an example:

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

class MyUser implements 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 implementation 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 interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
133
    }
134
135
    private function addClassModifiers(Class_ $classNode, CoreReflectionClass $classReflection) : void
136
    {
137
        if (! $classReflection->isInterface() && $classReflection->isAbstract()) {
138
            // Interface \Iterator is interface and abstract
139
            $classNode->makeAbstract();
140
        }
141
142
        if (! $classReflection->isFinal()) {
143
            return;
144
        }
145
146
        $classNode->makeFinal();
147
    }
148
149
    /**
150
     * @param Class_|Interface_ $classNode
151
     */
152
    private function addExtendsAndImplements(Declaration $classNode, CoreReflectionClass $classReflection) : void
153
    {
154
        $parentClass = $classReflection->getParentClass();
155
        $interfaces  = $classReflection->getInterfaceNames();
156
157
        if ($parentClass) {
158
            $classNode->extend(new FullyQualified($parentClass->getName()));
0 ignored issues
show
Bug introduced by Jaroslav Hanslík
It seems like you code against a specific sub-type and not the parent class PhpParser\Builder\Declaration as the method extend() does only exist in the following sub-classes of PhpParser\Builder\Declaration: PhpParser\Builder\Class_, PhpParser\Builder\Interface_. 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...
159
            $interfaces = array_diff($interfaces, $parentClass->getInterfaceNames());
160
        }
161
162
        foreach ($classReflection->getInterfaces() as $interface) {
163
            $interfaces = array_diff($interfaces, $interface->getInterfaceNames());
164
        }
165
166
        foreach ($interfaces as $interfaceName) {
167
            if ($classNode instanceof Interface_) {
168
                $classNode->extend(new FullyQualified($interfaceName));
169
            } else {
170
                $classNode->implement(new FullyQualified($interfaceName));
0 ignored issues
show
Bug introduced by Jaroslav Hanslík
It seems like you code against a specific sub-type and not the parent class PhpParser\Builder\Declaration as the method implement() does only exist in the following sub-classes of PhpParser\Builder\Declaration: PhpParser\Builder\Class_. 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...
171
            }
172
        }
173
    }
174
175
    private function addTraitUse(Declaration $classNode, CoreReflectionClass $classReflection) : void
176
    {
177
        $alreadyUsedTraitNames = [];
178
179
        foreach ($classReflection->getTraitAliases() as $methodNameAlias => $methodInfo) {
180
            [$traitName, $methodName] = explode('::', $methodInfo);
0 ignored issues
show
Bug introduced by Jaroslav Hanslík
The variable $traitName seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
Bug introduced by Jaroslav Hanslík
The variable $methodName does not exist. Did you mean $methodNameAlias?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
181
            $traitUseNode             = new TraitUse(
182
                [new FullyQualified($traitName)],
0 ignored issues
show
Bug introduced by Jaroslav Hanslík
The variable $traitName seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
183
                [new TraitUseAdaptation\Alias(new FullyQualified($traitName), $methodName, null, $methodNameAlias)]
0 ignored issues
show
Bug introduced by Jaroslav Hanslík
The variable $traitName seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
Bug introduced by Jaroslav Hanslík
The variable $methodName does not exist. Did you mean $methodNameAlias?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
184
            );
185
186
            $classNode->addStmt($traitUseNode);
187
188
            $alreadyUsedTraitNames[] = $traitName;
0 ignored issues
show
Bug introduced by Jaroslav Hanslík
The variable $traitName seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
189
        }
190
191
        foreach (array_diff($classReflection->getTraitNames(), $alreadyUsedTraitNames) as $traitName) {
192
            $classNode->addStmt(new TraitUse([new FullyQualified($traitName)]));
193
        }
194
    }
195
196
    private function addProperties(Declaration $classNode, CoreReflectionClass $classReflection) : void
197
    {
198
        $defaultProperties = $classReflection->getDefaultProperties();
199
200
        foreach ($classReflection->getProperties() as $propertyReflection) {
201
            if (! $this->isPropertyDeclaredInClass($propertyReflection, $classReflection)) {
202
                continue;
203
            }
204
205
            $propertyNode = $this->builderFactory->property($propertyReflection->getName());
206
207
            $this->addPropertyModifiers($propertyNode, $propertyReflection);
208
            $this->addDocComment($propertyNode, $propertyReflection);
209
210
            if (array_key_exists($propertyReflection->getName(), $defaultProperties)) {
211
                $propertyNode->setDefault($defaultProperties[$propertyReflection->getName()]);
212
            }
213
214
            $classNode->addStmt($propertyNode);
215
        }
216
    }
217
218
    private function isPropertyDeclaredInClass(CoreReflectionProperty $propertyReflection, CoreReflectionClass $classReflection) : bool
219
    {
220
        if ($propertyReflection->getDeclaringClass()->getName() !== $classReflection->getName()) {
0 ignored issues
show
introduced by Jaroslav Hanslík
Consider using $propertyReflection->class. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
Bug introduced by Jaroslav Hanslík
Consider using $classReflection->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
221
            return false;
222
        }
223
224
        foreach ($classReflection->getTraits() as $trait) {
225
            if ($trait->hasProperty($propertyReflection->getName())) {
226
                return false;
227
            }
228
        }
229
230
        return true;
231
    }
232
233
    private function addPropertyModifiers(Property $propertyNode, CoreReflectionProperty $propertyReflection) : void
234
    {
235
        if ($propertyReflection->isStatic()) {
236
            $propertyNode->makeStatic();
237
        }
238
239
        if ($propertyReflection->isPublic()) {
240
            $propertyNode->makePublic();
241
        }
242
243
        if ($propertyReflection->isProtected()) {
244
            $propertyNode->makeProtected();
245
        }
246
247
        if (! $propertyReflection->isPrivate()) {
248
            return;
249
        }
250
251
        $propertyNode->makePrivate();
252
    }
253
254
    private function addConstants(Declaration $classNode, CoreReflectionClass $classReflection) : void
255
    {
256
        foreach ($classReflection->getReflectionConstants() as $constantReflection) {
0 ignored issues
show
Bug introduced by Jaroslav Hanslík
It seems like you code against a specific sub-type and not the parent class ReflectionClass as the method getReflectionConstants() does only exist in the following sub-classes of ReflectionClass: Roave\BetterReflection\R...Adapter\ReflectionClass. 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...
257
            if ($constantReflection->getDeclaringClass()->getName() !== $classReflection->getName()) {
0 ignored issues
show
Bug introduced by Jaroslav Hanslík
Consider using $classReflection->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
258
                continue;
259
            }
260
261
            $classConstantNode = new ClassConst(
262
                [new Const_($constantReflection->getName(), BuilderHelpers::normalizeValue($constantReflection->getValue()))],
263
                $this->constantVisibilityFlags($constantReflection)
264
            );
265
266
            if ($constantReflection->getDocComment() !== false) {
267
                $classConstantNode->setDocComment(new Doc($constantReflection->getDocComment()));
268
            }
269
270
            $classNode->addStmt($classConstantNode);
271
        }
272
    }
273
274
    private function constantVisibilityFlags(ReflectionClassConstant $constant) : int
275
    {
276
        if ($constant->isPrivate()) {
277
            return ClassNode::MODIFIER_PRIVATE;
278
        }
279
280
        if ($constant->isProtected()) {
281
            return ClassNode::MODIFIER_PROTECTED;
282
        }
283
284
        return ClassNode::MODIFIER_PUBLIC;
285
    }
286
287
    private function addMethods(Declaration $classNode, CoreReflectionClass $classReflection) : void
288
    {
289
        foreach ($classReflection->getMethods() as $methodReflection) {
290
            if (! $this->isMethodDeclaredInClass($methodReflection, $classReflection)) {
291
                continue;
292
            }
293
294
            $methodNode = $this->builderFactory->method($methodReflection->getName());
0 ignored issues
show
Bug introduced by Jaroslav Hanslík
Consider using $methodReflection->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
295
296
            $this->addMethodFlags($methodNode, $methodReflection);
297
            $this->addDocComment($methodNode, $methodReflection);
298
            $this->addParameters($methodNode, $methodReflection);
299
300
            $returnType = $methodReflection->getReturnType();
0 ignored issues
show
Bug introduced by Jaroslav Hanslík
It seems like you code against a specific sub-type and not the parent class ReflectionMethod as the method getReturnType() does only exist in the following sub-classes of ReflectionMethod: Roave\BetterReflection\R...dapter\ReflectionMethod. 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...
301
302
            if ($methodReflection->getReturnType() !== null) {
0 ignored issues
show
Bug introduced by Jaroslav Hanslík
It seems like you code against a specific sub-type and not the parent class ReflectionMethod as the method getReturnType() does only exist in the following sub-classes of ReflectionMethod: Roave\BetterReflection\R...dapter\ReflectionMethod. 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...
303
                $methodNode->setReturnType($this->formatType($returnType));
304
            }
305
306
            $classNode->addStmt($methodNode);
307
        }
308
    }
309
310
    private function isMethodDeclaredInClass(CoreReflectionMethod $methodReflection, CoreReflectionClass $classReflection) : bool
311
    {
312
        if ($methodReflection->getDeclaringClass()->getName() !== $classReflection->getName()) {
0 ignored issues
show
introduced by Jaroslav Hanslík
Consider using $methodReflection->class. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
Bug introduced by Jaroslav Hanslík
Consider using $classReflection->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
313
            return false;
314
        }
315
316
        if (array_key_exists($methodReflection->getName(), $classReflection->getTraitAliases())) {
0 ignored issues
show
Bug introduced by Jaroslav Hanslík
Consider using $methodReflection->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
317
            return false;
318
        }
319
320
        foreach ($classReflection->getTraits() as $trait) {
321
            if ($trait->hasMethod($methodReflection->getName())) {
0 ignored issues
show
Bug introduced by Jaroslav Hanslík
Consider using $methodReflection->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
322
                return false;
323
            }
324
        }
325
326
        return true;
327
    }
328
329
    private function addMethodFlags(Method $methodNode, CoreReflectionMethod $methodReflection) : void
330
    {
331
        if ($methodReflection->isFinal()) {
332
            $methodNode->makeFinal();
333
        }
334
335
        if ($methodReflection->isAbstract()) {
336
            $methodNode->makeAbstract();
337
        }
338
339
        if ($methodReflection->isStatic()) {
340
            $methodNode->makeStatic();
341
        }
342
343
        if ($methodReflection->isPublic()) {
344
            $methodNode->makePublic();
345
        }
346
347
        if ($methodReflection->isProtected()) {
348
            $methodNode->makeProtected();
349
        }
350
351
        if ($methodReflection->isPrivate()) {
352
            $methodNode->makePrivate();
353
        }
354
355
        if (! $methodReflection->returnsReference()) {
356
            return;
357
        }
358
359
        $methodNode->makeReturnByRef();
360
    }
361
362
    private function addParameters(FunctionLike $functionNode, CoreReflectionFunctionAbstract $functionReflectionAbstract) : void
363
    {
364
        foreach ($functionReflectionAbstract->getParameters() as $parameterReflection) {
365
            $parameterNode = $this->builderFactory->param($parameterReflection->getName());
0 ignored issues
show
Bug introduced by Jaroslav Hanslík
Consider using $parameterReflection->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
366
367
            $this->addParameterModifiers($parameterReflection, $parameterNode);
368
369
            if ($parameterReflection->isOptional() && ! $parameterReflection->isVariadic()) {
0 ignored issues
show
Bug introduced by Marco Pivetta
It seems like you code against a specific sub-type and not the parent class ReflectionParameter as the method isVariadic() does only exist in the following sub-classes of ReflectionParameter: Roave\BetterReflection\R...ter\ReflectionParameter. 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...
370
                $parameterNode->setDefault($this->parameterDefaultValue($parameterReflection, $functionReflectionAbstract));
371
            }
372
373
            $functionNode->addParam($this->addParameterModifiers($parameterReflection, $parameterNode));
374
        }
375
    }
376
377
    private function addParameterModifiers(ReflectionParameter $parameterReflection, Param $parameterNode) : Param
378
    {
379
        if ($parameterReflection->isVariadic()) {
0 ignored issues
show
Bug introduced by Jaroslav Hanslík
It seems like you code against a specific sub-type and not the parent class ReflectionParameter as the method isVariadic() does only exist in the following sub-classes of ReflectionParameter: Roave\BetterReflection\R...ter\ReflectionParameter. 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...
380
            $parameterNode->makeVariadic();
381
        }
382
383
        if ($parameterReflection->isPassedByReference()) {
384
            $parameterNode->makeByRef();
385
        }
386
387
        $parameterType = $parameterReflection->getType();
0 ignored issues
show
Bug introduced by Jaroslav Hanslík
It seems like you code against a specific sub-type and not the parent class ReflectionParameter as the method getType() does only exist in the following sub-classes of ReflectionParameter: Roave\BetterReflection\R...ter\ReflectionParameter. 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...
388
389
        if ($parameterReflection->getType() !== null) {
0 ignored issues
show
Bug introduced by Jaroslav Hanslík
It seems like you code against a specific sub-type and not the parent class ReflectionParameter as the method getType() does only exist in the following sub-classes of ReflectionParameter: Roave\BetterReflection\R...ter\ReflectionParameter. 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...
390
            $parameterNode->setType($this->formatType($parameterType));
391
        }
392
393
        return $parameterNode;
394
    }
395
396
    /**
397
     * @return mixed
398
     */
399
    private function parameterDefaultValue(
400
        ReflectionParameter $parameterReflection,
401
        CoreReflectionFunctionAbstract $functionReflectionAbstract
402
    ) {
403
        if ($functionReflectionAbstract->isInternal()) {
404
            return null;
405
        }
406
407
        return $parameterReflection->getDefaultValue();
408
    }
409
410
    /**
411
     * @return Name|FullyQualified|NullableType
412
     */
413
    private function formatType(CoreReflectionType $type) : NodeAbstract
414
    {
415
        $name     = (string) $type;
416
        $nameNode = $type->isBuiltin() || in_array($name, ['self', 'parent'], true) ? new Name($name) : new FullyQualified($name);
417
        return $type->allowsNull() ? new NullableType($nameNode) : $nameNode;
418
    }
419
420
    private function generateStub(Builder $builder) : string
421
    {
422
        return "<?php\n\n" . $this->prettyPrinter->prettyPrint([$builder->getNode()]) . "\n";
423
    }
424
}
425