Completed
Push — dev-master ( 61317d...8c136d )
by Derek Stephen
29:12 queued 26:15
created

Dispatcher::checkControllerExists()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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