Completed
Pull Request — master (#74)
by Tobias
03:28
created

ClassDiscovery::storeInCache()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 2
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
use Http\Discovery\Strategy\DiscoveryStrategy;
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
    ];
28
29
    /**
30
     * Discovery cache to make the second time we use discovery faster.
31
     *
32
     * @var array
33
     */
34
    private static $cache = [];
35
36
    /**
37
     * Finds a class.
38
     *
39
     * @param string $type
40
     *
41
     * @return string|\Closure
42
     *
43
     * @throws DiscoveryFailedException
44
     */
45
    protected static function findOneByType($type)
46
    {
47
        // Look in the cache
48
        if (null !== ($class = self::getFromCache($type))) {
49
            return $class;
50
        }
51
52
        $exceptions = [];
53
        foreach (self::$strategies as $strategy) {
54
            try {
55
                if (is_callable($strategy)) {
56
                    $candidates = $strategy($type);
57
                } elseif ($strategy instanceof DiscoveryStrategy) {
58
                    $candidates = $strategy->getCandidates($type);
59
                } elseif (is_string($strategy)) {
60
                    $candidates = call_user_func($strategy.'::getCandidates', $type);
61
                } else {
62
                    throw new \Exception("Stratiegy must be callable, an instance of DiscoveryStrategy or at least a FQCN to a instance of a DiscoveryStrategy");
63
                }
64
            } catch (StrategyUnavailableException $e) {
65
                $exceptions[] = $e;
66
                continue;
67
            }
68
69
            foreach ($candidates as $candidate) {
70
                if (isset($candidate['condition'])) {
71
                    if (!self::evaluateCondition($candidate['condition'])) {
72
                        continue;
73
                    }
74
                }
75
76
                // save the result for later use
77
                self::storeInCache($type, $candidate);
78
79
                return $candidate['class'];
80
            }
81
        }
82
83
        throw new DiscoveryFailedException('Could not find resource using any discovery strategy', $exceptions);
84
    }
85
86
    /**
87
     * Get a value from cache.
88
     *
89
     * @param string $type
90
     *
91
     * @return string|null
92
     */
93
    private static function getFromCache($type)
94
    {
95
        if (!isset(self::$cache[$type])) {
96
            return;
97
        }
98
99
        $candidate = self::$cache[$type];
100
        if (isset($candidate['condition'])) {
101
            if (!self::evaluateCondition($candidate['condition'])) {
102
                return;
103
            }
104
        }
105
106
        return $candidate['class'];
107
    }
108
109
    /**
110
     * Store a value in cache.
111
     *
112
     * @param string $type
113
     * @param string $class
114
     */
115
    private static function storeInCache($type, $class)
116
    {
117
        self::$cache[$type] = $class;
118
    }
119
120
    /**
121
     * Set new strategies and clear the cache.
122
     *
123
     * @param array $strategies string array of fully qualified class name to a DiscoveryStrategy
124
     */
125
    public static function setStrategies(array $strategies)
126
    {
127
        self::$strategies = $strategies;
128
        self::clearCache();
129
    }
130
131
    /**
132
     * Append a strategy at the end of the strategy queue.
133
     *
134
     * @param string $strategy Fully qualified class name to a DiscoveryStrategy
135
     */
136
    public static function appendStrategy($strategy)
137
    {
138
        self::$strategies[] = $strategy;
139
        self::clearCache();
140
    }
141
142
    /**
143
     * Prepend a strategy at the beginning of the strategy queue.
144
     *
145
     * @param string $strategy Fully qualified class name to a DiscoveryStrategy
146
     */
147
    public static function prependStrategy($strategy)
148
    {
149
        array_unshift(self::$strategies, $strategy);
150
        self::clearCache();
151
    }
152
153
    /**
154
     * Clear the cache.
155
     */
156
    public static function clearCache()
157
    {
158
        self::$cache = [];
159
    }
160
161
    /**
162
     * Evaulates conditions to boolean.
163
     *
164
     * @param mixed $condition
165
     *
166
     * @return bool
167
     */
168
    protected static function evaluateCondition($condition)
169
    {
170
        if (is_string($condition)) {
171
            // Should be extended for functions, extensions???
172
            return class_exists($condition);
173
        } elseif (is_callable($condition)) {
174
            return $condition();
175
        } elseif (is_bool($condition)) {
176
            return $condition;
177
        } elseif (is_array($condition)) {
178
            $evaluatedCondition = true;
179
180
            // Immediately stop execution if the condition is false
181
            for ($i = 0; $i < count($condition) && false !== $evaluatedCondition; ++$i) {
182
                $evaluatedCondition &= static::evaluateCondition($condition[$i]);
183
            }
184
185
            return $evaluatedCondition;
186
        }
187
188
        return false;
189
    }
190
191
    /**
192
     * Get an instance of the $class.
193
     *
194
     * @param string|\Closure $class A FQCN of a class or a closure that instantiate the class.
195
     *
196
     * @return object
197
     *
198
     * @throws ClassInstantiationFailedException
199
     */
200
    protected static function instantiateClass($class)
201
    {
202
        try {
203
            if (is_string($class)) {
204
                return new $class();
205
            }
206
207
            if (is_callable($class)) {
208
                return $class();
209
            }
210
        } catch (\Exception $e) {
211
            throw new ClassInstantiationFailedException('Unexcepced exception when instantiating class.', 0, $e);
212
        }
213
214
        throw new ClassInstantiationFailedException('Could not instantiate class becuase parameter is neither a callable nor a string');
215
    }
216
}
217