Completed
Pull Request — master (#5)
by Derek Stephen
02:02
created

Dispatcher::checkNavigator()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 18
ccs 11
cts 11
cp 1
rs 9.6666
c 0
b 0
f 0
cc 4
nc 5
nop 0
crap 4
1
<?php
2
3
namespace Bone\Mvc;
4
5
use Bone\Filter;
6
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...
7
use ReflectionClass;
8
use Psr\Http\Message\ServerRequestInterface;
9
use Psr\Http\Message\ResponseInterface;
10
use Zend\Diactoros\Response\SapiEmitter;
11
12
/**
13
 * Class Dispatcher
14
 * @package Bone\Mvc
15
 */
16
class Dispatcher
17
{
18
    // Garrrr! An arrrray!
19
    private $config = array();
20
21
    /** @var ServerRequestInterface $request */
22
    private $request;
23
24
    /** @var Controller */
25
    private $controller;
26
27
    /** @var ResponseInterface $response */
28
    private $response;
29
30
    /**
31
     * Dispatcher constructor.
32
     * @param ServerRequestInterface $request
33
     * @param ResponseInterface $response
34
     * @throws Filter\Exception
35
     */
36 15
    public function __construct(ServerRequestInterface $request, ResponseInterface $response)
37 15
    {
38 15
        $this->request = $request;
39 15
        $this->response = $response;
40
41 15
        $router = new Router($request);
42 15
        $router->parseRoute();
43
44
        // what controller be we talkin' about?
45 15
        $filtered = Filter::filterString($router->getController(), 'DashToCamelCase');
46 15
        $this->config['controller_name'] = '\App\Controller\\' . ucwords($filtered) . 'Controller';
47
48
        // whit be yer action ?
49 15
        $filtered = Filter::filterString($router->getAction(), 'DashToCamelCase');
50 15
        $this->config['action_name'] = $filtered . 'Action';
51 15
        $this->config['controller'] = $router->getController();
52 15
        $this->config['action'] = $router->getAction();
53 15
        $this->config['params'] = $router->getParams();
54 15
    }
55
56
57
    /**
58
     *  Gaaarrr! Check the Navigator be readin' the map!
59
     * @return null|void
60
     */
61 2
    public function checkNavigator()
62 2
    {
63
        // can we find th' darned controller?
64 1
        if (!$this->checkControllerExists()) {
65 1
            $this->setNotFound();
66 1
            return;
67
        }
68
69
        // gaaarr! there be the controller!
70 1
        $this->controller = new $this->config['controller_name']($this->request);
71 1
        $this->controller->params = isset($this->config['params']) ? $this->config['params'] : null;
72
73
        // where's the bloody action?
74 1
        if (!$this->checkActionExists()) {
75 1
            $this->setNotFound();
76 1
        }
77 1
        return null;
78
    }
79
80
81
    /**
82
     * @return bool
83
     */
84 2
    private function checkControllerExists()
85 2
    {
86 2
        return class_exists($this->config['controller_name']);
87
    }
88
89
90
    /**
91
     * @return bool
92
     */
93 2
    private function checkActionExists()
94 2
    {
95 2
        return method_exists($this->controller, $this->config['action_name']);
96
    }
97
98
99
    /**
100
     * @return string
101
     * @throws \Exception
102
     */
103 4
    private function distributeBooty()
104 4
    {
105
        /** @var \stdClass $viewVars */
106 4
        $viewVars = $this->controller->view;
107
108 4
        if ($viewVars instanceof ResponseInterface) {
109 1
            $this->response = $viewVars;
110 1
            return $this->sendResponse();
111
        }
112
113 3
        $responseBody = $this->controller->getBody();
114
115 3
        if ($this->controller->hasViewEnabled()) {
116 2
            $view = $this->config['controller'] . '/' . $this->config['action'];
117
            try {
118 2
                $responseBody = $this->controller->getViewEngine()->render($view, (array) $viewVars);
119 2
            } catch (Exception $e) {
120
                throw $e;
121
            }
122 2
        }
123
124 3
        if ($this->controller->hasLayoutEnabled()) {
125 2
            $responseBody = $this->templateCheck($this->controller, $responseBody);
126 2
        }
127 3
        $this->prepareResponse($responseBody);
128 3
        $this->sendResponse();
129 3
    }
130
131
132
    /**
133
     * @throws Exception
134
     */
135 2
    public function fireCannons()
136 2
    {
137
        try {
138
            // Garr! Check the route with th' navigator
139 1
            $this->checkNavigator();
140
141
            // Fire cannons t' th' controller action
142 1
            $this->plunderEnemyShip();
143
144
            // Share the loot! send out th' response
145 1
            $this->distributeBooty();
146 1
        } catch (Exception $e) {
147
            $this->sinkingShip($e);
148
        }
149 1
    }
150
151
    /**
152
     * @param $booty
153
     */
154 3
    private function prepareResponse($booty)
155 3
    {
156 3
        $this->response->getBody()->write($booty);
157 3
        $this->setHeaders();
158 3
        $this->setStatusCode();
159 3
    }
160
161
162 4
    private function sendResponse()
163 4
    {
164 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...
165 4
        $emitter->emit($this->response);
166 4
    }
167
168 4
    private function setHeaders()
169 4
    {
170 4
        foreach ($this->controller->getHeaders() as $key => $value) {
171 2
            $this->response = $this->response->withHeader($key, $value);
172 4
        }
173 4
    }
174
175 4
    private function setStatusCode()
176 4
    {
177 4
        $status = $this->controller->getStatusCode();
178 4
        if ($status != 200) {
179
            try {
180 1
                $this->response = $this->response->withStatus($status);
181 1
            } catch (Exception $e) {
182 1
                $this->response = $this->response->withStatus(500);
183
            }
184
185 1
        }
186 4
    }
187
188
189 3
    private function plunderEnemyShip()
190 3
    {
191
        // run th' controller action
192 3
        $action = $this->config['action_name'];
193
194 3
        $this->controller->init();
195 3
        $vars = $this->controller->$action();
196
197 3
        if (is_array($vars)) {
198
199 1
            $viewVars = (array) $this->controller->view;
200 1
            $view = (object) array_merge($vars, $viewVars);
201 1
            $this->controller->view = $view;
202
203 3
        } elseif ($vars instanceof ResponseInterface) {
204
205 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...
206
207 1
        }
208
209 3
        $this->controller->postDispatch();
210 3
    }
211
212
    /**
213
     * @param Exception $e
214
     * @return string
215
     * @throws Exception
216
     */
217 1
    public function sinkingShip(Exception $e)
218 1
    {
219 1
        $controllerName = class_exists('\App\Controller\ErrorController') ? 'App\Controller\ErrorController' : 'Bone\Mvc\Controller';
220 1
        $this->controller = new $controllerName($this->request);
221 1
        $this->controller->setParam('error', $e);
222 1
        $reflection = new ReflectionClass(get_class($this->controller));
223 1
        $method = $reflection->getMethod('errorAction');
224 1
        $method->setAccessible(true);
225 1
        $method->invokeArgs($this->controller, []);
226 1
        $this->controller->error = $e;
227 1
        $this->config['controller'] = 'error';
228 1
        $this->config['action'] = 'error';
229 1
        $this->response = $this->response->withStatus(500);
230 1
        $this->distributeBooty();
231 1
    }
232
233
234
    /**
235
     * @param Controller $controller
236
     * @param string $content
237
     * @return string
238
     */
239 3
    private function templateCheck($controller, $content)
240 3
    {
241 3
        $responseBody = '';
242
        //check we be usin' th' templates in th' config
243 3
        $templates = Registry::ahoy()->get('templates');
244 3
        $template = $this->getTemplateName($templates);
245 3
        if ($template !== null) {
246 3
            $responseBody = $controller->getViewEngine()->render('layouts/' . $template, array('content' => $content));
247 3
        }
248 3
        return $responseBody;
249
    }
250
251
    /**
252
     * @param mixed $templates
253
     * @return string|null
254
     */
255 4
    private function getTemplateName($templates)
256 4
    {
257 4
        if (is_null($templates)) {
258 1
            return null;
259 4
        } elseif (is_array($templates)) {
260 1
            return (string) $templates[0];
261
        }
262 4
        return (string) $templates;
263
    }
264
265
    /**
266
     * Sets controller to error and action to not found
267
     * @return void
268
     */
269 2
    private function setNotFound()
270 2
    {
271 2
        $this->config['controller_name'] = class_exists('\App\Controller\ErrorController') ? '\App\Controller\ErrorController' : '\Bone\Mvc\Controller';
272 2
        $this->config['action_name'] = 'notFoundAction';
273 2
        $this->config['controller'] = 'error';
274 2
        $this->config['action'] = 'not-found';
275 2
        $this->controller = new $this->config['controller_name']($this->request);
276 2
        $this->response = $this->response->withStatus(404);
277 2
    }
278
}
279