Completed
Pull Request — master (#13)
by
unknown
06:57
created

SnappyRouter::invokeHandler()   B

Complexity

Conditions 6
Paths 26

Size

Total Lines 44
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 31
CRAP Score 6.008

Importance

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