ClassDiscovery::instantiateClass()   A
last analyzed

Complexity

Conditions 4
Paths 7

Size

Total Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
dl 0
loc 16
ccs 0
cts 5
cp 0
rs 9.7333
c 0
b 0
f 0
cc 4
nc 7
nop 1
crap 20
1
<?php
2
3
namespace Http\Discovery;
4
5
use Http\Discovery\Exception\ClassInstantiationFailedException;
6
use Http\Discovery\Exception\DiscoveryFailedException;
7
use Http\Discovery\Exception\NoCandidateFoundException;
8
use Http\Discovery\Exception\StrategyUnavailableException;
9
10
/**
11
 * Registry that based find results on class existence.
12
 *
13
 * @author David de Boer <[email protected]>
14
 * @author Márk Sági-Kazár <[email protected]>
15
 * @author Tobias Nyholm <[email protected]>
16
 */
17
abstract class ClassDiscovery
18
{
19
    /**
20
     * A list of strategies to find classes.
21
     *
22
     * @var array
23
     */
24
    private static $strategies = [
25
        Strategy\CommonClassesStrategy::class,
26
        Strategy\CommonPsr17ClassesStrategy::class,
27
        Strategy\PuliBetaStrategy::class,
28
    ];
29
30
    private static $deprecatedStrategies = [
31
        Strategy\PuliBetaStrategy::class => true,
32
    ];
33
34
    /**
35
     * Discovery cache to make the second time we use discovery faster.
36
     *
37
     * @var array
38
     */
39
    private static $cache = [];
40
41
    /**
42
     * Finds a class.
43
     *
44
     * @param string $type
45
     *
46 15
     * @return string|\Closure
47
     *
48
     * @throws DiscoveryFailedException
49 15
     */
50
    protected static function findOneByType($type)
51
    {
52
        // Look in the cache
53 15
        if (null !== ($class = self::getFromCache($type))) {
54 15
            return $class;
55
        }
56 15
57
        $exceptions = [];
58
        foreach (self::$strategies as $strategy) {
59
            try {
60
                $candidates = call_user_func($strategy.'::getCandidates', $type);
61
            } catch (StrategyUnavailableException $e) {
62
                if (!isset(self::$deprecatedStrategies[$strategy])) {
63 15
                    $exceptions[] = $e;
64 9
                }
65 7
66 1
                continue;
67
            }
68
69
            foreach ($candidates as $candidate) {
70
                if (isset($candidate['condition'])) {
71 9
                    if (!self::evaluateCondition($candidate['condition'])) {
72
                        continue;
73 9
                    }
74
                }
75
76 7
                // save the result for later use
77
                self::storeInCache($type, $candidate);
78
79 6
                return $candidate['class'];
80
            }
81
82
            $exceptions[] = new NoCandidateFoundException($strategy, $candidates);
83
        }
84
85
        throw DiscoveryFailedException::create($exceptions);
86
    }
87
88
    /**
89 15
     * Get a value from cache.
90
     *
91 15
     * @param string $type
92 15
     *
93
     * @return string|null
94
     */
95
    private static function getFromCache($type)
96
    {
97
        if (!isset(self::$cache[$type])) {
98
            return;
99
        }
100
101
        $candidate = self::$cache[$type];
102
        if (isset($candidate['condition'])) {
103
            if (!self::evaluateCondition($candidate['condition'])) {
104
                return;
105
            }
106
        }
107
108
        return $candidate['class'];
109
    }
110
111 9
    /**
112
     * Store a value in cache.
113 9
     *
114 9
     * @param string $type
115
     * @param string $class
116
     */
117
    private static function storeInCache($type, $class)
118
    {
119
        self::$cache[$type] = $class;
120
    }
121 30
122
    /**
123 30
     * Set new strategies and clear the cache.
124 30
     *
125 30
     * @param array $strategies string array of fully qualified class name to a DiscoveryStrategy
126
     */
127
    public static function setStrategies(array $strategies)
128
    {
129
        self::$strategies = $strategies;
130
        self::clearCache();
131
    }
132 1
133
    /**
134 1
     * Returns the currently configured discovery strategies as fully qualified class names.
135
     *
136
     * @return string[]
137
     */
138
    public static function getStrategies(): iterable
139
    {
140
        return self::$strategies;
141
    }
142 1
143
    /**
144 1
     * Append a strategy at the end of the strategy queue.
145 1
     *
146 1
     * @param string $strategy Fully qualified class name to a DiscoveryStrategy
147
     */
148
    public static function appendStrategy($strategy)
149
    {
150
        self::$strategies[] = $strategy;
151
        self::clearCache();
152
    }
153 1
154
    /**
155 1
     * Prepend a strategy at the beginning of the strategy queue.
156 1
     *
157 1
     * @param string $strategy Fully qualified class name to a DiscoveryStrategy
158
     */
159
    public static function prependStrategy($strategy)
160
    {
161
        array_unshift(self::$strategies, $strategy);
162 30
        self::clearCache();
163
    }
164 30
165 30
    /**
166
     * Clear the cache.
167
     */
168
    public static function clearCache()
169
    {
170
        self::$cache = [];
171
    }
172
173
    /**
174 7
     * Evaluates conditions to boolean.
175
     *
176 7
     * @param mixed $condition
177
     *
178
     * @return bool
179
     */
180 7
    protected static function evaluateCondition($condition)
181
    {
182
        if (is_string($condition)) {
183 7
            // Should be extended for functions, extensions???
184 7
            return self::safeClassExists($condition);
185
        }
186 1
        if (is_callable($condition)) {
187 1
            return (bool) $condition();
188 1
        }
189
        if (is_bool($condition)) {
190 1
            return $condition;
191
        }
192
        if (is_array($condition)) {
193
            foreach ($condition as $c) {
194
                if (false === static::evaluateCondition($c)) {
195
                    // Immediately stop execution if the condition is false
196
                    return false;
197
                }
198
            }
199
200
            return true;
201
        }
202
203
        return false;
204
    }
205
206
    /**
207
     * Get an instance of the $class.
208
     *
209 5
     * @param string|\Closure $class A FQCN of a class or a closure that instantiate the class.
210
     *
211
     * @return object
212 5
     *
213 5
     * @throws ClassInstantiationFailedException
214
     */
215
    protected static function instantiateClass($class)
216
    {
217
        try {
218
            if (is_string($class)) {
219
                return new $class();
220
            }
221
222
            if (is_callable($class)) {
223
                return $class();
224
            }
225
        } catch (\Exception $e) {
226
            throw new ClassInstantiationFailedException('Unexpected exception when instantiating class.', 0, $e);
227
        }
228
229
        throw new ClassInstantiationFailedException('Could not instantiate class because parameter is neither a callable nor a string');
230
    }
231
232
    /**
233
     * We want to do a "safe" version of PHP's "class_exists" because Magento has a bug
234
     * (or they call it a "feature"). Magento is throwing an exception if you do class_exists()
235
     * on a class that ends with "Factory" and if that file does not exits.
236
     *
237
     * This function will catch all potential exceptions and make sure it returns a boolean.
238
     *
239
     * @param string $class
240
     * @param bool   $autoload
0 ignored issues
show
Bug introduced by
There is no parameter named $autoload. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
241
     *
242
     * @return bool
243
     */
244
    public static function safeClassExists($class)
245
    {
246
        try {
247
            return class_exists($class) || interface_exists($class);
248
        } catch (\Exception $e) {
249
            return false;
250
        }
251
    }
252
}
253