Completed
Push — dev-master ( 86a9d6...915e4b )
by Derek Stephen
05:17
created

Dispatcher::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 20
ccs 14
cts 14
cp 1
rs 9.6
c 0
b 0
f 0
cc 1
nc 1
nop 3
crap 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\HttpHandlerRunner\Emitter\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
    {
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 3
    public function checkNavigator()
67
    {
68
        // can we find th' darned controller?
69 3
        if (!$this->checkControllerExists()) {
70 2
            $this->setNotFound();
71 2
            return;
72
        }
73
74
        // merge the feckin params
75 2
        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 2
        $this->controller = new $this->config['controller_name']($this->request);
82 2
        $this->controller->setServerEnvironment($this->getEnv());
83
84
        // where's the bloody action?
85 2
        if (!$this->checkActionExists()) {
86 1
            $this->setNotFound();
87
        }
88 2
        return null;
89
    }
90
91
92
    /**
93
     * @return bool
94
     */
95 4
    private function checkControllerExists()
96
    {
97 4
        return class_exists($this->config['controller_name']);
98
    }
99
100
101
    /**
102
     * @return bool
103
     */
104 3
    private function checkActionExists()
105
    {
106 3
        return method_exists($this->controller, $this->config['action_name']);
107
    }
108
109
110
    /**
111
     * @return string
112
     * @throws \Exception
113
     */
114 5
    private function distributeBooty()
115
    {
116
        /** @var \stdClass $viewVars */
117 5
        $viewVars = $this->controller->view;
118
119 5
        if ($viewVars instanceof ResponseInterface) {
120 1
            $this->response = $viewVars;
121 1
            $this->sendResponse();
122 1
            return;
123
        }
124
125 4
        $responseBody = $this->controller->getBody();
126
127 4
        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 1
            } catch (Exception $e) {
132 1
                throw $e;
133
            }
134
        }
135
136 4
        if ($this->controller->hasLayoutEnabled()) {
137 1
            $responseBody = $this->templateCheck($this->controller, $responseBody);
138
        }
139 4
        $this->prepareResponse($responseBody);
140 4
        $this->sendResponse();
141 4
    }
142
143
144
    /**
145
     * @throws Exception
146
     */
147 2
    public function fireCannons()
148
    {
149
        try {
150
            // Garr! Check the route with th' navigator
151 2
            $this->checkNavigator();
152
153
            // Fire cannons t' th' controller action
154 2
            $this->plunderEnemyShip();
155
156
            // Share the loot! send out th' response
157 2
            $this->distributeBooty();
158 1
        } catch (Exception $e) {
159 1
            $this->sinkingShip($e);
160
        }
161 2
    }
162
163
    /**
164
     * @param $booty
165
     */
166 4
    private function prepareResponse($booty)
167
    {
168 4
        $this->response->getBody()->write($booty);
169 4
        $this->setHeaders();
170 4
        $this->setStatusCode();
171 4
    }
172
173
174 5
    private function sendResponse()
175
    {
176 5
        $emitter = new SapiEmitter();
177 5
        $emitter->emit($this->response);
178 5
    }
179
180 5
    private function setHeaders()
181
    {
182 5
        foreach ($this->controller->getHeaders() as $key => $value) {
183 1
            $this->response = $this->response->withHeader($key, $value);
184
        }
185 5
    }
186
187 5
    private function setStatusCode()
188
    {
189 5
        $status = $this->controller->getStatusCode();
190 5
        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 5
    }
199
200
201 4
    private function plunderEnemyShip()
202
    {
203
        // run th' controller action
204 4
        $action = $this->config['action_name'];
205
206 4
        $this->controller->init();
207 4
        $vars = $this->controller->$action();
208
209 4
        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 3
        } 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 4
        $this->controller->postDispatch();
222 4
    }
223
224
    /**
225
     * @param Exception $e
226
     * @return string
227
     * @throws Exception
228
     */
229 2
    public function sinkingShip(Exception $e)
230
    {
231 2
        $controllerName = class_exists('\App\Controller\ErrorController') ? 'App\Controller\ErrorController' : 'Bone\Mvc\Controller';
232 2
        $this->controller = new $controllerName($this->request);
233 2
        $this->controller->setParam('error', $e);
234 2
        $this->controller->setServerEnvironment($this->getEnv());
235 2
        $reflection = new ReflectionClass(get_class($this->controller));
236 2
        $method = $reflection->getMethod('errorAction');
237 2
        $method->setAccessible(true);
238 2
        $method->invokeArgs($this->controller, []);
239 2
        $this->controller->error = $e;
240 2
        $this->config['controller'] = 'error';
241 2
        $this->config['action'] = 'error';
242 2
        $this->response = $this->response->withStatus(500);
243 2
        $this->distributeBooty();
244 2
    }
245
246
247
    /**
248
     * @param Controller $controller
249
     * @param string $content
250
     * @return string
251
     */
252 2
    private function templateCheck($controller, $content)
253
    {
254 2
        $responseBody = '';
255
        //check we be usin' th' templates in th' config
256 2
        $templates = Registry::ahoy()->get('templates');
257 2
        $template = $this->getTemplateName($templates);
258 2
        if ($template !== null) {
259 2
            $responseBody = $controller->getViewEngine()->render('layouts/' . $template, array('content' => $content));
260
        }
261 2
        return $responseBody;
262
    }
263
264
    /**
265
     * @param mixed $templates
266
     * @return string|null
267
     */
268 3
    private function getTemplateName($templates)
269
    {
270 3
        if (is_null($templates)) {
271 1
            return null;
272 3
        } elseif (is_array($templates)) {
273 1
            return (string) $templates[0];
274
        }
275 3
        return (string) $templates;
276
    }
277
278
    /**
279
     * Sets controller to error and action to not found
280
     * @return void
281
     */
282 3
    private function setNotFound()
283
    {
284 3
        $this->config['controller_name'] = class_exists('\App\Controller\ErrorController') ? '\App\Controller\ErrorController' : '\Bone\Mvc\Controller';
285 3
        $this->config['action_name'] = 'notFoundAction';
286 3
        $this->config['controller'] = 'error';
287 3
        $this->config['action'] = 'not-found';
288 3
        $this->controller = new $this->config['controller_name']($this->request);
289 3
        $this->controller->setServerEnvironment($this->getEnv());
290 3
        $this->response = $this->response->withStatus(404);
291 3
    }
292
293
    /**
294
     * @return Environment
295
     */
296 5
    private function getEnv(): Environment
297
    {
298 5
        return $this->env;
299
    }
300
}
301