Completed
Push — dev-master ( dd6200...6fdc6b )
by Derek Stephen
03:03 queued 01:56
created

Dispatcher::checkControllerExists()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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