Passed
Push — master ( 035240...f758bd )
by Gabor
03:14
created

FinalMiddleware::sendOutput()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 16
ccs 0
cts 0
cp 0
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 10
nc 2
nop 2
crap 6
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\Middleware;
15
16
use RuntimeException;
17
use WebHemi\Adapter\Auth\AuthAdapterInterface;
18
use WebHemi\Adapter\Http\ResponseInterface;
19
use WebHemi\Adapter\Http\ServerRequestInterface;
20
use WebHemi\Adapter\Log\LogAdapterInterface;
21
use WebHemi\Adapter\Renderer\RendererAdapterInterface;
22
use WebHemi\Application\EnvironmentManager;
23
use WebHemi\Data\Entity\User\UserEntity;
24
25
/**
26
 * Class FinalMiddleware.
27
 */
28
class FinalMiddleware implements MiddlewareInterface
29
{
30
    /** @var RendererAdapterInterface */
31
    private $templateRenderer;
32
    /** @var AuthAdapterInterface */
33
    private $authAdapter;
34
    /** @var EnvironmentManager */
35
    private $environmentManager;
36
    /** @var LogAdapterInterface */
37
    private $logAdapter;
38
39
    /**
40
     * FinalMiddleware constructor.
41
     *
42
     * @param RendererAdapterInterface $templateRenderer
43
     * @param AuthAdapterInterface     $authAdapter
44
     * @param EnvironmentManager       $environmentManager
45
     * @param LogAdapterInterface      $logAdapter
46
     */
47 8
    public function __construct(
48
        RendererAdapterInterface $templateRenderer,
49
        AuthAdapterInterface $authAdapter,
50
        EnvironmentManager $environmentManager,
51
        LogAdapterInterface $logAdapter
52
    ) {
53 8
        $this->templateRenderer = $templateRenderer;
54 8
        $this->authAdapter = $authAdapter;
55 8
        $this->environmentManager = $environmentManager;
56 8
        $this->logAdapter = $logAdapter;
57 8
    }
58
59
    /**
60
     * Sends out the headers and prints the response body to the output.
61
     *
62
     * @param ServerRequestInterface $request
63
     * @param ResponseInterface      $response
64
     * @return void
65
     */
66 3
    public function __invoke(ServerRequestInterface&$request, ResponseInterface&$response) : void
67
    {
68
        // @codeCoverageIgnoreStart
69
        if (!defined('PHPUNIT_WEBHEMI_TESTSUITE') && headers_sent()) {
70
            throw new RuntimeException('Unable to emit response; headers already sent', 1000);
71
        }
72
        // @codeCoverageIgnoreEnd
73
74
        // Handle errors here.
75 3
        if (!in_array($response->getStatusCode(), [ResponseInterface::STATUS_OK, ResponseInterface::STATUS_REDIRECT])) {
76 2
            $errorTemplate = 'error-'.$response->getStatusCode();
77 2
            $exception = $request->getAttribute(ServerRequestInterface::REQUEST_ATTR_MIDDLEWARE_EXCEPTION);
78
            /** @var array $data */
79 2
            $templateData = $request->getAttribute(ServerRequestInterface::REQUEST_ATTR_DISPATCH_DATA);
80 2
            $templateData['exception'] = $exception;
81
82 2
            if ($request->isXmlHttpRequest()) {
83 1
                $request = $request->withAttribute(ServerRequestInterface::REQUEST_ATTR_DISPATCH_DATA, $templateData);
84
            } else {
85 1
                $body = $this->templateRenderer->render($errorTemplate, $templateData);
86 1
                $response = $response->withBody($body);
87
            }
88
89 2
            if ('admin' == $this->environmentManager->getSelectedModule()) {
90 2
                $identity = 'Unauthenticated user';
91
92 2
                if ($this->authAdapter->hasIdentity()) {
93
                    /** @var UserEntity $userEntity */
94 2
                    $userEntity = $this->authAdapter->getIdentity();
95 2
                    $identity = $userEntity->getEmail();
96
                }
97
98
                $logData = [
99 2
                    'User' => $identity,
100 2
                    'IP' => $this->environmentManager->getClientIp(),
101 2
                    'RequestUri' => $request->getUri()->getPath().'?'.$request->getUri()->getQuery(),
102 2
                    'RequestMethod' => $request->getMethod(),
103 2
                    'Error' => $response->getStatusCode().' '.$response->getReasonPhrase(),
104 2
                    'Exception' => $exception,
105 2
                    'Parameters' => $request->getParsedBody()
106
                ];
107 2
                $this->logAdapter->log('error', json_encode($logData));
108
            }
109
        }
110
111
        // Skip sending output when PHP Unit is running.
112
        // @codeCoverageIgnoreStart
113
        if (!defined('PHPUNIT_WEBHEMI_TESTSUITE')) {
114
            $this->sendOutput($request, $response);
115
        }
116
        // @codeCoverageIgnoreEnd
117 3
    }
118
119
    /**
120
     * Inject the Content-Length header if is not already present.
121
     *
122
     * NOTE: if there will be chunk content displayed, check if the response getSize counts the real size correctly
123
     *
124
     * @param ResponseInterface $response
125
     * @return void
126
     *
127
     * @codeCoverageIgnore - no putput for tests.
128
     */
129
    private function injectContentLength(ResponseInterface&$response) : void
130
    {
131
        if (!$response->hasHeader('Content-Length') && !is_null($response->getBody()->getSize())) {
132
            $response = $response->withHeader('Content-Length', (string) $response->getBody()->getSize());
133
        }
134
    }
135
136
    /**
137
     * Filter a header name to word case.
138
     *
139
     * @param string $headerName
140
     * @return string
141
     */
142 5
    private function filterHeaderName(string $headerName) : string
143
    {
144 5
        $filtered = str_replace('-', ' ', $headerName);
145 5
        $filtered = ucwords($filtered);
146 5
        return str_replace(' ', '-', $filtered);
147
    }
148
149
    /**
150
     * Sends the HTTP header.
151
     *
152
     * @param ResponseInterface $response
153
     * @return void
154
     *
155
     * @codeCoverageIgnore - vendor and core function calls
156
     */
157
    private function sendHttpHeader(ResponseInterface $response) : void
158
    {
159
        $reasonPhrase = $response->getReasonPhrase();
160
        header(sprintf(
161
            'HTTP/%s %d%s',
162
            $response->getProtocolVersion(),
163
            $response->getStatusCode(),
164
            ($reasonPhrase ? ' '.$reasonPhrase : '')
165
        ));
166
    }
167
168
    /**
169
     * Sends out output headers.
170
     *
171
     * @param array $headers
172
     * @return void
173
     *
174
     * @codeCoverageIgnore - vendor and core function calls in loop
175
     */
176
    private function sendOutputHeaders(array $headers) : void
177
    {
178
        foreach ($headers as $headerName => $values) {
179
            $name  = $this->filterHeaderName($headerName);
180
            $first = true;
181
            foreach ($values as $value) {
182
                header(sprintf('%s: %s', $name, $value), $first);
183
                $first = false;
184
            }
185
        }
186
    }
187
188
    /**
189
     * Sends output according to the request.
190
     *
191
     * @param ServerRequestInterface $request
192
     * @param ResponseInterface $response
193
     * @return void
194
     *
195
     * @codeCoverageIgnore - no output for tests
196
     */
197
    private function sendOutput(ServerRequestInterface $request, ResponseInterface $response) : void
198
    {
199
        if ($request->isXmlHttpRequest()) {
200
            $templateData = $request->getAttribute(ServerRequestInterface::REQUEST_ATTR_DISPATCH_DATA);
201
202
            $output = json_encode($templateData);
203
        } else {
204
            $this->injectContentLength($response);
205
            $output = $response->getBody();
206
        }
207
208
        $this->sendHttpHeader($response);
209
        $this->sendOutputHeaders($response->getHeaders());
210
211
        echo $output;
212
    }
213
}
214