Completed
Push — master ( 7c6931...2dcbf7 )
by Tomasz
02:34
created

RouteDetector::setDefault()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 3
cts 3
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
crap 1
1
<?php
2
3
namespace Gendoria\CommandQueue\RouteDetection\Detector;
4
5
use Gendoria\CommandQueue\RouteDetection\Detection\ClassDetection;
6
use Gendoria\CommandQueue\RouteDetection\Detection\DefaultDetection;
7
use Gendoria\CommandQueue\RouteDetection\Detection\DetectionInterface;
8
use InvalidArgumentException;
9
use ReflectionClass;
10
11
/**
12
 * Detector class used for match class expressions with arbitrary routes.
13
 *
14
 * @author Tomasz Struczyński <[email protected]>
15
 */
16
class RouteDetector
17
{
18
    /**
19
     * Simple routes in a form of [Class] => [PoolName].
20
     *
21
     * @var array
22
     */
23
    private $simpleRoutes = array();
24
25
    /**
26
     * Expression routes in a form of [ClassExpression] => [PoolName].
27
     *
28
     * @var array
29
     */
30
    private $regexpRoutes = array();
31
32
    /**
33
     * Default route.
34
     *
35
     * @var string
36
     */
37
    private $defaultRoute = '';
38
39
    /**
40
     * Class constructor.
41
     */
42 24
    public function __construct($defaultRoute = '')
43
    {
44 24
        $this->setDefault($defaultRoute);
45 24
    }
46
47
    /**
48
     * Add new route.
49
     *
50
     * @param string $expression Either simple expression, or RegExp describing route.
51
     * @param string $route
52
     *
53
     * @return bool True, if route has been set, false otherwise.
54
     */
55 17
    public function addRoute($expression, $route)
56
    {
57
        //Detect command expression
58 17
        if (strpos($expression, '*') !== false) {
59 3
            $expression = '|^'.str_replace(array('*', '\\'), array('.*', '\\\\'), $expression).'$|i';
60 3
            if (array_key_exists($expression, $this->regexpRoutes) && $this->regexpRoutes[$expression] == $route) {
61 1
                return false;
62
            }
63 3
            $this->regexpRoutes[$expression] = (string) $route;
64 3
        } else {
65 15
            if (array_key_exists($expression, $this->simpleRoutes) && $this->simpleRoutes[$expression] == $route) {
66 2
                return false;
67
            }
68 15
            $this->simpleRoutes[$expression] = (string) $route;
69
        }
70
71 17
        return true;
72
    }
73
74
    /**
75
     * Set default route.
76
     *
77
     * @param string $route
78
     */
79 21
    public function setDefault($route)
80
    {
81 21
        $this->defaultRoute = (string) $route;
82 21
    }
83
84
    /**
85
     * Get default route.
86
     *
87
     * @return string
88
     */
89 12
    protected function getDefault()
90
    {
91 12
        return $this->defaultRoute;
92
    }
93
94
    /**
95
     * Detect correct route for given class.
96
     *
97
     * @param string $className
98
     *
99
     * @return string
100
     *
101
     * @throws InvalidArgumentException Thrown, if argument is not a class name.
102
     */
103 24
    public function detect($className)
104
    {
105 24
        if (!class_exists($className)) {
106 1
            throw new \InvalidArgumentexception('Given name is not a class');
107
        }
108
109 23
        return $this->doDetect($className)->getPoolName();
110
    }
111
112
    /**
113
     * Detect pool.
114
     *
115
     * @param string $className
116
     * @param boolean $performInterfaceDetection
117
     *
118
     * @return DetectionInterface
119
     */
120 23
    private function doDetect($className, $performInterfaceDetection = true)
121
    {
122 23
        $detection = $this->doDetectByRoutes($className);
123 23
        if ($detection) {
124 10
            return $detection;
125
        }
126
        //Nothing is found so far. We will check all of the class interfaces and base classes.
127
        //First - check base classes up to the 'root'
128 17
        $parentClass = get_parent_class($className);
129 17
        if ($parentClass) {
130 15
            $parentDetection = $this->doDetect($parentClass, false);
131 15
            if ($parentDetection instanceof ClassDetection) {
132 4
                return $parentDetection;
133
            }
134 11
        }
135
        //Check the class interfaces
136 13
        if ($performInterfaceDetection) {
137 13
            $definition = $this->doDetectByInterfaces($className);
138 13
            if ($definition) {
139 7
                return $definition;
140
            }
141 6
        }
142
143 13
        return new DefaultDetection($this->defaultRoute);
144
    }
145
146
    /**
147
     * Perform detection based on class name and routes registered for this class.
148
     *
149
     * @param string $className
150
     *
151
     * @return ClassDetection|null
152
     */
153 23
    private function doDetectByRoutes($className)
154
    {
155
        //If we have entry for a command class, we should always return it, as it is most specific.
156 23
        if (!empty($this->simpleRoutes[$className])) {
157 15
            return new ClassDetection($this->simpleRoutes[$className]);
158
        }
159
        //Now, we should check, if we have 'regexp' entry for class
160 19
        foreach ($this->regexpRoutes as $regexpRoute => $poolName) {
161 2
            if (preg_match($regexpRoute, $className)) {
162 2
                return new ClassDetection($poolName);
163
            }
164 17
        }
165 17
    }
166
167
    /**
168
     * Perform detection based on class interfaces.
169
     *
170
     * @param string $className
171
     *
172
     * @return DetectionInterface|null
173
     */
174 13
    private function doDetectByInterfaces($className)
175
    {
176 13
        $interfaces = $this->getOrderedInterfaces($className);
177 13
        foreach ($interfaces as $interface) {
178 12
            $candidate = $this->doDetectByRoutes($interface);
179 12
            if ($candidate) {
180 7
                return $candidate;
181
            }
182 10
        }
183
184 6
        return;
185
    }
186
187
    /**
188
     * Get a list of class interfaces ordered by plase of interface declaration.
189
     *
190
     * The list is ordered by place of interface declaration. Interfaces declared on most child class are first,
191
     * while those in base class(es) - last.
192
     *
193
     * @param string $className
194
     *
195
     * @return array
196
     */
197 13
    private function getOrderedInterfaces($className)
198
    {
199 13
        $interfacesArr = $this->prepareClassTreeInterfaces($className);
200 13
        $interfaces = array();
201 13
        foreach ($interfacesArr as $classInterfaces) {
202 12
            foreach ($classInterfaces as $interface) {
203 12
                if (array_search($interface, $interfaces) === false) {
204 12
                    array_unshift($interfaces, $interface);
205 12
                }
206 12
            }
207 13
        }
208
209 13
        return $interfaces;
210
    }
211
    
212
    /**
213
     * Prepares all interfaces for given class and its parents.
214
     * 
215
     * @param string $className
216
     * @return array
217
     */
218 13
    private function prepareClassTreeInterfaces($className)
219
    {
220 13
        $interfacesArr = array();
221 13
        $reflection = new ReflectionClass($className);
222 13
        $interfacesArr[] = $reflection->getInterfaceNames();
223 13
        if (empty($interfacesArr[0])) {
224 1
            return array();
225
        }
226 12
        $classParents = class_parents($className);
227 12
        foreach ($classParents as $parentClass) {
228 11
            $reflection = new ReflectionClass($parentClass);
229 11
            $tmpInterfaces = $reflection->getInterfaceNames();
230 11
            if (empty($tmpInterfaces)) {
231 8
                return $interfacesArr;
232
            }
233 10
            array_unshift($interfacesArr, $tmpInterfaces);
234 11
        }
235 4
        return $interfacesArr;
236
    }
237
}
238