SnappyRouter::handleRoute()   B
last analyzed

Complexity

Conditions 7
Paths 12

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 7

Importance

Changes 0
Metric Value
cc 7
nc 12
nop 1
dl 0
loc 22
ccs 14
cts 14
cp 1
crap 7
rs 8.6346
c 0
b 0
f 0
1
<?php
2
3
namespace Vectorface\SnappyRouter;
4
5
use \Exception;
6
use Psr\Log\LoggerInterface;
7
use Psr\Log\NullLogger;
8
use Vectorface\SnappyRouter\Config\Config;
9
use Vectorface\SnappyRouter\Config\ConfigInterface;
10
use Vectorface\SnappyRouter\Di\Di;
11
use Vectorface\SnappyRouter\Di\DiInterface;
12
use Vectorface\SnappyRouter\Exception\ResourceNotFoundException;
13
use Vectorface\SnappyRouter\Exception\RouterExceptionInterface;
14
use Vectorface\SnappyRouter\Handler\AbstractHandler;
15
use Vectorface\SnappyRouter\Response\AbstractResponse;
16
use Vectorface\SnappyRouter\Response\Response;
17
18
/**
19
 * The main routing class that handles the full request.
20
 * @copyright Copyright (c) 2014, VectorFace, Inc.
21
 * @author Dan Bruce <[email protected]>
22
 */
23
class SnappyRouter
24
{
25
    /** the DI key for the main configuration */
26
    const KEY_CONFIG = 'config';
27
28
    private $config; // the configuration
29
    private $handlers; // array of registered handlers
30
31
    /**
32
     * @var LoggerInterface
33
     */
34
    private $logger;
35
36
    /**
37
     * The constructor for the service router.
38
     * @param array $config The configuration array.
39
     */
40 8
    public function __construct(ConfigInterface $config)
41
    {
42 8
        $this->config = $config;
43 8
        $this->parseConfig();
44 7
        $this->logger = new NullLogger();
45 7
    }
46
47
    /**
48
     * Configure SnappyRouter to log its actions.
49
     *
50
     * @param LoggerInterface $logger
51
     */
52 1
    public function setLogger(LoggerInterface $logger)
53
    {
54 1
        $this->logger = $logger;
55 1
    }
56
57
    /**
58
     * Returns the array of registered handlers.
59
     * @return The array of registered handlers.
60
     */
61 7
    public function getHandlers()
62
    {
63 7
        return $this->handlers;
64
    }
65
66
    /**
67
     * Handles the standard route. Determines the execution environment
68
     * and makes the appropriate call.
69
     * @param string $environment (optional) An optional environment variable, if not
70
     *        specified, the method will fallback to php_sapi_name().
71
     * @return string Returns the encoded response string.
72
     */
73 4
    public function handleRoute($environment = null)
74
    {
75 4
        if (null === $environment) {
76 3
            $environment = PHP_SAPI;
77
        }
78
79
        switch ($environment) {
80 4
            case 'cli':
81 1
            case 'phpdbg':
82 3
                $components = empty($_SERVER['argv']) ? array() : $_SERVER['argv'];
83 3
                return $this->handleCliRoute($components).PHP_EOL;
84
            default:
85 1
                $queryPos = strpos($_SERVER['REQUEST_URI'], '?');
86 1
                $path = (false === $queryPos) ? $_SERVER['REQUEST_URI'] : substr($_SERVER['REQUEST_URI'], 0, $queryPos);
87 1
                return $this->handleHttpRoute(
88 1
                    $path,
89 1
                    $_GET,
90 1
                    $_POST,
91 1
                    isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'GET'
92
                );
93
        }
94
    }
95
96
    /**
97
     * Handles routing an HTTP request directly.
98
     * @param string $path The URL path from the client.
99
     * @param array $query The query parameters as an array.
100
     * @param array $post The post data as an array.
101
     * @param string $verb The HTTP verb used in the request.
102
     * @return string Returns an encoded string to pass back to the client.
103
     */
104 4
    public function handleHttpRoute($path, $query, $post, $verb)
105
    {
106 4
        $this->logger->debug("SnappyRouter: Handling HTTP route: $path");
107 4
        return $this->invokeHandler(false, array($path, $query, $post, $verb));
108
    }
109
110
    /**
111
     * Handles routing a CLI request directly.
112
     * @param array $pathComponents The array of path components to the CLI script.
113
     * @return string Returns an encoded string to be output to the CLI.
114
     */
115 3
    public function handleCliRoute($pathComponents)
116
    {
117 3
        $this->logger->debug("SnappyRouter: Handling CLI route: " . implode("/", $pathComponents));
118 3
        return $this->invokeHandler(true, array($pathComponents));
119
    }
120
121
    /**
122
     * Determines which handler is appropriate for this request.
123
     *
124
     * @param bool $isCli True for CLI handlers, false otherwise.
125
     * @param array $handlerParams An array parameters required by the handler.
126
     * @return Returns the handler to be used for the route.
127
     */
128 7
    private function invokeHandler($isCli, $handlerParams)
129
    {
130 7
        $activeHandler = null;
131
        try {
132
            // determine which handler should handle this path
133 7
            $activeHandler = $this->determineHandler($isCli, $handlerParams);
134 5
            $this->logger->debug("SnappyRouter: Selected handler: " . get_class($activeHandler));
135
            // invoke the initial plugin hook
136 5
            $activeHandler->invokePluginsHook(
137 5
                'afterHandlerSelected',
138 5
                array($activeHandler)
139
            );
140 5
            $this->logger->debug("SnappyRouter: routing");
141 5
            $response = $activeHandler->performRoute();
142 3
            $activeHandler->invokePluginsHook(
143 3
                'afterFullRouteInvoked',
144 3
                array($activeHandler)
145
            );
146 3
            return $response;
147 4
        } catch (Exception $e) {
148 4
            return $this->handleInvocationException($e, $activeHandler, $isCli);
149
        }
150
    }
151
152
    /**
153
     * Attempts to mop up after an exception during handler invocation.
154
     *
155
     * @param \Exception $exception The exception that occurred during invocation.
156
     * @param HandlerInterface $activeHandler The active handler, or null.
157
     * @param bool $isCli True for CLI handlers, false otherwise.
158
     * @return mixed Returns a handler-dependent response type, usually a string.
159
     */
160 4
    private function handleInvocationException($exception, $activeHandler, $isCli)
161
    {
162 4
        $this->logger->debug(sprintf(
163 4
            "SnappyRouter: caught exception while invoking handler: %s (%d)",
164 4
            $exception->getMessage(),
165 4
            $exception->getCode()
166
        ));
167
168
        // if we have a valid handler give it a chance to handle the error
169 4
        if (null !== $activeHandler) {
170 2
            $activeHandler->invokePluginsHook(
171 2
                'errorOccurred',
172 2
                array($activeHandler, $exception)
173
            );
174 2
            return $activeHandler->getEncoder()->encode(
175 2
                new Response($activeHandler->handleException($exception))
176
            );
177
        }
178
179
        // if not on the command line, set an HTTP response code
180 2
        if (!$isCli) {
181 1
            $responseCode = AbstractResponse::RESPONSE_SERVER_ERROR;
182 1
            if ($exception instanceof RouterExceptionInterface) {
183 1
                $responseCode = $exception->getAssociatedStatusCode();
184
            }
185 1
            \Vectorface\SnappyRouter\http_response_code($responseCode);
186
        }
187 2
        return $exception->getMessage();
188
    }
189
190
    /**
191
     * Determines which handler is appropriate for this request.
192
     *
193
     * @param bool $isCli True for CLI handlers, false otherwise.
194
     * @param array $checkParams An array parameters for the handler isAppropriate method.
195
     * @return Returns the handler to be used for the route.
196
     */
197 7
    private function determineHandler($isCli, $checkParams)
198
    {
199
        // determine which handler should handle this path
200 7
        foreach ($this->getHandlers() as $handler) {
201 6
            if ($isCli !== $handler->isCliHandler()) {
202 5
                continue;
203
            }
204 6
            $callback = array($handler, 'isAppropriate');
205 6
            if (true === call_user_func_array($callback, $checkParams)) {
206 6
                return $handler;
207
            }
208
        }
209
210 2
        $config = Di::getDefault()->get('config');
211 2
        if ($isCli) {
212 1
            $errorMessage = 'No CLI handler registered.';
213
        } else {
214 1
            $errorMessage = ($config->isDebug()) ? 'No handler responded to the request.' : '';
215
        }
216 2
        throw new ResourceNotFoundException($errorMessage);
217
    }
218
219
    /**
220
     * Parses the config, sets up the default DI and registers the config
221
     * in the DI.
222
     */
223 8
    private function parseConfig()
224
    {
225
        // setup the DI layer
226 8
        $diClass = $this->config->get(Config::KEY_DI);
227 8
        if (class_exists($diClass)) {
228 7
            $di = new $diClass();
229 7
            if ($di instanceof DiInterface) {
230 7
                Di::setDefault($di);
231
            }
232
        }
233 8
        Di::getDefault()->set(self::KEY_CONFIG, $this->config);
234 8
        $this->setupHandlers(
235 8
            $this->config->get(Config::KEY_HANDLERS, array())
236
        );
237 7
    }
238
239
    // helper to setup the array of handlers
240 8
    private function setupHandlers($handlers)
241
    {
242 8
        $this->handlers = array();
243 8
        foreach ($handlers as $handlerClass => $handlerDetails) {
244 7
            $options = array();
245 7
            if (isset($handlerDetails[Config::KEY_OPTIONS])) {
246 7
                $options = (array)$handlerDetails[Config::KEY_OPTIONS];
247
            }
248
249 7
            if (isset($handlerDetails[Config::KEY_CLASS])) {
250 7
                $handlerClass = $handlerDetails[Config::KEY_CLASS];
251
            }
252
253 7
            if (!class_exists($handlerClass)) {
254 1
                throw new Exception(
255 1
                    'Cannot instantiate instance of '.$handlerClass
256
                );
257
            }
258 7
            $this->handlers[] = new $handlerClass($options);
259
        }
260 7
    }
261
}
262