ReflectionSourceStubber::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 5
ccs 4
cts 4
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 1
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;
21
use PhpParser\Node\Const_;
22
use PhpParser\Node\Name;
23
use PhpParser\Node\Name\FullyQualified;
24
use PhpParser\Node\NullableType;
25
use PhpParser\Node\Stmt\Class_ as ClassNode;
26
use PhpParser\Node\Stmt\ClassConst;
27
use PhpParser\Node\Stmt\TraitUse;
28
use PhpParser\Node\Stmt\TraitUseAdaptation;
29
use PhpParser\NodeAbstract;
30
use PhpParser\PrettyPrinter\Standard;
31
use ReflectionClass as CoreReflectionClass;
32
use ReflectionClassConstant;
33
use ReflectionFunction as CoreReflectionFunction;
34
use ReflectionFunctionAbstract as CoreReflectionFunctionAbstract;
35
use ReflectionMethod as CoreReflectionMethod;
36
use ReflectionParameter;
37
use ReflectionProperty as CoreReflectionProperty;
38
use ReflectionType as CoreReflectionType;
39
use Reflector as CoreReflector;
40
use function array_diff;
41
use function array_key_exists;
42
use function class_exists;
43
use function explode;
44
use function function_exists;
45
use function get_defined_constants;
46
use function in_array;
47
use function interface_exists;
48
use function trait_exists;
49
50
/**
51
 * It generates a stub source from internal reflection for given class or function name.
52
 *
53
 * @internal
54
 */
55
final class ReflectionSourceStubber implements SourceStubber
56
{
57
    private const BUILDER_OPTIONS = ['shortArraySyntax' => true];
58
59
    /** @var BuilderFactory */
60
    private $builderFactory;
61
62
    /** @var Standard */
63 203
    private $prettyPrinter;
64
65 203
    public function __construct()
66 203
    {
67 203
        $this->builderFactory = new BuilderFactory();
68
        $this->prettyPrinter  = new Standard(self::BUILDER_OPTIONS);
69 199
    }
70
71 199
    public function generateClassStub(string $className) : ?StubData
72
    {
73
        if (! (class_exists($className, false) || interface_exists($className, false) || trait_exists($className, false))) {
74
            return null;
75 199
        }
76 199
77
        $classReflection = new CoreReflectionClass($className);
78 199
        $classNode       = $this->createClass($classReflection);
79 177
80
        if ($classNode instanceof Class_) {
81
            $this->addClassModifiers($classNode, $classReflection);
82 199
        }
83 197
84
        if ($classNode instanceof Class_ || $classNode instanceof Interface_) {
85
            $this->addExtendsAndImplements($classNode, $classReflection);
86 199
        }
87 179
88 179
        if ($classNode instanceof Class_ || $classNode instanceof Trait_) {
89
            $this->addProperties($classNode, $classReflection);
90
            $this->addTraitUse($classNode, $classReflection);
91 199
        }
92 199
93 199
        $this->addDocComment($classNode, $classReflection);
94
        $this->addConstants($classNode, $classReflection);
95 199
        $this->addMethods($classNode, $classReflection);
96 195
97
        $extensionName = $classReflection->getExtensionName() ?: null;
98
99 4
        if (! $classReflection->inNamespace()) {
100
            return $this->createStubData($this->generateStub($classNode->getNode()), $extensionName);
101
        }
102 4
103
        return $this->createStubData($this->generateStubInNamespace($classNode->getNode(), $classReflection->getNamespaceName()), $extensionName);
104 4
    }
105
106
    public function generateFunctionStub(string $functionName) : ?StubData
107
    {
108 4
        if (! function_exists($functionName)) {
109 4
            return null;
110
        }
111 4
112 4
        $functionReflection = new CoreReflectionFunction($functionName);
113
        $functionNode       = $this->builderFactory->function($functionReflection->getShortName());
114 4
115 4
        $this->addDocComment($functionNode, $functionReflection);
116
        $this->addParameters($functionNode, $functionReflection);
117
118
        $extensionName = $functionReflection->getExtensionName() ?: null;
119
120
        if (! $functionReflection->inNamespace()) {
121
            return $this->createStubData($this->generateStub($functionNode->getNode()), $extensionName);
122
        }
123
124 199
        return $this->createStubData($this->generateStubInNamespace($functionNode->getNode(), $functionReflection->getNamespaceName()), $extensionName);
125
    }
126 199
127 2
    /**
128
     * {@inheritDoc}
129
     */
130 197
    public function generateConstantStub(string $constantName) : ?StubData
131 117
    {
132
        // Not supported because of resource as value
133
        if (in_array($constantName, ['STDIN', 'STDOUT', 'STDERR'], true)) {
134 177
            return null;
135
        }
136
137
        $constantData = $this->findConstantData($constantName);
138
139
        if ($constantData === null) {
140
            return null;
141 203
        }
142
143 203
        [$constantValue, $extensionName] = $constantData;
0 ignored issues
show
Bug introduced by Jaroslav Hanslík
The variable $constantValue does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by Jaroslav Hanslík
The variable $extensionName does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
144 202
145
        $constantNode = $this->builderFactory->funcCall('define', [$constantName, $constantValue]);
146
147 2
        return $this->createStubData($this->generateStub($constantNode), $extensionName);
148 2
    }
149
150 177
    /**
151
     * @return array<int, (mixed|string|null)>|null
0 ignored issues
show
Documentation introduced by Jaroslav Hanslík
The doc-type array<int, could not be parsed: Expected ">" at position 5, but found "end of type". (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
152 177
     */
153
    private function findConstantData(string $constantName) : ?array
154 14
    {
155
        $constants = get_defined_constants(true);
156
157 177
        foreach ($constants as $constantExtensionName => $extensionConstants) {
158 170
            if (array_key_exists($constantName, $extensionConstants)) {
159
                return [
160
                    $extensionConstants[$constantName],
161 9
                    $constantExtensionName !== 'user' ? $constantExtensionName : null,
162 9
                ];
163
            }
164
        }
165
166
        return null;
167 197
    }
168
169 197
    /**
170 197
     * @return Class_|Interface_|Trait_
171
     */
172 197
    private function createClass(CoreReflectionClass $classReflection) : Declaration
173 81
    {
174 81
        if ($classReflection->isTrait()) {
175
            return $this->builderFactory->trait($classReflection->getShortName());
176
        }
177 197
178 103
        if ($classReflection->isInterface()) {
179
            return $this->builderFactory->interface($classReflection->getShortName());
180
        }
181 197
182 103
        return $this->builderFactory->class($classReflection->getShortName());
183 48
    }
184
185 97
    /**
186
     * @param Class_|Interface_|Trait_|Method|Property|Function_                                     $node
187
     * @param CoreReflectionClass|CoreReflectionMethod|CoreReflectionProperty|CoreReflectionFunction $reflection
188 197
     */
189
    private function addDocComment(Builder $node, CoreReflector $reflection) : void
190 179
    {
191
        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, ReflectionClass, ReflectionClassConstant, ReflectionFunction, ReflectionFunction, ReflectionFunctionAbstract, ReflectionFunctionAbstract, ReflectionMethod, ReflectionMethod, ReflectionObject, ReflectionObject, ReflectionProperty, ReflectionProperty, Roave\BetterReflection\R...Adapter\ReflectionClass, Roave\BetterReflection\R...ReflectionClassConstant, Roave\BetterReflection\R...pter\ReflectionFunction, Roave\BetterReflection\R...dapter\ReflectionMethod, Roave\BetterReflection\R...dapter\ReflectionObject, Roave\BetterReflection\R...pter\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...
192 179
            return;
193
        }
194 179
195 1
        $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 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...
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, ReflectionClass, ReflectionClassConstant, ReflectionFunction, ReflectionFunction, ReflectionFunctionAbstract, ReflectionFunctionAbstract, ReflectionMethod, ReflectionMethod, ReflectionObject, ReflectionObject, ReflectionProperty, ReflectionProperty, Roave\BetterReflection\R...Adapter\ReflectionClass, Roave\BetterReflection\R...ReflectionClassConstant, Roave\BetterReflection\R...pter\ReflectionFunction, Roave\BetterReflection\R...dapter\ReflectionMethod, Roave\BetterReflection\R...dapter\ReflectionObject, Roave\BetterReflection\R...pter\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...
196 1
    }
197 1
198 1
    private function addClassModifiers(Class_ $classNode, CoreReflectionClass $classReflection) : void
199
    {
200
        if (! $classReflection->isInterface() && $classReflection->isAbstract()) {
201 1
            // Interface \Iterator is interface and abstract
202
            $classNode->makeAbstract();
203 1
        }
204
205
        if (! $classReflection->isFinal()) {
206 179
            return;
207 2
        }
208
209 179
        $classNode->makeFinal();
210
    }
211 179
212
    /**
213 179
     * @param Class_|Interface_ $classNode
214
     */
215 179
    private function addExtendsAndImplements(Declaration $classNode, CoreReflectionClass $classReflection) : void
216 53
    {
217 31
        $parentClass = $classReflection->getParentClass();
218
        $interfaces  = $classReflection->getInterfaceNames();
219
220 53
        if ($parentClass) {
221
            $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...
222 53
            $interfaces = array_diff($interfaces, $parentClass->getInterfaceNames());
223 53
        }
224
225 53
        foreach ($classReflection->getInterfaces() as $interface) {
226 53
            $interfaces = array_diff($interfaces, $interface->getInterfaceNames());
227
        }
228
229 53
        foreach ($interfaces as $interfaceName) {
230
            if ($classNode instanceof Interface_) {
231 179
                $classNode->extend(new FullyQualified($interfaceName));
232
            } else {
233 53
                $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...
234
            }
235 53
        }
236 30
    }
237
238
    private function addTraitUse(Declaration $classNode, CoreReflectionClass $classReflection) : void
239 53
    {
240 1
        $alreadyUsedTraitNames = [];
241 1
242
        foreach ($classReflection->getTraitAliases() as $methodNameAlias => $methodInfo) {
243
            [$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...
244
            $traitUseNode             = new TraitUse(
245 53
                [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...
246
                [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...
247
            );
248 53
249
            $classNode->addStmt($traitUseNode);
250 53
251 1
            $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...
252
        }
253
254 53
        foreach (array_diff($classReflection->getTraitNames(), $alreadyUsedTraitNames) as $traitName) {
255 25
            $classNode->addStmt(new TraitUse([new FullyQualified($traitName)]));
256
        }
257
    }
258 53
259 31
    private function addProperties(Declaration $classNode, CoreReflectionClass $classReflection) : void
260
    {
261
        $defaultProperties = $classReflection->getDefaultProperties();
262 53
263 53
        foreach ($classReflection->getProperties() as $propertyReflection) {
264
            if (! $this->isPropertyDeclaredInClass($propertyReflection, $classReflection)) {
265
                continue;
266 31
            }
267 31
268
            $propertyNode = $this->builderFactory->property($propertyReflection->getName());
269 199
270
            $this->addPropertyModifiers($propertyNode, $propertyReflection);
271 199
            $this->addDocComment($propertyNode, $propertyReflection);
272 51
273 17
            if (array_key_exists($propertyReflection->getName(), $defaultProperties)) {
274
                $propertyNode->setDefault($defaultProperties[$propertyReflection->getName()]);
275
            }
276 51
277 51
            $classNode->addStmt($propertyNode);
278 51
        }
279
    }
280
281 51
    private function isPropertyDeclaredInClass(CoreReflectionProperty $propertyReflection, CoreReflectionClass $classReflection) : bool
282 1
    {
283
        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...
284
            return false;
285 51
        }
286
287 199
        foreach ($classReflection->getTraits() as $trait) {
288
            if ($trait->hasProperty($propertyReflection->getName())) {
289 51
                return false;
290
            }
291 51
        }
292 1
293
        return true;
294
    }
295 51
296 1
    private function addPropertyModifiers(Property $propertyNode, CoreReflectionProperty $propertyReflection) : void
297
    {
298
        if ($propertyReflection->isStatic()) {
299 51
            $propertyNode->makeStatic();
300
        }
301
302 199
        if ($propertyReflection->isPublic()) {
303
            $propertyNode->makePublic();
304 199
        }
305 184
306 89
        if ($propertyReflection->isProtected()) {
307
            $propertyNode->makeProtected();
308
        }
309 183
310
        if (! $propertyReflection->isPrivate()) {
311 183
            return;
312 183
        }
313 183
314
        $propertyNode->makePrivate();
315 183
    }
316
317 183
    private function addConstants(Declaration $classNode, CoreReflectionClass $classReflection) : void
318 1
    {
319
        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: ReflectionObject, 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...
320
            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...
321 183
                continue;
322
            }
323 199
324
            $classConstantNode = new ClassConst(
325 184
                [new Const_($constantReflection->getName(), BuilderHelpers::normalizeValue($constantReflection->getValue()))],
326
                $this->constantVisibilityFlags($constantReflection)
327 184
            );
328 89
329
            if ($constantReflection->getDocComment() !== false) {
330
                $classConstantNode->setDocComment(new Doc($constantReflection->getDocComment()));
331 183
            }
332 1
333
            $classNode->addStmt($classConstantNode);
334
        }
335 183
    }
336 2
337 1
    private function constantVisibilityFlags(ReflectionClassConstant $constant) : int
338
    {
339
        if ($constant->isPrivate()) {
340
            return ClassNode::MODIFIER_PRIVATE;
341 183
        }
342
343
        if ($constant->isProtected()) {
344 183
            return ClassNode::MODIFIER_PROTECTED;
345
        }
346 183
347 58
        return ClassNode::MODIFIER_PUBLIC;
348
    }
349
350 183
    private function addMethods(Declaration $classNode, CoreReflectionClass $classReflection) : void
351 107
    {
352
        foreach ($classReflection->getMethods() as $methodReflection) {
353
            if (! $this->isMethodDeclaredInClass($methodReflection, $classReflection)) {
354 183
                continue;
355 41
            }
356
357
            $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...
358 183
359 182
            $this->addMethodFlags($methodNode, $methodReflection);
360
            $this->addDocComment($methodNode, $methodReflection);
361
            $this->addParameters($methodNode, $methodReflection);
362 183
363 4
            $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...
364
365
            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...
366 183
                $methodNode->setReturnType($this->formatType($returnType));
367 54
            }
368
369
            $classNode->addStmt($methodNode);
370 183
        }
371 183
    }
372
373
    private function isMethodDeclaredInClass(CoreReflectionMethod $methodReflection, CoreReflectionClass $classReflection) : bool
374 1
    {
375 1
        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...
376
            return false;
377 187
        }
378
379 187
        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...
380 166
            return false;
381
        }
382 166
383
        foreach ($classReflection->getTraits() as $trait) {
384 166
            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...
385 125
                return false;
386
            }
387
        }
388 166
389
        return true;
390 187
    }
391
392 166
    private function addMethodFlags(Method $methodNode, CoreReflectionMethod $methodReflection) : void
393
    {
394 166
        if ($methodReflection->isFinal()) {
395 7
            $methodNode->makeFinal();
396
        }
397
398 166
        if ($methodReflection->isAbstract()) {
399 18
            $methodNode->makeAbstract();
400
        }
401
402 166
        if ($methodReflection->isStatic()) {
403
            $methodNode->makeStatic();
404 166
        }
405 52
406
        if ($methodReflection->isPublic()) {
407
            $methodNode->makePublic();
408 166
        }
409
410
        if ($methodReflection->isProtected()) {
411
            $methodNode->makeProtected();
412
        }
413
414 125
        if ($methodReflection->isPrivate()) {
415
            $methodNode->makePrivate();
416
        }
417
418 125
        if (! $methodReflection->returnsReference()) {
419 124
            return;
420
        }
421
422 1
        $methodNode->makeReturnByRef();
423
    }
424
425
    private function addParameters(FunctionLike $functionNode, CoreReflectionFunctionAbstract $functionReflectionAbstract) : void
426
    {
427
        foreach ($functionReflectionAbstract->getParameters() as $parameterReflection) {
428 52
            $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...
429
430 52
            $this->addParameterModifiers($parameterReflection, $parameterNode);
431 52
432
            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...
433 52
                $parameterNode->setDefault($this->parameterDefaultValue($parameterReflection, $functionReflectionAbstract));
434
            }
435
436 4
            $functionNode->addParam($this->addParameterModifiers($parameterReflection, $parameterNode));
437
        }
438 4
    }
439 4
440
    private function addParameterModifiers(ReflectionParameter $parameterReflection, Param $parameterNode) : Param
441 4
    {
442
        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...
443
            $parameterNode->makeVariadic();
444 203
        }
445
446 203
        if ($parameterReflection->isPassedByReference()) {
447
            $parameterNode->makeByRef();
448
        }
449
450
        $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...
451
452 203
        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...
453
            $parameterNode->setType($this->formatType($parameterType));
454 203
        }
455
456
        return $parameterNode;
457
    }
458
459
    /**
460
     * @return mixed
461
     */
462
    private function parameterDefaultValue(
463
        ReflectionParameter $parameterReflection,
464
        CoreReflectionFunctionAbstract $functionReflectionAbstract
465
    ) {
466
        if ($functionReflectionAbstract->isInternal()) {
467
            return null;
468
        }
469
470
        return $parameterReflection->getDefaultValue();
471
    }
472
473
    /**
474
     * @return Name|FullyQualified|NullableType
475
     */
476
    private function formatType(CoreReflectionType $type) : NodeAbstract
477
    {
478
        $name     = (string) $type;
479
        $nameNode = $type->isBuiltin() || in_array($name, ['self', 'parent'], true) ? new Name($name) : new FullyQualified($name);
480
481
        return $type->allowsNull() ? new NullableType($nameNode) : $nameNode;
482
    }
483
484
    private function generateStubInNamespace(Node $node, string $namespaceName) : string
485
    {
486
        $namespaceBuilder = $this->builderFactory->namespace($namespaceName);
487
        $namespaceBuilder->addStmt($node);
488
489
        return $this->generateStub($namespaceBuilder->getNode());
490
    }
491
492
    private function generateStub(Node $node) : string
493
    {
494
        return "<?php\n\n" . $this->prettyPrinter->prettyPrint([$node]) . ($node instanceof Node\Expr\FuncCall ? ';' : '') . "\n";
495
    }
496
497
    private function createStubData(string $stub, ?string $extensionName) : StubData
498
    {
499
        return new StubData($stub, $extensionName);
500
    }
501
}
502