Completed
Pull Request — master (#16)
by Jonathan
01:33
created

SnappyRouter::setLogger()   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
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 4
ccs 3
cts 3
cp 1
crap 1
rs 10
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)
0 ignored issues
show
Coding Style introduced by
handleRoute uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
handleRoute uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
handleRoute uses the super-global variable $_POST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
74
    {
75 4
        if (null === $environment) {
76 3
            $environment = PHP_SAPI;
77 3
        }
78
79
        switch ($environment) {
80 4
            case 'cli':
81 4
            case 'phpdbg':
82 3
                $components = empty($_SERVER['argv']) ? array() : $_SERVER['argv'];
83 3
                return $this->handleCliRoute($components).PHP_EOL;
84 1
            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 1
                );
93 1
        }
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 5
            );
140 5
            $this->logger->debug("SnappyRouter: routing");
141 5
            $response = $activeHandler->performRoute();
142 3
            $activeHandler->invokePluginsHook(
143 3
                'afterFullRouteInvoked',
144 3
                array($activeHandler)
145 3
            );
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 4
        ));
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 2
            );
174 2
            return $activeHandler->getEncoder()->encode(
175 2
                new Response($activeHandler->handleException($exception))
176 2
            );
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 1
            }
185 1
            \Vectorface\SnappyRouter\http_response_code($responseCode);
186 1
        }
187 2
        return $exception->getMessage();
188
189
    }
190
191
    /**
192
     * Determines which handler is appropriate for this request.
193
     *
194
     * @param bool $isCli True for CLI handlers, false otherwise.
195
     * @param array $checkParams An array parameters for the handler isAppropriate method.
196
     * @return Returns the handler to be used for the route.
197
     */
198 7
    private function determineHandler($isCli, $checkParams)
199
    {
200
        // determine which handler should handle this path
201 7
        foreach ($this->getHandlers() as $handler) {
202 6
            if ($isCli !== $handler->isCliHandler()) {
203 5
                continue;
204
            }
205 6
            $callback = array($handler, 'isAppropriate');
206 6
            if (true === call_user_func_array($callback, $checkParams)) {
207 5
                return $handler;
208
            }
209 4
        }
210
211 2
        $config = Di::getDefault()->get('config');
212 2
        if ($isCli) {
213 1
            $errorMessage = 'No CLI handler registered.';
214 1
        } else {
215 1
            $errorMessage = ($config->isDebug()) ? 'No handler responded to the request.' : '';
216
        }
217 2
        throw new ResourceNotFoundException($errorMessage);
218
    }
219
220
    /**
221
     * Parses the config, sets up the default DI and registers the config
222
     * in the DI.
223
     */
224 8
    private function parseConfig()
225
    {
226
        // setup the DI layer
227 8
        $diClass = $this->config->get(Config::KEY_DI);
228 8
        if (class_exists($diClass)) {
229 7
            $di = new $diClass();
230 7
            if ($di instanceof DiInterface) {
231 7
                Di::setDefault($di);
232 7
            }
233 7
        }
234 8
        Di::getDefault()->set(self::KEY_CONFIG, $this->config);
235 8
        $this->setupHandlers(
236 8
            $this->config->get(Config::KEY_HANDLERS, array())
237 8
        );
238 7
    }
239
240
    // helper to setup the array of handlers
241 8
    private function setupHandlers($handlers)
242
    {
243 8
        $this->handlers = array();
244 8
        foreach ($handlers as $handlerClass => $handlerDetails) {
245 7
            $options = array();
246 7
            if (isset($handlerDetails[Config::KEY_OPTIONS])) {
247 7
                $options = (array)$handlerDetails[Config::KEY_OPTIONS];
248 7
            }
249
250 7
            if (isset($handlerDetails[Config::KEY_CLASS])) {
251 7
                $handlerClass = $handlerDetails[Config::KEY_CLASS];
252 7
            }
253
254 7
            if (!class_exists($handlerClass)) {
255 1
                throw new Exception(
256
                    'Cannot instantiate instance of '.$handlerClass
257 1
                );
258
            }
259 7
            $this->handlers[] = new $handlerClass($options);
260 8
        }
261 7
    }
262
}
263