RequestHandler::setControllersPath()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 1
dl 0
loc 6
rs 10
c 0
b 0
f 0
1
<?php /** @noinspection PhpIncludeInspection */
2
3
declare(strict_types=1);
4
5
namespace Tebe\Pvc\Middleware;
6
7
use Psr\Http\Message\ResponseInterface;
8
use Psr\Http\Message\ServerRequestInterface;
9
use Psr\Http\Server\RequestHandlerInterface;
10
use Tebe\HttpFactory\HttpFactory;
11
use Tebe\Pvc\Controller\BaseController;
12
use Tebe\Pvc\Controller\ErrorController;
13
use Tebe\Pvc\Exception\HttpException;
14
use Tebe\Pvc\Exception\SystemException;
15
use Tebe\Pvc\Helper\RequestHelper;
16
use Tebe\Pvc\View\View;
17
18
class RequestHandler implements RequestHandlerInterface
19
{
20
    /**
21
     * @var View
22
     */
23
    private $view;
24
25
    /**
26
     * @var ServerRequestInterface
27
     */
28
    private $request;
29
30
    /**
31
     * @var string
32
     */
33
    private $controllersPath;
34
35
    /**
36
     * RouterMiddleware constructor.
37
     * @param View $view
38
     * @param string $controllersPath
39
     * @throws SystemException
40
     */
41
    public function __construct(View $view, string $controllersPath)
42
    {
43
        $this->setView($view);
44
        $this->setControllersPath($controllersPath);
45
    }
46
47
    /**
48
     * Handle the request and return a response.
49
     * @param ServerRequestInterface $request
50
     * @return ResponseInterface
51
     * @throws HttpException
52
     * @throws SystemException
53
     */
54
    public function handle(ServerRequestInterface $request): ResponseInterface
55
    {
56
        $this->request = $request;
57
        $response = (new HttpFactory())->createResponse();
58
        $response = $response->withHeader('Content-Type', 'text/html');
59
        $pathInfo = RequestHelper::getPathInfo($request);
60
61
        try {
62
            $controller = $this->resolveController($pathInfo);
63
            $mixed = $this->executeAction($controller);
64
        } catch (\Throwable $t) {
65
            // handle errors with built-in error controller
66
            $controller = new ErrorController($this->view, $request, 'error/error');
67
            $controller->setError($t);
68
            $mixed = $this->executeAction($controller);
69
            $response = $response->withStatus($t->getCode());
70
        }
71
72
        if (is_array($mixed)) {
73
            $response = $response->withHeader('Content-Type', 'application/json');
74
            $response->getBody()->write(json_encode($mixed));
75
        } elseif (is_string($mixed)) {
76
            $response->getBody()->write($mixed);
77
        } else {
78
            throw new \Exception('Unsupported content type');
79
        }
80
81
        return $response;
82
    }
83
84
    /**
85
     * @param string $pathInfo
86
     * @return BaseController
87
     * @throws HttpException
88
     */
89
    private function resolveController(string $pathInfo): BaseController
90
    {
91
        [$controllerName] = explode('/', $pathInfo);
92
93
        $controllerPath = sprintf('%s/%sController.php', $this->controllersPath, ucfirst($controllerName));
94
95
        if (!is_file($controllerPath)) {
96
            throw HttpException::notFound($pathInfo);
97
        }
98
99
        require_once($controllerPath);
100
101
        // this seems to work...
102
        $declaredClasses = get_declared_classes();
103
        $controllerClass = array_pop($declaredClasses);
104
105
        $controller = new $controllerClass($this->view, $this->request, $pathInfo);
106
        return $controller;
107
    }
108
109
    /**
110
     * @param BaseController $controller
111
     * @return mixed
112
     * @throws HttpException
113
     * @throws SystemException
114
     */
115
    private function executeAction(BaseController $controller)
116
    {
117
        $actionMethod = $controller->getActionMethod();
118
119
        if (!method_exists($controller, $actionMethod)) {
120
            // throw not found error (404)
121
            throw HttpException::notFound($controller->getRoute());
122
        }
123
124
        $httpMethod = $this->request->getMethod();
125
        if (!$this->testForHttpMethod($controller, $httpMethod)) {
126
            // throw not found error (404)
127
            $allowedHttpMethods = $controller->getAllowedHttpMethods();
128
            throw HttpException::methodNotAllowed($controller->getRoute(), $httpMethod, $allowedHttpMethods);
129
        }
130
131
        try {
132
            $queryParams = $this->getHttpGetVars($controller, $actionMethod);
133
            $html = call_user_func_array([$controller, $actionMethod], $queryParams);
134
        } catch (\Throwable $t) {
135
            // catch all errors that happen within action handler
136
            throw SystemException::serverError($t->getMessage(), $t);
137
        }
138
139
        return $html;
140
    }
141
142
    /**
143
     * Get the HTTP GET vars which are requested by the method using method parameters (a kind of injecting the get
144
     * vars into the action method name).
145
     * @param BaseController $controller
146
     * @param string $methodName
147
     * @return array
148
     * @throws \ReflectionException
149
     */
150
    private function getHttpGetVars(BaseController $controller, string $methodName): array
151
    {
152
        $requestParams = [];
153
        $reflectionMethod = new \ReflectionMethod($controller, $methodName);
154
        $reflectionParameters = $reflectionMethod->getParameters();
155
        foreach ($reflectionParameters as $reflectionParameter) {
156
            $name = $reflectionParameter->getName();
157
            $queryParams = $this->request->getQueryParams();
158
            if (isset($queryParams[$name])) {
159
                $requestParams[$name] = $queryParams[$name];
160
            }
161
        }
162
163
        return $requestParams;
164
    }
165
166
    /**
167
     * @param BaseController $controller
168
     * @param string $httpMethod
169
     * @return bool
170
     */
171
    private function testForHttpMethod(BaseController $controller, string $httpMethod)
172
    {
173
        $httpMethod = strtoupper($httpMethod);
174
        if ($controller->getControllerName() == 'error') {
175
            return true;
176
        }
177
        $allowedHttpMethods = $controller->getAllowedHttpMethods();
178
        if (in_array($httpMethod, $allowedHttpMethods)) {
179
            return true;
180
        }
181
        return false;
182
    }
183
184
    /**
185
     * @param View $view
186
     */
187
    private function setView(View $view)
188
    {
189
        $this->view = $view;
190
    }
191
192
    /**
193
     * @param string $controllersPath
194
     * @throws SystemException
195
     */
196
    private function setControllersPath(string $controllersPath)
197
    {
198
        if (!is_dir($controllersPath)) {
199
            throw SystemException::directoryNotExist($controllersPath);
200
        }
201
        $this->controllersPath = $controllersPath;
202
    }
203
}
204