Passed
Push — master ( 9b63e5...2b9e2e )
by Gabor
03:08
created

WebApplication::getRequest()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 21
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 21
ccs 11
cts 11
cp 1
rs 9.3142
c 0
b 0
f 0
cc 2
eloc 11
nc 1
nop 0
crap 2
1
<?php
2
/**
3
 * WebHemi.
4
 *
5
 * PHP version 7.1
6
 *
7
 * @copyright 2012 - 2017 Gixx-web (http://www.gixx-web.com)
8
 * @license   https://opensource.org/licenses/MIT The MIT License (MIT)
9
 *
10
 * @link      http://www.gixx-web.com
11
 */
12
declare(strict_types = 1);
13
14
namespace WebHemi\Application\Web;
15
16
use Throwable;
17
use WebHemi\Adapter\Http\ResponseInterface;
18
use WebHemi\Adapter\Http\ServerRequestInterface;
19
use WebHemi\Adapter\Http\HttpAdapterInterface;
20
use WebHemi\Application\AbstractApplication;
21
use WebHemi\Application\EnvironmentManager;
22
use WebHemi\Application\PipelineManager;
23
use WebHemi\Application\SessionManager;
24
use WebHemi\Middleware\DispatcherMiddleware;
25
use WebHemi\Middleware\FinalMiddleware;
26
use WebHemi\Middleware\MiddlewareInterface;
27
28
/**
29
 * Class WebApplication.
30
 */
31
class WebApplication extends AbstractApplication
32
{
33
    /**
34
     * Starts the session.
35
     *
36
     * @return void
37
     *
38
     * @codeCoverageIgnore - not testing session (yet)
39
     */
40
    private function initSession() : void
41
    {
42
        if (defined('PHPUNIT_WEBHEMI_TESTSUITE')) {
43
            return;
44
        }
45
46
        /** @var SessionManager $sessionManager */
47
        $sessionManager = $this->getContainer()->get(SessionManager::class);
48
        /** @var EnvironmentManager $environmentManager */
49
        $environmentManager = $this->getContainer()->get(EnvironmentManager::class);
50
51
        $name = $environmentManager->getSelectedApplication();
52
        $timeOut = 3600;
53
        $path = $environmentManager->getSelectedApplicationUri();
54
        $domain = $environmentManager->getApplicationDomain();
55
        $secure = $environmentManager->isSecuredApplication();
56
        $httpOnly = true;
57
58
        $sessionManager->start($name, $timeOut, $path, $domain, $secure, $httpOnly);
59
    }
60
61
    /**
62
     * Runs the application. This is where the magic happens.
63
     * According tho the environment settings this must build up the middleware pipeline and execute it.
64
     *
65
     * a Pre-Routing Middleware can be; priority < 0:
66
     *  - LockCheck - check if the client IP is banned > S102|S403
67
     *  - Auth - if the user is not logged in, but there's a "Remember me" cookie, then logs in > S102
68
     *
69
     * Routing Middleware is fixed (RoutingMiddleware::class); priority = 0:
70
     *  - A middleware that routes the incoming Request and delegates to the matched middleware. > S102|S404|S405
71
     *    The RouteResult should be attached to the Request.
72
     *    If the Routing is not defined explicitly in the pipeline, then it will be injected with priority 0.
73
     *
74
     * a Post-Routing Middleware can be; priority between 0 and 100:
75
     *  - Acl - checks if the given route is available for the client. Also checks the auth > S102|S401|S403
76
     *  - CacheReader - checks if a suitable response body is cached. > S102|S200
77
     *
78
     * Dispatcher Middleware is fixed (DispatcherMiddleware::class); priority = 100:
79
     *  - A middleware which gets the corresponding Action middleware and applies it > S102
80
     *    If the Dispatcher is not defined explicitly in the pipeline, then it will be injected with priority 100.
81
     *    The Dispatcher should not set the response Status Code to 200 to let Post-Dispatchers to be called.
82
     *
83
     * a Post-Dispatch Middleware can be; priority > 100:
84
     *  - CacheWriter - writes response body into DataStorage (DB, File etc.) > S102
85
     *
86
     * Final Middleware is fixed (FinalMiddleware:class):
87
     *  - This middleware behaves a bit differently. It cannot be ordered, it's always the last called middleware:
88
     *    - when the middleware pipeline reached its end (typically when the Status Code is still 102)
89
     *    - when one item of the middleware pipeline returns with return response (status code is set to 200|40*|500)
90
     *    - when during the pipeline process an Exception is thrown.
91
     *
92
     * When the middleware pipeline is finished the application prints the header and the output.
93
     *
94
     * If a middleware other than the Routing, Dispatcher and Final Middleware has no priority set, it will be
95
     * considered to have priority = 50.
96
     *
97
     * @return void
98
     */
99 5
    public function run() : void
100
    {
101
        // Start session.
102 5
        $this->initSession();
103
104
        /** @var PipelineManager $pipelineManager */
105 5
        $pipelineManager = $this->getContainer()->get(PipelineManager::class);
106 5
        $request = $this->getRequest();
107 5
        $response = $this->getResponse();
108
109
        /** @var string $middlewareClass */
110 5
        $middlewareClass = $pipelineManager->start();
111
112 5
        while ($middlewareClass !== null
113 5
            && $response->getStatusCode() == ResponseInterface::STATUS_PROCESSING
114
        ) {
115 5
            $this->invokeMiddleware($middlewareClass, $request, $response);
116 5
            $middlewareClass = $pipelineManager->next();
117
        };
118
119
        // If there was no error, we mark as ready for output.
120 5
        if ($response->getStatusCode() == ResponseInterface::STATUS_PROCESSING) {
121 1
            $response = $response->withStatus(ResponseInterface::STATUS_OK);
122
        }
123
124
        /** @var FinalMiddleware $finalMiddleware */
125 5
        $finalMiddleware = $this->getContainer()->get(FinalMiddleware::class);
126
127
        // Send out headers and content.
128 5
        $finalMiddleware($request, $response);
129 5
    }
130
131
    /**
132
     * Gets the Request object.
133
     *
134
     * @return ServerRequestInterface
135
     */
136 5
    private function getRequest()
137
    {
138
        /** @var HttpAdapterInterface $httpAdapter */
139 5
        $httpAdapter = $this->getContainer()->get(HttpAdapterInterface::class);
140
        /** @var EnvironmentManager $environmentManager */
141 5
        $environmentManager = $this->getContainer()->get(EnvironmentManager::class);
142
143
        /** @var ServerRequestInterface $request */
144 5
        $request = $httpAdapter->getRequest()
145 5
            ->withAttribute(
146 5
                ServerRequestInterface::REQUEST_ATTR_DISPATCH_DATA,
147
                [
148 5
                    'selected_module' => $environmentManager->getSelectedModule(),
149 5
                    'application_domain' => 'http'.($environmentManager->isSecuredApplication() ? 's' : '')
150 5
                        .'://'
151 5
                        .$environmentManager->getApplicationDomain()
152
                ]
153
            );
154
155 5
        return $request;
156
    }
157
158
    /**
159
     * Gets the Response object.
160
     *
161
     * @return ResponseInterface
162
     */
163 5
    private function getResponse()
164
    {
165
        /** @var HttpAdapterInterface $httpAdapter */
166 5
        $httpAdapter = $this->getContainer()->get(HttpAdapterInterface::class);
167
168
        /** @var ResponseInterface $response */
169 5
        return $httpAdapter->getResponse();
170
    }
171
172
    /**
173
     * Instantiates and invokes a middleware
174
     *
175
     * @param string $middlewareClass
176
     * @param ServerRequestInterface $request
177
     * @param ResponseInterface $response
178
     */
179 5
    private function invokeMiddleware(
180
        string $middlewareClass,
181
        ServerRequestInterface&$request,
182
        ResponseInterface&$response
183
    ) : void {
184
        try {
185
            /** @var MiddlewareInterface $middleware */
186 5
            $middleware = $this->getContainer()->get($middlewareClass);
187 5
            $requestAttributes = $request->getAttributes();
188
189
            // As an extra step if an action middleware is resolved, it should be invoked by the dispatcher.
190 5
            if ($middleware instanceof DispatcherMiddleware
191 5
                && isset($requestAttributes[ServerRequestInterface::REQUEST_ATTR_RESOLVED_ACTION_CLASS])
192
            ) {
193
                /** @var MiddlewareInterface $actionMiddleware */
194 3
                $actionMiddleware = $this->getContainer()
195 3
                    ->get($requestAttributes[ServerRequestInterface::REQUEST_ATTR_RESOLVED_ACTION_CLASS]);
196 3
                $request = $request->withAttribute(
197 3
                    ServerRequestInterface::REQUEST_ATTR_ACTION_MIDDLEWARE,
198
                    $actionMiddleware
199
                );
200
            }
201
202 5
            $middleware($request, $response);
203 2
        } catch (Throwable $exception) {
0 ignored issues
show
Bug introduced by
The class Throwable does not exist. Is this class maybe located in a folder that is not analyzed, or in a newer version of your dependencies than listed in your composer.lock/composer.json?
Loading history...
204 2
            $code = ResponseInterface::STATUS_INTERNAL_SERVER_ERROR;
205
206 2
            if (in_array($exception->getCode(), [403, 404])) {
207 1
                $code = $exception->getCode();
208
            }
209
210 2
            $response = $response->withStatus($code);
211 2
            $request = $request->withAttribute(
212 2
                ServerRequestInterface::REQUEST_ATTR_MIDDLEWARE_EXCEPTION,
213
                $exception
214
            );
215
        }
216 5
    }
217
}
218