PluginProbe
DecaLog / 4.4.0
DecaLog v4.4.0
3.0.2 3.1.0 3.10.0 3.2.0 3.3.0 3.4.0 3.4.1 3.5.0 3.5.1 3.6.0 3.6.1 3.6.2 3.6.3 3.7.0 3.7.1 3.8.0 3.9.0 3.9.1 4.0.0 4.1.0 4.2.0 4.3.0 4.3.1 4.4.0 4.5.0 All 75 releases
decalog / includes / libraries / http / discovery / ClassDiscovery.php

ClassDiscovery.php in DecaLog 4.4.0, at includes/libraries/http/discovery/ClassDiscovery.php

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