ReflectionClassLike   B
last analyzed

Complexity

Total Complexity 52

Size/Duplication

Total Lines 269
Duplicated Lines 2.6 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 97.94%

Importance

Changes 4
Bugs 2 Features 0
Metric Value
wmc 52
c 4
b 2
f 0
lcom 1
cbo 5
dl 7
loc 269
rs 7.9487
ccs 95
cts 97
cp 0.9794

16 Methods

Rating   Name   Duplication   Size   Complexity  
A isSubclassOf() 0 4 1
C getMethods() 7 36 16
A getSelfMethods() 0 9 2
A getMethod() 0 10 3
A getConstructor() 0 8 2
A hasMethod() 0 10 3
A implementsInterface() 0 11 3
A addMethod() 0 8 1
A getExtension() 0 4 1
A getExtensionName() 0 4 1
A isInternal() 0 4 1
A isUserDefined() 0 4 1
A isClass() 0 4 1
A isInterface() 0 4 1
A isTrait() 0 4 1
C filterMethods() 0 24 14

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

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

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

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

1
<?php
2
3
namespace Benoth\StaticReflection\Reflection;
4
5
abstract class ReflectionClassLike extends Reflection
6
{
7
    use Parts\AliasTrait;
8
    use Parts\DocCommentTrait;
9
    use Parts\IndexableTrait;
10
11
    protected $methods = [];
12
13
    /**
14
     * Checks if the class is a subclass of the specified class or implements a specified interface.
15
     *
16
     * @param string $className
17
     *
18
     * @return bool
19
     */
20
    public function isSubclassOf($className)
21
    {
22
        return false;
23
    }
24
25
    /**
26
     * Gets an array of methods, including inherited ones.
27
     *
28
     * @param int $filter Any combination of ReflectionMethod::IS_STATIC, ReflectionMethod::IS_PUBLIC, ReflectionMethod::IS_PROTECTED, ReflectionMethod::IS_PRIVATE, ReflectionMethod::IS_ABSTRACT, ReflectionMethod::IS_FINAL.
29
     *
30
     * @return ReflectionMethod[]
31
     */
32 390
    public function getMethods($filter = null)
33
    {
34
        // The order needs to be 1) current class, 2) traits, 3) parent class, 4) interfaces
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
35 390
        $methods = $this->getSelfMethods();
36
37 390
        if ($this instanceof ReflectionClass || $this instanceof ReflectionTrait) {
38 387
            foreach ($this->getTraitsMethods() as $methodName => $method) {
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Benoth\StaticReflection\...ion\ReflectionClassLike as the method getTraitsMethods() does only exist in the following sub-classes of Benoth\StaticReflection\...ion\ReflectionClassLike: Benoth\StaticReflection\Reflection\ReflectionClass, Benoth\StaticReflection\Reflection\ReflectionTrait. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

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

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

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

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

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

Available Fixes

  1. Change the type-hint for the parameter:

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

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

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
39 117
                if (!array_key_exists($methodName, $methods)) {
40 117
                    $methods[$methodName] = $method;
41 117
                }
42 387
            }
43 387
        }
44
45 390
        if ($this instanceof ReflectionClass && ($this->getParentClass() instanceof ReflectionClass || $this->getParentClass() instanceof \ReflectionClass)) {
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Benoth\StaticReflection\...ion\ReflectionClassLike as the method getParentClass() does only exist in the following sub-classes of Benoth\StaticReflection\...ion\ReflectionClassLike: Benoth\StaticReflection\Reflection\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...
46 132
            foreach ($this->getParentClass()->getMethods() as $methodName => $method) {
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Benoth\StaticReflection\...ion\ReflectionClassLike as the method getParentClass() does only exist in the following sub-classes of Benoth\StaticReflection\...ion\ReflectionClassLike: Benoth\StaticReflection\Reflection\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...
47 132
                if (is_int($methodName)) {
48 15
                    $methodName = $method->getName();
49 15
                }
50 132
                if (!array_key_exists($methodName, $methods)) {
51 132
                    $methods[$methodName] = $method;
52 132
                }
53 132
            }
54 132
        }
55
56 390
        if ($this instanceof ReflectionClass || $this instanceof ReflectionInterface) {
57 387 View Code Duplication
            foreach ($this->getInterfaces() as $interfaceName => $interface) {
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Benoth\StaticReflection\...ion\ReflectionClassLike as the method getInterfaces() does only exist in the following sub-classes of Benoth\StaticReflection\...ion\ReflectionClassLike: Benoth\StaticReflection\Reflection\ReflectionClass, Benoth\StaticReflection\...ion\ReflectionInterface. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

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

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

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

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

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

Available Fixes

  1. Change the type-hint for the parameter:

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

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

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
58 117
                foreach ($interface->getMethods() as $methodName => $method) {
59 117
                    if (!array_key_exists($methodName, $methods)) {
60 117
                        $methods[$methodName] = $method;
61 117
                    }
62 117
                }
63 387
            }
64 387
        }
65
66 390
        return $this->filterMethods($methods, $filter);
67
    }
68
69
    /**
70
     * Gets an array of methods, without inherited ones.
71
     *
72
     * @param int $filter Any combination of ReflectionMethod::IS_STATIC, ReflectionMethod::IS_PUBLIC, ReflectionMethod::IS_PROTECTED, ReflectionMethod::IS_PRIVATE, ReflectionMethod::IS_ABSTRACT, ReflectionMethod::IS_FINAL.
73
     *
74
     * @return \Benoth\StaticReflection\Reflection\ReflectionMethod[]
75
     */
76 408
    public function getSelfMethods($filter = null)
77
    {
78 408
        $methods = [];
79 408
        foreach ($this->methods as $method) {
80 408
            $methods[$method->getName()] = $method;
81 408
        }
82
83 408
        return $this->filterMethods($methods, $filter);
84
    }
85
86
    /**
87
     * Gets a ReflectionMethod for a class method.
88
     *
89
     * @param string $methodSearchedName The method name to reflect
90
     *
91
     * @throws \ReflectionException If the method does not exist
92
     *
93
     * @return \Benoth\StaticReflection\Reflection\ReflectionMethod
94
     */
95 198
    public function getMethod($methodSearchedName)
96
    {
97 198
        foreach ($this->getMethods() as $methodName => $method) {
98 198
            if ($methodName === $methodSearchedName) {
99 180
                return $method;
100
            }
101 168
        }
102
103 18
        throw new \ReflectionException('Method '.$methodSearchedName.' does not exist');
104
    }
105
106
    /**
107
     * Gets the constructor of the reflected class.
108
     *
109
     * @return \Benoth\StaticReflection\Reflection\ReflectionMethod
110
     */
111 15
    public function getConstructor()
112
    {
113
        try {
114 15
            return $this->getMethod('__construct');
115 3
        } catch (\ReflectionException $e) {
116 3
            return;
117
        }
118
    }
119
120
    /**
121
     * Checks if a method is defined.
122
     *
123
     * @param string $methodSearchedName Name of the method being checked for
124
     *
125
     * @return bool
126
     */
127 48
    public function hasMethod($methodSearchedName)
128
    {
129 48
        foreach ($this->getMethods() as $methodName => $method) {
130 48
            if ($methodName === $methodSearchedName) {
131 30
                return true;
132
            }
133 30
        }
134
135 18
        return false;
136
    }
137
138
    /**
139
     * Checks if current entity implements an interface.
140
     *
141
     * @param string $interfaceSearchedName The interface name
142
     *
143
     * @return bool
144
     */
145 15
    public function implementsInterface($interfaceSearchedName)
146
    {
147 15
        $interfaceSearchedName = trim($interfaceSearchedName, '\\');
148 15
        foreach ($this->getInterfaceNames() as $interfaceName) {
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Benoth\StaticReflection\...ion\ReflectionClassLike as the method getInterfaceNames() does only exist in the following sub-classes of Benoth\StaticReflection\...ion\ReflectionClassLike: Benoth\StaticReflection\Reflection\ReflectionClass, Benoth\StaticReflection\...ion\ReflectionInterface. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

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

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

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

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

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

Available Fixes

  1. Change the type-hint for the parameter:

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

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

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
149 9
            if (trim($interfaceName, '\\') === $interfaceSearchedName) {
150 9
                return true;
151
            }
152 15
        }
153
154 15
        return false;
155
    }
156
157
    /**
158
     * Add a method to the reflected class.
159
     *
160
     * @param ReflectionMethod $method
161
     */
162 1062
    public function addMethod(ReflectionMethod $method)
163
    {
164 1062
        $this->methods[$method->getShortName()] = $method;
165 1062
        $method->setDeclaringClassLike($this);
166 1062
        $method->setFilename($this->getFileName());
167
168 1062
        return $this;
169
    }
170
171
    /**
172
     * Gets a ReflectionExtension object for the extension which defined the class.
173
     *
174
     * @return null|\ReflectionExtension
175
     */
176 15
    public function getExtension()
177
    {
178 15
        return; // Always null for user-defined classes
179
    }
180
181
    /**
182
     * Gets the name of the extension which defined the class.
183
     *
184
     * @return bool
185
     */
186 15
    public function getExtensionName()
187
    {
188 15
        return false; // Always false for user-defined classes
189
    }
190
191
    /**
192
     * Checks if class is defined internally by an extension, or the core.
193
     *
194
     * @return bool
195
     */
196 15
    public function isInternal()
197
    {
198 15
        return false; // Always false for user-defined classes
199
    }
200
201
    /**
202
     * Checks if class is user defined.
203
     *
204
     * @return bool
205
     */
206 15
    public function isUserDefined()
207
    {
208 15
        return true; // Always true for user-defined classes
209
    }
210
211
    /**
212
     * Checks if the entity is a class.
213
     *
214
     * @return bool
215
     */
216 15
    public function isClass()
217
    {
218 15
        return $this instanceof ReflectionClass;
219
    }
220
221
    /**
222
     * Checks if the entity is an interface.
223
     *
224
     * @return bool
225
     */
226 15
    public function isInterface()
227
    {
228 15
        return $this instanceof ReflectionInterface;
229
    }
230
231
    /**
232
     * Checks if the entity is a trait.
233
     *
234
     * @return bool
235
     */
236 15
    public function isTrait()
237
    {
238 15
        return $this instanceof ReflectionTrait;
239
    }
240
241
    /**
242
     * Filter an array of methods.
243
     *
244
     * @param \Benoth\StaticReflection\Reflection\ReflectionMethod[] $methods
245
     * @param int                                                    $filter  Any combination of ReflectionMethod::IS_STATIC, ReflectionMethod::IS_PUBLIC, ReflectionMethod::IS_PROTECTED, ReflectionMethod::IS_PRIVATE, ReflectionMethod::IS_ABSTRACT, ReflectionMethod::IS_FINAL.
246
     *
247
     * @return \Benoth\StaticReflection\Reflection\ReflectionMethod[]
248
     */
249 408
    protected function filterMethods(array $methods, $filter)
250
    {
251 408
        if (!is_int($filter)) {
252 408
            return $methods;
253
        }
254
255 129
        return array_filter($methods, function (ReflectionMethod $method) use ($filter) {
256 129
            if (self::IS_PRIVATE === (self::IS_PRIVATE & $filter) && $method->isPrivate()) {
257 24
                return true;
258 123
            } elseif (self::IS_PROTECTED === (self::IS_PROTECTED & $filter) && $method->isProtected()) {
259 21
                return true;
260 123
            } elseif (self::IS_PUBLIC === (self::IS_PUBLIC & $filter) && $method->isPublic()) {
261 21
                return true;
262 120
            } elseif (self::IS_FINAL === (self::IS_FINAL & $filter) && $method->isFinal()) {
263 6
                return true;
264 120
            } elseif (self::IS_ABSTRACT === (self::IS_ABSTRACT & $filter) && $method->isAbstract()) {
265 9
                return true;
266 117
            } elseif (self::IS_STATIC === (self::IS_STATIC & $filter) && $method->isStatic()) {
267 6
                return true;
268
            }
269
270 117
            return false;
271 129
        });
272
    }
273
}
274