Completed
Push — dev-master ( 178807...dd6200 )
by Derek Stephen
01:44
created

Dispatcher::checkNavigator()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 5.0909

Importance

Changes 0
Metric Value
dl 0
loc 24
ccs 11
cts 13
cp 0.8462
rs 9.2248
c 0
b 0
f 0
cc 5
nc 5
nop 0
crap 5.0909
1
<?php
2
3
namespace Bone\Mvc;
4
5
use Bone\Filter;
6
use Bone\Server\Environment;
7
use Exception;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Bone\Mvc\Exception.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
8
use ReflectionClass;
9
use Psr\Http\Message\ServerRequestInterface;
10
use Psr\Http\Message\ResponseInterface;
11
use Zend\Diactoros\Response\SapiEmitter;
12
13
/**
14
 * Class Dispatcher
15
 * @package Bone\Mvc
16
 */
17
class Dispatcher
18
{
19
    /** @var array $config */
20
    private $config = [];
21
22
    /** @var ServerRequestInterface $request */
23
    private $request;
24
25
    /** @var Controller */
26
    private $controller;
27
28
    /** @var ResponseInterface $response */
29
    private $response;
30
31
    /** @var Environment $env */
32
    private $env;
33
34
    /**
35
     * Dispatcher constructor.
36
     * @param ServerRequestInterface $request
37
     * @param ResponseInterface $response
38
     * @throws \Exception
39
     */
40 15
    public function __construct(ServerRequestInterface $request, ResponseInterface $response, Environment $env)
41 15
    {
42 15
        $this->request = $request;
43 15
        $this->response = $response;
44 15
        $this->env = $env;
45
46 15
        $router = new Router($request);
47 15
        $router->parseRoute();
48
49
        // what controller be we talkin' about?
50 15
        $filtered = Filter::filterString($router->getController(), 'DashToCamelCase');
51 15
        $this->config['controller_name'] = '\App\Controller\\' . ucwords($filtered) . 'Controller';
52
53
        // whit be yer action ?
54 15
        $filtered = Filter::filterString($router->getAction(), 'DashToCamelCase');
55 15
        $this->config['action_name'] = $filtered . 'Action';
56 15
        $this->config['controller'] = $router->getController();
57 15
        $this->config['action'] = $router->getAction();
58 15
        $this->config['params'] = $router->getParams();
59 15
    }
60
61
62
    /**
63
     *  Gaaarrr! Check the Navigator be readin' the map!
64
     * @return null|void
65
     */
66 2
    public function checkNavigator()
67 2
    {
68
        // can we find th' darned controller?
69 1
        if (!$this->checkControllerExists()) {
70 1
            $this->setNotFound();
71 1
            return;
72
        }
73
74
        // merge the feckin params
75 1
        if (array_key_exists('params', $this->config) && is_array($this->config['params']) ) {
76
            $merged = array_merge($this->config['params'], $this->request->getQueryParams());
77
            $this->request = $this->request->withQueryParams($merged);
78
        }
79
80
        // create the controller
81 1
        $this->controller = new $this->config['controller_name']($this->request);
82 1
        $this->controller->setServerEnvironment($this->getEnv());
83
84
        // where's the bloody action?
85 1
        if (!$this->checkActionExists()) {
86 1
            $this->setNotFound();
87
        }
88 1
        return null;
89
    }
90
91
92
    /**
93
     * @return bool
94
     */
95 2
    private function checkControllerExists()
96 2
    {
97 2
        return class_exists($this->config['controller_name']);
98
    }
99
100
101
    /**
102
     * @return bool
103
     */
104 2
    private function checkActionExists()
105 2
    {
106 2
        return method_exists($this->controller, $this->config['action_name']);
107
    }
108
109
110
    /**
111
     * @return string
112
     * @throws \Exception
113
     */
114 4
    private function distributeBooty()
115 4
    {
116
        /** @var \stdClass $viewVars */
117 4
        $viewVars = $this->controller->view;
118
119 4
        if ($viewVars instanceof ResponseInterface) {
120 1
            $this->response = $viewVars;
121 1
            $this->sendResponse();
122 1
            return;
123
        }
124
125 3
        $responseBody = $this->controller->getBody();
126
127 3
        if ($this->controller->hasViewEnabled()) {
128 2
            $view = $this->config['controller'] . '/' . $this->config['action'];
129
            try {
130 2
                $responseBody = $this->controller->getViewEngine()->render($view, (array) $viewVars);
131
            } catch (Exception $e) {
132
                throw $e;
133
            }
134
        }
135
136 3
        if ($this->controller->hasLayoutEnabled()) {
137 2
            $responseBody = $this->templateCheck($this->controller, $responseBody);
138
        }
139 3
        $this->prepareResponse($responseBody);
140 3
        $this->sendResponse();
141 3
    }
142
143
144
    /**
145
     * @throws Exception
146
     */
147 2
    public function fireCannons()
148 2
    {
149
        try {
150
            // Garr! Check the route with th' navigator
151 1
            $this->checkNavigator();
152
153
            // Fire cannons t' th' controller action
154 1
            $this->plunderEnemyShip();
155
156
            // Share the loot! send out th' response
157 1
            $this->distributeBooty();
158
        } catch (Exception $e) {
159
            $this->sinkingShip($e);
160
        }
161 1
    }
162
163
    /**
164
     * @param $booty
165
     */
166 3
    private function prepareResponse($booty)
167 3
    {
168 3
        $this->response->getBody()->write($booty);
169 3
        $this->setHeaders();
170 3
        $this->setStatusCode();
171 3
    }
172
173
174 4
    private function sendResponse()
175 4
    {
176 4
        $emitter = new SapiEmitter();
0 ignored issues
show
Deprecated Code introduced by
The class Zend\Diactoros\Response\SapiEmitter has been deprecated with message: since 1.8.0. The package zendframework/zend-httphandlerrunner now provides this functionality.

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
177 4
        $emitter->emit($this->response);
178 4
    }
179
180 4
    private function setHeaders()
181 4
    {
182 4
        foreach ($this->controller->getHeaders() as $key => $value) {
183 2
            $this->response = $this->response->withHeader($key, $value);
184
        }
185 4
    }
186
187 4
    private function setStatusCode()
188 4
    {
189 4
        $status = $this->controller->getStatusCode();
190 4
        if ($status != 200) {
191
            try {
192 1
                $this->response = $this->response->withStatus($status);
193 1
            } catch (Exception $e) {
194 1
                $this->response = $this->response->withStatus(500);
195
            }
196
197
        }
198 4
    }
199
200
201 3
    private function plunderEnemyShip()
202 3
    {
203
        // run th' controller action
204 3
        $action = $this->config['action_name'];
205
206 3
        $this->controller->init();
207 3
        $vars = $this->controller->$action();
208
209 3
        if (is_array($vars)) {
210
211 1
            $viewVars = (array) $this->controller->view;
212 1
            $view = (object) array_merge($vars, $viewVars);
213 1
            $this->controller->view = $view;
214
215 2
        } elseif ($vars instanceof ResponseInterface) {
216
217 1
            $this->controller->view = $vars;
0 ignored issues
show
Documentation Bug introduced by
It seems like $vars of type object<Psr\Http\Message\ResponseInterface> is incompatible with the declared type object<stdClass> of property $view.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
218
219
        }
220
221 3
        $this->controller->postDispatch();
222 3
    }
223
224
    /**
225
     * @param Exception $e
226
     * @return string
227
     * @throws Exception
228
     */
229 1
    public function sinkingShip(Exception $e)
230 1
    {
231 1
        $controllerName = class_exists('\App\Controller\ErrorController') ? 'App\Controller\ErrorController' : 'Bone\Mvc\Controller';
232 1
        $this->controller = new $controllerName($this->request);
233 1
        $this->controller->setParam('error', $e);
234 1
        $this->controller->setServerEnvironment($this->getEnv());
235 1
        $reflection = new ReflectionClass(get_class($this->controller));
236 1
        $method = $reflection->getMethod('errorAction');
237 1
        $method->setAccessible(true);
238 1
        $method->invokeArgs($this->controller, []);
239 1
        $this->controller->error = $e;
240 1
        $this->config['controller'] = 'error';
241 1
        $this->config['action'] = 'error';
242 1
        $this->response = $this->response->withStatus(500);
243 1
        $this->distributeBooty();
244 1
    }
245
246
247
    /**
248
     * @param Controller $controller
249
     * @param string $content
250
     * @return string
251
     */
252 3
    private function templateCheck($controller, $content)
253 3
    {
254 3
        $responseBody = '';
255
        //check we be usin' th' templates in th' config
256 3
        $templates = Registry::ahoy()->get('templates');
257 3
        $template = $this->getTemplateName($templates);
258 3
        if ($template !== null) {
259 3
            $responseBody = $controller->getViewEngine()->render('layouts/' . $template, array('content' => $content));
260
        }
261 3
        return $responseBody;
262
    }
263
264
    /**
265
     * @param mixed $templates
266
     * @return string|null
267
     */
268 4
    private function getTemplateName($templates)
269 4
    {
270 4
        if (is_null($templates)) {
271 1
            return null;
272 4
        } elseif (is_array($templates)) {
273 1
            return (string) $templates[0];
274
        }
275 4
        return (string) $templates;
276
    }
277
278
    /**
279
     * Sets controller to error and action to not found
280
     * @return void
281
     */
282 2
    private function setNotFound()
283 2
    {
284 2
        $this->config['controller_name'] = class_exists('\App\Controller\ErrorController') ? '\App\Controller\ErrorController' : '\Bone\Mvc\Controller';
285 2
        $this->config['action_name'] = 'notFoundAction';
286 2
        $this->config['controller'] = 'error';
287 2
        $this->config['action'] = 'not-found';
288 2
        $this->controller = new $this->config['controller_name']($this->request);
289 2
        $this->controller->setServerEnvironment($this->getEnv());
290 2
        $this->response = $this->response->withStatus(404);
291 2
    }
292
293
    /**
294
     * @return Environment
295
     */
296 3
    private function getEnv(): Environment
297 3
    {
298 3
        return $this->env;
299
    }
300
}
301