Completed
Push — dev-master ( d4c7bb...33aff8 )
by Derek Stephen
06:03
created

Dispatcher::getResponseBody()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 21
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 21
ccs 12
cts 12
cp 1
rs 9.0534
c 0
b 0
f 0
cc 4
eloc 12
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 11
    public function __construct(ServerRequestInterface $request, ResponseInterface $response)
37 11
    {
38 11
        $this->request = $request;
39 11
        $this->response = $response;
40
41 11
        $router = new Router($request);
42 11
        $router->parseRoute();
43
44
        // what controller be we talkin' about?
45 11
        $filtered = Filter::filterString($router->getController(), 'DashToCamelCase');
46 11
        $this->config['controller_name'] = '\App\Controller\\' . ucwords($filtered) . 'Controller';
47
48
        // whit be yer action ?
49 11
        $filtered = Filter::filterString($router->getAction(), 'DashToCamelCase');
50 11
        $this->config['action_name'] = $filtered . 'Action';
51 11
        $this->config['controller'] = $router->getController();
52 11
        $this->config['action'] = $router->getAction();
53 11
        $this->config['params'] = $router->getParams();
54 11
    }
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
        }
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 3
    private function distributeBooty()
104 3
    {
105
        /** @var \stdClass $viewVars */
106 3
        $viewVars = $this->controller->view;
107
108 3
        if ($viewVars instanceof ResponseInterface) {
109
            $this->response = $viewVars;
110
            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 1
            } catch (Exception $e) {
120 1
                throw $e;
121
            }
122
        }
123
124 3
        if ($this->controller->hasLayoutEnabled()) {
125 2
            $responseBody = $this->templateCheck($this->controller, $responseBody);
126
        }
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 1
            $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 3
    private function sendResponse()
163 3
    {
164 3
        $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 3
        $emitter->emit($this->response);
166 3
    }
167
168 3
    private function setHeaders()
169 3
    {
170 3
        foreach ($this->controller->getHeaders() as $key => $value) {
171 1
            $this->response = $this->response->withHeader($key, $value);
172
        }
173 3
    }
174
175 3
    private function setStatusCode()
176 3
    {
177 3
        $status = $this->controller->getStatusCode();
178 3
        if ($status != 200) {
179
            try {
180
                $this->response = $this->response->withStatus($status);
181
            } catch (Exception $e) {
182
                $this->response = $this->response->withStatus(500);
183
            }
184
185
        }
186 3
    }
187
188
189 2
    private function plunderEnemyShip()
190 2
    {
191
        // run th' controller action
192 2
        $action = $this->config['action_name'];
193 2
        $this->controller->init();
194 2
        $vars = $this->controller->$action();
195 2
        if (is_array($vars)) {
196 1
            $viewVars = (array) $this->controller->view;
197 1
            $view = (object) array_merge($vars, $viewVars);
198 1
            $this->controller->view = $view;
199 1
        } elseif ($vars instanceof ResponseInterface) {
200
            $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...
201
        }
202 2
        $this->controller->postDispatch();
203 2
    }
204
205
    /**
206
     * @param Exception $e
207
     * @return string
208
     * @throws Exception
209
     */
210 2
    public function sinkingShip(Exception $e)
211 2
    {
212 1
        $controllerName = class_exists('\App\Controller\ErrorController') ? 'App\Controller\ErrorController' : 'Bone\Mvc\Controller';
213 1
        $this->controller = new $controllerName($this->request);
214 1
        $this->controller->setParam('error', $e);
215 1
        $reflection = new ReflectionClass(get_class($this->controller));
216 1
        $method = $reflection->getMethod('errorAction');
217 1
        $method->setAccessible(true);
218 1
        $method->invokeArgs($this->controller, []);
219 1
        $this->controller->error = $e;
220 1
        $this->config['controller'] = 'error';
221 1
        $this->config['action'] = 'error';
222 1
        $this->response = $this->response->withStatus(500);
223 1
        $this->distributeBooty();
224 1
    }
225
226
227
    /**
228
     * @param Controller $controller
229
     * @param string $content
230
     * @return string
231
     */
232 3
    private function templateCheck($controller, $content)
233 3
    {
234 3
        $responseBody = '';
235
        //check we be usin' th' templates in th' config
236 3
        $templates = Registry::ahoy()->get('templates');
237 3
        $template = $this->getTemplateName($templates);
238 3
        if ($template !== null) {
239 3
            $responseBody = $controller->getViewEngine()->render('layouts/' . $template, array('content' => $content));
240
        }
241 3
        return $responseBody;
242
    }
243
244
    /**
245
     * @param mixed $templates
246
     * @return string|null
247
     */
248 4
    private function getTemplateName($templates)
249 4
    {
250 4
        if (is_null($templates)) {
251 1
            return null;
252 4
        } elseif (is_array($templates)) {
253 1
            return (string) $templates[0];
254
        }
255 4
        return (string) $templates;
256
    }
257
258
    /**
259
     * Sets controller to error and action to not found
260
     * @return void
261
     */
262 2
    private function setNotFound()
263 2
    {
264 2
        $this->config['controller_name'] = class_exists('\App\Controller\ErrorController') ? '\App\Controller\ErrorController' : '\Bone\Mvc\Controller';
265 2
        $this->config['action_name'] = 'notFoundAction';
266 2
        $this->config['controller'] = 'error';
267 2
        $this->config['action'] = 'not-found';
268 2
        $this->controller = new $this->config['controller_name']($this->request);
269 2
        $this->response = $this->response->withStatus(404);
270 2
    }
271
}
272