Passed
Push — master ( b202e3...14e011 )
by Divine Niiquaye
02:35 queued 13s
created

RouteHandler::wrapResponse()   B

Complexity

Conditions 7
Paths 4

Size

Total Lines 20
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 7.049

Importance

Changes 0
Metric Value
cc 7
eloc 9
nc 4
nop 3
dl 0
loc 20
ccs 9
cts 10
cp 0.9
crap 7.049
rs 8.8333
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of Flight Routing.
7
 *
8
 * PHP version 7.1 and above required
9
 *
10
 * @author    Divine Niiquaye Ibok <[email protected]>
11
 * @copyright 2019 Biurad Group (https://biurad.com/)
12
 * @license   https://opensource.org/licenses/BSD-3-Clause License
13
 *
14
 * For the full copyright and license information, please view the LICENSE
15
 * file that was distributed with this source code.
16
 */
17
18
namespace Flight\Routing;
19
20
use JsonSerializable;
21
use Psr\Http\Message\ResponseInterface;
22
use Psr\Http\Message\ServerRequestInterface;
23
use Psr\Http\Message\StreamInterface;
24
use Psr\Http\Server\RequestHandlerInterface;
25
use stdClass;
26
use Throwable;
27
28
/**
29
 * Provides ability to invoke any handler and write it's response into ResponseInterface.
30
 *
31
 * @author Divine Niiquaye Ibok <[email protected]>
32
 */
33
final class RouteHandler implements RequestHandlerInterface
34
{
35
    public const CONTENT_TYPE = 'Content-Type';
36
37
    /** @var callable */
38
    private $callable;
39
40
    /** @var ResponseInterface */
41
    private $responseFactory;
42
43
    /**
44
     * @param callable          $callable
45
     * @param ResponseInterface $responseFactory
46
     */
47 7
    public function __construct(callable $callable, ResponseInterface $responseFactory)
48
    {
49 7
        $this->callable        = $callable;
50 7
        $this->responseFactory = $responseFactory;
51 7
    }
52
53
    /**
54
     * {@inheritdoc}
55
     *
56
     * @throws Throwable
57
     */
58 6
    public function handle(ServerRequestInterface $request): ResponseInterface
59
    {
60 6
        $outputLevel = \ob_get_level();
61 6
        \ob_start();
62
63 6
        $output = '';
64 6
        $result = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $result is dead and can be removed.
Loading history...
65
66 6
        $response = $this->responseFactory
67 6
            ->withHeader(self::CONTENT_TYPE, 'text/html; charset=utf-8');
68
69
        try {
70 6
            $result = ($this->callable)($request, $response);
71 1
        } catch (Throwable $e) {
72 1
            \ob_get_clean();
73
74 1
            throw $e;
75 5
        } finally {
76 6
            while (\ob_get_level() > $outputLevel + 1) {
77
                $output = \ob_get_clean() . $output;
78
            }
79
        }
80
81 5
        return $this->wrapResponse($response, $result, \ob_get_clean() . $output);
82
    }
83
84
    /**
85
     * Convert endpoint result into valid PSR 7 response.
86
     * content-type fallback is "text/html; charset=utf-8".
87
     *
88
     * @param ResponseInterface $response initial pipeline response
89
     * @param mixed             $result   generated endpoint output
90
     * @param string            $output   buffer output
91
     *
92
     * @return ResponseInterface
93
     */
94 5
    private function wrapResponse(ResponseInterface $response, $result = null, string $output = ''): ResponseInterface
95
    {
96
        // Always return the response...
97 5
        if ($result instanceof ResponseInterface) {
98 1
            if (!empty($output) && $result->getBody()->isWritable()) {
99
                $result->getBody()->write($output);
100
            }
101
102 1
            return $result;
103
        }
104
105 4
        if (\is_array($result) || $result instanceof JsonSerializable || $result instanceof stdClass) {
106 1
            $result = \json_encode($result);
107
        }
108
109 4
        $response->getBody()->write((string) $result);
110 4
        $response->getBody()->write($output);
111
112
        //Always detect response anf glue buffered output
113 4
        return $this->detectResponse($response);
114
    }
115
116 4
    private function detectResponse(ResponseInterface $response): ResponseInterface
117
    {
118 4
        if ($this->isJson($response->getBody())) {
119 1
            return $response->withHeader(self::CONTENT_TYPE, 'application/json');
120
        }
121
122 3
        if ($this->isXml($response->getBody())) {
123 1
            return $response->withHeader(self::CONTENT_TYPE, 'application/xml; charset=utf-8');
124
        }
125
126
        // Set content-type to plain text if string doesn't contain </html> tag.
127 2
        if (0 === \preg_match('/(.*)(<\/html[^>]*>)/i', (string) $response->getBody())) {
128 1
            return $response->withHeader(self::CONTENT_TYPE, 'text/plain; charset=utf-8');
129
        }
130
131 1
        return $response;
132
    }
133
134 4
    private function isJson(StreamInterface $stream): bool
135
    {
136 4
        $stream->rewind();
137
138 4
        \json_decode($stream->getContents(), true);
139
140 4
        return \JSON_ERROR_NONE === \json_last_error();
141
    }
142
143 3
    private function isXml(StreamInterface $stream): bool
144
    {
145 3
        $stream->rewind();
146
147 3
        $previousValue = \libxml_use_internal_errors(true);
148 3
        $isXml         = \simplexml_load_string($contents = $stream->getContents());
149 3
        \libxml_use_internal_errors($previousValue);
150
151 3
        return false !== $isXml && false !== \strpos($contents, '<?xml');
152
    }
153
}
154