Completed
Push — master ( d1be6d...8440b6 )
by Andrey
07:58
created

getClassCandidates()   C

Complexity

Conditions 15
Paths 29

Size

Total Lines 45

Duplication

Lines 10
Ratio 22.22 %

Importance

Changes 0
Metric Value
cc 15
nc 29
nop 1
dl 10
loc 45
rs 5.9166
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/*
4
 * This file is part of the Symfony package.
5
 *
6
 * (c) Fabien Potencier <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Symfony\Component\Debug\FatalErrorHandler;
13
14
use Symfony\Component\Debug\Exception\ClassNotFoundException;
15
use Symfony\Component\Debug\Exception\FatalErrorException;
16
use Symfony\Component\Debug\DebugClassLoader;
17
use Composer\Autoload\ClassLoader as ComposerClassLoader;
18
use Symfony\Component\ClassLoader\ClassLoader as SymfonyClassLoader;
19
use Symfony\Component\ClassLoader\UniversalClassLoader as SymfonyUniversalClassLoader;
20
21
/**
22
 * ErrorHandler for classes that do not exist.
23
 *
24
 * @author Fabien Potencier <[email protected]>
25
 */
26
class ClassNotFoundFatalErrorHandler implements FatalErrorHandlerInterface
27
{
28
    /**
29
     * {@inheritdoc}
30
     */
31
    public function handleError(array $error, FatalErrorException $exception)
32
    {
33
        $messageLen = strlen($error['message']);
34
        $notFoundSuffix = '\' not found';
35
        $notFoundSuffixLen = strlen($notFoundSuffix);
36
        if ($notFoundSuffixLen > $messageLen) {
37
            return;
38
        }
39
40
        if (0 !== substr_compare($error['message'], $notFoundSuffix, -$notFoundSuffixLen)) {
41
            return;
42
        }
43
44
        foreach (array('class', 'interface', 'trait') as $typeName) {
45
            $prefix = ucfirst($typeName).' \'';
46
            $prefixLen = strlen($prefix);
47
            if (0 !== strpos($error['message'], $prefix)) {
48
                continue;
49
            }
50
51
            $fullyQualifiedClassName = substr($error['message'], $prefixLen, -$notFoundSuffixLen);
52
            if (false !== $namespaceSeparatorIndex = strrpos($fullyQualifiedClassName, '\\')) {
53
                $className = substr($fullyQualifiedClassName, $namespaceSeparatorIndex + 1);
54
                $namespacePrefix = substr($fullyQualifiedClassName, 0, $namespaceSeparatorIndex);
55
                $message = sprintf('Attempted to load %s "%s" from namespace "%s".', $typeName, $className, $namespacePrefix);
56
                $tail = ' for another namespace?';
57
            } else {
58
                $className = $fullyQualifiedClassName;
59
                $message = sprintf('Attempted to load %s "%s" from the global namespace.', $typeName, $className);
60
                $tail = '?';
61
            }
62
63
            if ($candidates = $this->getClassCandidates($className)) {
64
                $tail = array_pop($candidates).'"?';
65
                if ($candidates) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $candidates of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
66
                    $tail = ' for e.g. "'.implode('", "', $candidates).'" or "'.$tail;
67
                } else {
68
                    $tail = ' for "'.$tail;
69
                }
70
            }
71
            $message .= "\nDid you forget a \"use\" statement".$tail;
72
73
            return new ClassNotFoundException($message, $exception);
74
        }
75
    }
76
77
    /**
78
     * Tries to guess the full namespace for a given class name.
79
     *
80
     * By default, it looks for PSR-0 and PSR-4 classes registered via a Symfony or a Composer
81
     * autoloader (that should cover all common cases).
82
     *
83
     * @param string $class A class name (without its namespace)
84
     *
85
     * @return array An array of possible fully qualified class names
86
     */
87
    private function getClassCandidates($class)
88
    {
89
        if (!is_array($functions = spl_autoload_functions())) {
90
            return array();
91
        }
92
93
        // find Symfony and Composer autoloaders
94
        $classes = array();
95
96
        foreach ($functions as $function) {
97
            if (!is_array($function)) {
98
                continue;
99
            }
100
            // get class loaders wrapped by DebugClassLoader
101
            if ($function[0] instanceof DebugClassLoader) {
102
                $function = $function[0]->getClassLoader();
103
104
                // @deprecated since version 2.5. Returning an object from DebugClassLoader::getClassLoader() is deprecated.
105
                if (is_object($function)) {
106
                    $function = array($function);
107
                }
108
109
                if (!is_array($function)) {
110
                    continue;
111
                }
112
            }
113
114
            if ($function[0] instanceof ComposerClassLoader || $function[0] instanceof SymfonyClassLoader || $function[0] instanceof SymfonyUniversalClassLoader) {
0 ignored issues
show
Bug introduced by
The class Symfony\Component\ClassLoader\ClassLoader does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
Bug introduced by
The class Symfony\Component\ClassLoader\UniversalClassLoader does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
115 View Code Duplication
                foreach ($function[0]->getPrefixes() as $prefix => $paths) {
0 ignored issues
show
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...
116
                    foreach ($paths as $path) {
117
                        $classes = array_merge($classes, $this->findClassInPath($path, $class, $prefix));
118
                    }
119
                }
120
            }
121
            if ($function[0] instanceof ComposerClassLoader) {
122 View Code Duplication
                foreach ($function[0]->getPrefixesPsr4() as $prefix => $paths) {
0 ignored issues
show
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...
123
                    foreach ($paths as $path) {
124
                        $classes = array_merge($classes, $this->findClassInPath($path, $class, $prefix));
125
                    }
126
                }
127
            }
128
        }
129
130
        return array_unique($classes);
131
    }
132
133
    /**
134
     * @param string $path
135
     * @param string $class
136
     * @param string $prefix
137
     *
138
     * @return array
139
     */
140
    private function findClassInPath($path, $class, $prefix)
141
    {
142
        if (!$path = realpath($path.'/'.strtr($prefix, '\\_', '//')) ?: realpath($path.'/'.dirname(strtr($prefix, '\\_', '//'))) ?: realpath($path)) {
143
            return array();
144
        }
145
146
        $classes = array();
147
        $filename = $class.'.php';
148
        foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) {
149
            if ($filename == $file->getFileName() && $class = $this->convertFileToClass($path, $file->getPathName(), $prefix)) {
150
                $classes[] = $class;
151
            }
152
        }
153
154
        return $classes;
155
    }
156
157
    /**
158
     * @param string $path
159
     * @param string $file
160
     * @param string $prefix
161
     *
162
     * @return string|null
163
     */
164
    private function convertFileToClass($path, $file, $prefix)
165
    {
166
        $candidates = array(
167
            // namespaced class
168
            $namespacedClass = str_replace(array($path.DIRECTORY_SEPARATOR, '.php', '/'), array('', '', '\\'), $file),
169
            // namespaced class (with target dir)
170
            $prefix.$namespacedClass,
171
            // namespaced class (with target dir and separator)
172
            $prefix.'\\'.$namespacedClass,
173
            // PEAR class
174
            str_replace('\\', '_', $namespacedClass),
175
            // PEAR class (with target dir)
176
            str_replace('\\', '_', $prefix.$namespacedClass),
177
            // PEAR class (with target dir and separator)
178
            str_replace('\\', '_', $prefix.'\\'.$namespacedClass),
179
        );
180
181
        if ($prefix) {
182
            $candidates = array_filter($candidates, function ($candidate) use ($prefix) { return 0 === strpos($candidate, $prefix); });
183
        }
184
185
        // We cannot use the autoloader here as most of them use require; but if the class
186
        // is not found, the new autoloader call will require the file again leading to a
187
        // "cannot redeclare class" error.
188
        foreach ($candidates as $candidate) {
189
            if ($this->classExists($candidate)) {
190
                return $candidate;
191
            }
192
        }
193
194
        require_once $file;
195
196
        foreach ($candidates as $candidate) {
197
            if ($this->classExists($candidate)) {
198
                return $candidate;
199
            }
200
        }
201
    }
202
203
    /**
204
     * @param string $class
205
     *
206
     * @return bool
207
     */
208
    private function classExists($class)
209
    {
210
        return class_exists($class, false) || interface_exists($class, false) || (function_exists('trait_exists') && trait_exists($class, false));
211
    }
212
}
213