Completed
Push — dev-master ( 61e033...e8caf5 )
by Derek Stephen
37:26
created

Dispatcher   A

Complexity

Total Complexity 34

Size/Duplication

Total Lines 281
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 8

Test Coverage

Coverage 96.45%

Importance

Changes 0
Metric Value
wmc 34
lcom 1
cbo 8
dl 0
loc 281
ccs 136
cts 141
cp 0.9645
rs 9.68
c 0
b 0
f 0

16 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 20 1
A checkNavigator() 0 21 4
A checkControllerExists() 0 4 1
A checkActionExists() 0 4 1
A distributeBooty() 0 28 5
A fireCannons() 0 15 2
A prepareResponse() 0 6 1
A sendResponse() 0 5 1
A setHeaders() 0 6 2
A setStatusCode() 0 12 3
A plunderEnemyShip() 0 22 3
A sinkingShip() 0 16 2
A templateCheck() 0 11 2
A getTemplateName() 0 9 3
A setNotFound() 0 10 2
A getEnv() 0 4 1
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
    // Garrrr! An arrrray!
20
    private $config = array();
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
        // gaaarr! there be the controller!
75 1
        $this->controller = new $this->config['controller_name']($this->request);
76 1
        $this->controller->params = isset($this->config['params'])
77
            ? array_merge($this->controller->params, $this->config['params'])
78 1
            : $this->controller->params;
79 1
        $this->controller->setServerEnvironment($this->getEnv());
80
81
        // where's the bloody action?
82 1
        if (!$this->checkActionExists()) {
83 1
            $this->setNotFound();
84
        }
85 1
        return null;
86
    }
87
88
89
    /**
90
     * @return bool
91
     */
92 2
    private function checkControllerExists()
93 2
    {
94 2
        return class_exists($this->config['controller_name']);
95
    }
96
97
98
    /**
99
     * @return bool
100
     */
101 2
    private function checkActionExists()
102 2
    {
103 2
        return method_exists($this->controller, $this->config['action_name']);
104
    }
105
106
107
    /**
108
     * @return string
109
     * @throws \Exception
110
     */
111 4
    private function distributeBooty()
112 4
    {
113
        /** @var \stdClass $viewVars */
114 4
        $viewVars = $this->controller->view;
115
116 4
        if ($viewVars instanceof ResponseInterface) {
117 1
            $this->response = $viewVars;
118 1
            $this->sendResponse();
119 1
            return;
120
        }
121
122 3
        $responseBody = $this->controller->getBody();
123
124 3
        if ($this->controller->hasViewEnabled()) {
125 2
            $view = $this->config['controller'] . '/' . $this->config['action'];
126
            try {
127 2
                $responseBody = $this->controller->getViewEngine()->render($view, (array) $viewVars);
128
            } catch (Exception $e) {
129
                throw $e;
130
            }
131
        }
132
133 3
        if ($this->controller->hasLayoutEnabled()) {
134 2
            $responseBody = $this->templateCheck($this->controller, $responseBody);
135
        }
136 3
        $this->prepareResponse($responseBody);
137 3
        $this->sendResponse();
138 3
    }
139
140
141
    /**
142
     * @throws Exception
143
     */
144 2
    public function fireCannons()
145 2
    {
146
        try {
147
            // Garr! Check the route with th' navigator
148 1
            $this->checkNavigator();
149
150
            // Fire cannons t' th' controller action
151 1
            $this->plunderEnemyShip();
152
153
            // Share the loot! send out th' response
154 1
            $this->distributeBooty();
155
        } catch (Exception $e) {
156
            $this->sinkingShip($e);
157
        }
158 1
    }
159
160
    /**
161
     * @param $booty
162
     */
163 3
    private function prepareResponse($booty)
164 3
    {
165 3
        $this->response->getBody()->write($booty);
166 3
        $this->setHeaders();
167 3
        $this->setStatusCode();
168 3
    }
169
170
171 4
    private function sendResponse()
172 4
    {
173 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...
174 4
        $emitter->emit($this->response);
175 4
    }
176
177 4
    private function setHeaders()
178 4
    {
179 4
        foreach ($this->controller->getHeaders() as $key => $value) {
180 2
            $this->response = $this->response->withHeader($key, $value);
181
        }
182 4
    }
183
184 4
    private function setStatusCode()
185 4
    {
186 4
        $status = $this->controller->getStatusCode();
187 4
        if ($status != 200) {
188
            try {
189 1
                $this->response = $this->response->withStatus($status);
190 1
            } catch (Exception $e) {
191 1
                $this->response = $this->response->withStatus(500);
192
            }
193
194
        }
195 4
    }
196
197
198 3
    private function plunderEnemyShip()
199 3
    {
200
        // run th' controller action
201 3
        $action = $this->config['action_name'];
202
203 3
        $this->controller->init();
204 3
        $vars = $this->controller->$action();
205
206 3
        if (is_array($vars)) {
207
208 1
            $viewVars = (array) $this->controller->view;
209 1
            $view = (object) array_merge($vars, $viewVars);
210 1
            $this->controller->view = $view;
211
212 2
        } elseif ($vars instanceof ResponseInterface) {
213
214 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...
215
216
        }
217
218 3
        $this->controller->postDispatch();
219 3
    }
220
221
    /**
222
     * @param Exception $e
223
     * @return string
224
     * @throws Exception
225
     */
226 1
    public function sinkingShip(Exception $e)
227 1
    {
228 1
        $controllerName = class_exists('\App\Controller\ErrorController') ? 'App\Controller\ErrorController' : 'Bone\Mvc\Controller';
229 1
        $this->controller = new $controllerName($this->request);
230 1
        $this->controller->setParam('error', $e);
231 1
        $this->controller->setServerEnvironment($this->getEnv());
232 1
        $reflection = new ReflectionClass(get_class($this->controller));
233 1
        $method = $reflection->getMethod('errorAction');
234 1
        $method->setAccessible(true);
235 1
        $method->invokeArgs($this->controller, []);
236 1
        $this->controller->error = $e;
237 1
        $this->config['controller'] = 'error';
238 1
        $this->config['action'] = 'error';
239 1
        $this->response = $this->response->withStatus(500);
240 1
        $this->distributeBooty();
241 1
    }
242
243
244
    /**
245
     * @param Controller $controller
246
     * @param string $content
247
     * @return string
248
     */
249 3
    private function templateCheck($controller, $content)
250 3
    {
251 3
        $responseBody = '';
252
        //check we be usin' th' templates in th' config
253 3
        $templates = Registry::ahoy()->get('templates');
254 3
        $template = $this->getTemplateName($templates);
255 3
        if ($template !== null) {
256 3
            $responseBody = $controller->getViewEngine()->render('layouts/' . $template, array('content' => $content));
257
        }
258 3
        return $responseBody;
259
    }
260
261
    /**
262
     * @param mixed $templates
263
     * @return string|null
264
     */
265 4
    private function getTemplateName($templates)
266 4
    {
267 4
        if (is_null($templates)) {
268 1
            return null;
269 4
        } elseif (is_array($templates)) {
270 1
            return (string) $templates[0];
271
        }
272 4
        return (string) $templates;
273
    }
274
275
    /**
276
     * Sets controller to error and action to not found
277
     * @return void
278
     */
279 2
    private function setNotFound()
280 2
    {
281 2
        $this->config['controller_name'] = class_exists('\App\Controller\ErrorController') ? '\App\Controller\ErrorController' : '\Bone\Mvc\Controller';
282 2
        $this->config['action_name'] = 'notFoundAction';
283 2
        $this->config['controller'] = 'error';
284 2
        $this->config['action'] = 'not-found';
285 2
        $this->controller = new $this->config['controller_name']($this->request);
286 2
        $this->controller->setServerEnvironment($this->getEnv());
287 2
        $this->response = $this->response->withStatus(404);
288 2
    }
289
290
    /**
291
     * @return Environment
292
     */
293 3
    private function getEnv(): Environment
294 3
    {
295 3
        return $this->env;
296
    }
297
}
298