Completed
Push — master ( 7c36d9...27903a )
by David
04:58
created

ClassDiscovery   A

Complexity

Total Complexity 31

Size/Duplication

Total Lines 230
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 69.86%

Importance

Changes 0
Metric Value
wmc 31
lcom 1
cbo 3
dl 0
loc 230
ccs 51
cts 73
cp 0.6986
rs 9.92
c 0
b 0
f 0

11 Methods

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