Completed
Pull Request — dev (#11)
by
unknown
04:51
created

SnappyRouter::getHandlers()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 0
crap 1
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 3
                $components = empty($_SERVER['argv']) ? array() : $_SERVER['argv'];
64 3
                return $this->handleCliRoute($components).PHP_EOL;
65 1
            default:
66 1
                $queryPos = strpos($_SERVER['REQUEST_URI'], '?');
67 1
                $path = (false === $queryPos) ? $_SERVER['REQUEST_URI'] : substr($_SERVER['REQUEST_URI'], 0, $queryPos);
68 1
                return $this->handleHttpRoute(
69 1
                    $path,
70 1
                    $_GET,
71 1
                    $_POST,
72 1
                    isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'GET'
73 1
                );
74 1
        }
75
    }
76
77
    /**
78
     * Handles routing an HTTP request directly.
79
     * @param string $path The URL path from the client.
80
     * @param array $query The query parameters as an array.
81
     * @param array $post The post data as an array.
82
     * @param string $verb The HTTP verb used in the request.
83
     * @return string Returns an encoded string to pass back to the client.
84
     */
85 4
    public function handleHttpRoute($path, $query, $post, $verb)
86
    {
87 4
        return $this->invokeHandler(false, array($path, $query, $post, $verb));
88
    }
89
90
    /**
91
     * Handles routing a CLI request directly.
92
     * @param array $pathComponents The array of path components to the CLI script.
93
     * @return string Returns an encoded string to be output to the CLI.
94
     */
95 3
    public function handleCliRoute($pathComponents)
96
    {
97 3
        return $this->invokeHandler(true, array($pathComponents));
98
    }
99
100
    /**
101
     * Determines which handler is appropriate for this request.
102
     *
103
     * @param bool $isCli True for CLI handlers, false otherwise.
104
     * @param array $handlerParams An array parameters required by the handler.
105
     * @return Returns the handler to be used for the route.
106
     */
107 7
    private function invokeHandler($isCli, $handlerParams)
108
    {
109 7
        $activeHandler = null;
110
        try {
111
            // determine which handler should handle this path
112 7
            $activeHandler = $this->determineHandler($isCli, $handlerParams);
113
            // invoke the initial plugin hook
114 5
            $activeHandler->invokePluginsHook(
115 5
                'afterHandlerSelected',
116 5
                array($activeHandler)
117 5
            );
118 5
            $response = $activeHandler->performRoute();
119 3
            $activeHandler->invokePluginsHook(
120 3
                'afterFullRouteInvoked',
121 3
                array($activeHandler)
122 3
            );
123 3
            return $response;
124 4
        } catch (Exception $e) {
125
            // if we have a valid handler give it a chance to handle the error
126 4
            if (null !== $activeHandler) {
127 2
                $activeHandler->invokePluginsHook(
128 2
                    'errorOccurred',
129 2
                    array($activeHandler, $e)
130 2
                );
131 2
                return $activeHandler->getEncoder()->encode(
132 2
                    new Response($activeHandler->handleException($e))
133 2
                );
134
            }
135
136
            // if not on the command line, set an HTTP response code
137 2
            if (!$isCli) {
138 1
                $responseCode = AbstractResponse::RESPONSE_SERVER_ERROR;
139 1
                if ($e instanceof RouterExceptionInterface) {
140 1
                    $responseCode = $e->getAssociatedStatusCode();
141 1
                }
142 1
                \Vectorface\SnappyRouter\http_response_code($responseCode);
143 1
            }
144 2
            return $e->getMessage();
145
        }
146
    }
147
148
    /**
149
     * Determines which handler is appropriate for this request.
150
     *
151
     * @param bool $isCli True for CLI handlers, false otherwise.
152
     * @param array $checkParams An array parameters for the handler isAppropriate method.
153
     * @return Returns the handler to be used for the route.
154
     */
155 7
    private function determineHandler($isCli, $checkParams)
156
    {
157
        // determine which handler should handle this path
158 7
        foreach ($this->getHandlers() as $handler) {
159 6
            if ($isCli !== $handler->isCliHandler()) {
160 5
                continue;
161
            }
162 6
            $callback = array($handler, 'isAppropriate');
163 6
            if (true === call_user_func_array($callback, $checkParams)) {
164 5
                return $handler;
165
            }
166 4
        }
167
168 2
        $config = Di::getDefault()->get('config');
169 2
        if ($isCli) {
170 1
            $errorMessage = 'No CLI handler registered.';
171 1
        } else {
172 1
            $errorMessage = ($config->isDebug()) ? 'No handler responded to the request.' : '';
173
        }
174 2
        throw new ResourceNotFoundException($errorMessage);
175
    }
176
177
    /**
178
     * Parses the config, sets up the default DI and registers the config
179
     * in the DI.
180
     */
181 8
    private function parseConfig()
182
    {
183
        // setup the DI layer
184 8
        $diClass = $this->config->get(Config::KEY_DI);
185 8
        if (class_exists($diClass)) {
186 7
            $di = new $diClass();
187 7
            if ($di instanceof DiInterface) {
188 7
                Di::setDefault($di);
189 7
            }
190 7
        }
191 8
        Di::getDefault()->set(self::KEY_CONFIG, $this->config);
192 8
        $this->setupHandlers(
193 8
            $this->config->get(Config::KEY_HANDLERS, array())
194 8
        );
195 7
    }
196
197
    // helper to setup the array of handlers
198 8
    private function setupHandlers($handlers)
199
    {
200 8
        $this->handlers = array();
201 8
        foreach ($handlers as $handlerClass => $handlerDetails) {
202 7
            $options = array();
203 7
            if (isset($handlerDetails[Config::KEY_OPTIONS])) {
204 7
                $options = (array)$handlerDetails[Config::KEY_OPTIONS];
205 7
            }
206
207 7
            if (isset($handlerDetails[Config::KEY_CLASS])) {
208 7
                $handlerClass = $handlerDetails[Config::KEY_CLASS];
209 7
            }
210
211 7
            if (!class_exists($handlerClass)) {
212 1
                throw new Exception(
213
                    'Cannot instantiate instance of '.$handlerClass
214 1
                );
215
            }
216 7
            $this->handlers[] = new $handlerClass($options);
217 8
        }
218 7
    }
219
}
220