Completed
Push — master ( 9fcc4c...e97560 )
by David
07:18
created

src/ClassDiscovery.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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\StrategyUnavailableException;
8
9
/**
10
 * Registry that based find results on class existence.
11
 *
12
 * @author David de Boer <[email protected]>
13
 * @author Márk Sági-Kazár <[email protected]>
14
 * @author Tobias Nyholm <[email protected]>
15
 */
16
abstract class ClassDiscovery
17
{
18
    /**
19
     * A list of strategies to find classes.
20
     *
21
     * @var array
22
     */
23
    private static $strategies = [
24
        Strategy\PuliBetaStrategy::class,
25
        Strategy\CommonClassesStrategy::class,
26
    ];
27
28
    /**
29
     * Discovery cache to make the second time we use discovery faster.
30
     *
31
     * @var array
32
     */
33
    private static $cache = [];
34
35
    /**
36
     * Finds a class.
37
     *
38
     * @param string $type
39
     *
40
     * @return string|\Closure
41
     *
42
     * @throws DiscoveryFailedException
43
     */
44 15
    protected static function findOneByType($type)
45
    {
46
        // Look in the cache
47 15
        if (null !== ($class = self::getFromCache($type))) {
48
            return $class;
49
        }
50
51 15
        $exceptions = [];
52 15
        foreach (self::$strategies as $strategy) {
53
            try {
54 15
                $candidates = call_user_func($strategy.'::getCandidates', $type);
55 15
            } catch (StrategyUnavailableException $e) {
56
                $exceptions[] = $e;
57
58
                continue;
59
            }
60
61 15
            foreach ($candidates as $candidate) {
62 9
                if (isset($candidate['condition'])) {
63 7
                    if (!self::evaluateCondition($candidate['condition'])) {
64 1
                        continue;
65
                    }
66 7
                }
67
68
                // save the result for later use
69 9
                self::storeInCache($type, $candidate);
70
71 9
                return $candidate['class'];
72 7
            }
73 7
        }
74
75 6
        throw DiscoveryFailedException::create($exceptions);
76
    }
77
78
    /**
79
     * Get a value from cache.
80
     *
81
     * @param string $type
82
     *
83
     * @return string|null
84
     */
85 15
    private static function getFromCache($type)
86
    {
87 15
        if (!isset(self::$cache[$type])) {
88 15
            return;
89
        }
90
91
        $candidate = self::$cache[$type];
92
        if (isset($candidate['condition'])) {
93
            if (!self::evaluateCondition($candidate['condition'])) {
94
                return;
95
            }
96
        }
97
98
        return $candidate['class'];
99
    }
100
101
    /**
102
     * Store a value in cache.
103
     *
104
     * @param string $type
105
     * @param string $class
106
     */
107 9
    private static function storeInCache($type, $class)
108
    {
109 9
        self::$cache[$type] = $class;
110 9
    }
111
112
    /**
113
     * Set new strategies and clear the cache.
114
     *
115
     * @param array $strategies string array of fully qualified class name to a DiscoveryStrategy
116
     */
117 27
    public static function setStrategies(array $strategies)
118
    {
119 27
        self::$strategies = $strategies;
120 27
        self::clearCache();
121 27
    }
122
123
    /**
124
     * Append a strategy at the end of the strategy queue.
125
     *
126
     * @param string $strategy Fully qualified class name to a DiscoveryStrategy
127
     */
128 1
    public static function appendStrategy($strategy)
129
    {
130 1
        self::$strategies[] = $strategy;
131 1
        self::clearCache();
132 1
    }
133
134
    /**
135
     * Prepend a strategy at the beginning of the strategy queue.
136
     *
137
     * @param string $strategy Fully qualified class name to a DiscoveryStrategy
138
     */
139 1
    public static function prependStrategy($strategy)
140
    {
141 1
        array_unshift(self::$strategies, $strategy);
142 1
        self::clearCache();
143 1
    }
144
145
    /**
146
     * Clear the cache.
147
     */
148 27
    public static function clearCache()
149
    {
150 27
        self::$cache = [];
151 27
    }
152
153
    /**
154
     * Evaluates conditions to boolean.
155
     *
156
     * @param mixed $condition
157
     *
158
     * @return bool
159
     */
160 7
    protected static function evaluateCondition($condition)
161
    {
162 7
        if (is_string($condition)) {
163
            // Should be extended for functions, extensions???
164
            return self::safeClassExists($condition);
165 7
        } elseif (is_callable($condition)) {
166
            return $condition();
167 7
        } elseif (is_bool($condition)) {
168 7
            return $condition;
169
        } elseif (is_array($condition)) {
170
            // Immediately stop execution if the condition is false
171
            for ($i = 0; $i < count($condition); ++$i) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
172
                if (false === static::evaluateCondition($condition[$i])) {
173
                    return false;
174
                }
175
            }
176
177
            return true;
178
        }
179
180
        return false;
181
    }
182
183
    /**
184
     * Get an instance of the $class.
185
     *
186
     * @param string|\Closure $class A FQCN of a class or a closure that instantiate the class.
187
     *
188
     * @return object
189
     *
190
     * @throws ClassInstantiationFailedException
191
     */
192 5
    protected static function instantiateClass($class)
193
    {
194
        try {
195 5
            if (is_string($class)) {
196 5
                return new $class();
197
            }
198
199
            if (is_callable($class)) {
200
                return $class();
201
            }
202
        } catch (\Exception $e) {
203
            throw new ClassInstantiationFailedException('Unexpected exception when instantiating class.', 0, $e);
204
        }
205
206
        throw new ClassInstantiationFailedException('Could not instantiate class because parameter is neither a callable nor a string');
207
    }
208
209
    /**
210
     * We want to do a "safe" version of PHP's "class_exists" because Magento has a bug
211
     * (or they call it a "feature"). Magento is throwing an exception if you do class_exists()
212
     * on a class that ends with "Factory" and if that file does not exits.
213
     *
214
     * This function will catch all potential exceptions and make sure it returns a boolean.
215
     *
216
     * @param string $class
217
     * @param bool   $autoload
218
     *
219
     * @return bool
220
     */
221
    public static function safeClassExists($class)
222
    {
223
        try {
224
            return class_exists($class);
225
        } catch (\Exception $e) {
226
            return false;
227
        }
228
    }
229
}
230