Passed
Push — master ( 3d26ac...b47a92 )
by Aleksei
05:49
created

CoreHandler::withContext()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 5
nc 1
nop 3
dl 0
loc 8
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * Spiral Framework.
5
 *
6
 * @license   MIT
7
 * @author    Anton Titov (Wolfy-J)
8
 */
9
10
declare(strict_types=1);
11
12
namespace Spiral\Router;
13
14
use Psr\Http\Message\ResponseFactoryInterface;
15
use Psr\Http\Message\ResponseInterface as Response;
16
use Psr\Http\Message\ServerRequestInterface as Request;
17
use Psr\Http\Server\RequestHandlerInterface;
18
use Spiral\Core\CoreInterface;
19
use Spiral\Core\Exception\ControllerException;
20
use Spiral\Core\ScopeInterface;
21
use Spiral\Http\Exception\ClientException;
22
use Spiral\Http\Exception\ClientException\BadRequestException;
23
use Spiral\Http\Exception\ClientException\ForbiddenException;
24
use Spiral\Http\Exception\ClientException\NotFoundException;
25
use Spiral\Http\Exception\ClientException\UnauthorizedException;
26
use Spiral\Http\Stream\GeneratorStream;
27
use Spiral\Http\Traits\JsonTrait;
28
use Spiral\Router\Exception\HandlerException;
29
30
final class CoreHandler implements RequestHandlerInterface
31
{
32
    use JsonTrait;
33
34
    /** @var CoreInterface */
35
    private $core;
36
37
    /** @var ScopeInterface */
38
    private $scope;
39
40
    /** @var string|null */
41
    private $controller;
42
43
    /** @var string|null */
44
    private $action;
45
46
    /** @var bool */
47
    private $verbActions;
48
49
    /** @var array|null */
50
    private $parameters;
51
52
    /** @var ResponseFactoryInterface */
53
    private $responseFactory;
54
55
    public function __construct(
56
        CoreInterface $core,
57
        ScopeInterface $scope,
58
        ResponseFactoryInterface $responseFactory
59
    ) {
60
        $this->core = $core;
61
        $this->scope = $scope;
62
        $this->responseFactory = $responseFactory;
63
    }
64
65
    /**
66
     * @param string|null $action
67
     */
68
    public function withContext(string $controller, string $action, array $parameters): CoreHandler
69
    {
70
        $handler = clone $this;
71
        $handler->controller = $controller;
72
        $handler->action = $action;
73
        $handler->parameters = $parameters;
74
75
        return $handler;
76
    }
77
78
    /**
79
     * Disable or enable HTTP prefix for actions.
80
     */
81
    public function withVerbActions(bool $verbActions): CoreHandler
82
    {
83
        $handler = clone $this;
84
        $handler->verbActions = $verbActions;
85
86
        return $handler;
87
    }
88
89
    /**
90
     * @inheritdoc
91
     *
92
     * @psalm-suppress UnusedVariable
93
     * @throws \Throwable
94
     */
95
    public function handle(Request $request): Response
96
    {
97
        if ($this->controller === null) {
98
            throw new HandlerException('Controller and action pair is not set');
99
        }
100
101
        $outputLevel = ob_get_level();
102
        ob_start();
103
104
        $output = $result = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $result is dead and can be removed.
Loading history...
105
106
        $response = $this->responseFactory->createResponse(200);
107
        try {
108
            // run the core withing PSR-7 Request/Response scope
109
            $result = $this->scope->runScope(
110
                [
111
                    Request::class  => $request,
112
                    Response::class => $response,
113
                ],
114
                function () use ($request) {
115
                    return $this->core->callAction(
116
                        $this->controller,
0 ignored issues
show
Bug introduced by
It seems like $this->controller can also be of type null; however, parameter $controller of Spiral\Core\CoreInterface::callAction() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

116
                        /** @scrutinizer ignore-type */ $this->controller,
Loading history...
117
                        $this->getAction($request),
118
                        $this->parameters
0 ignored issues
show
Bug introduced by
It seems like $this->parameters can also be of type null; however, parameter $parameters of Spiral\Core\CoreInterface::callAction() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

118
                        /** @scrutinizer ignore-type */ $this->parameters
Loading history...
119
                    );
120
                }
121
            );
122
        } catch (ControllerException $e) {
123
            ob_get_clean();
124
            throw $this->mapException($e);
125
        } catch (\Throwable $e) {
126
            ob_get_clean();
127
            throw $e;
128
        } finally {
129
            while (ob_get_level() > $outputLevel + 1) {
130
                $output = ob_get_clean() . $output;
131
            }
132
        }
133
134
        return $this->wrapResponse(
135
            $response,
136
            $result,
137
            ob_get_clean() . $output
138
        );
139
    }
140
141
    private function getAction(Request $request): string
142
    {
143
        if ($this->verbActions) {
144
            return strtolower($request->getMethod()) . ucfirst($this->action);
0 ignored issues
show
Bug introduced by
It seems like $this->action can also be of type null; however, parameter $string of ucfirst() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

144
            return strtolower($request->getMethod()) . ucfirst(/** @scrutinizer ignore-type */ $this->action);
Loading history...
145
        }
146
147
        return $this->action;
148
    }
149
150
    /**
151
     * Convert endpoint result into valid response.
152
     *
153
     * @param Response $response Initial pipeline response.
154
     * @param mixed    $result   Generated endpoint output.
155
     * @param string   $output   Buffer output.
156
     */
157
    private function wrapResponse(Response $response, $result = null, string $output = ''): Response
158
    {
159
        if ($result instanceof Response) {
160
            if ($output !== '' && $result->getBody()->isWritable()) {
161
                $result->getBody()->write($output);
162
            }
163
164
            return $result;
165
        }
166
167
        if ($result instanceof \Generator) {
168
            return $response->withBody(new GeneratorStream($result));
169
        }
170
171
        if (\is_array($result) || $result instanceof \JsonSerializable) {
172
            $response = $this->writeJson($response, $result);
173
        } else {
174
            $response->getBody()->write((string)$result);
175
        }
176
177
        //Always glue buffered output
178
        $response->getBody()->write($output);
179
180
        return $response;
181
    }
182
183
    /**
184
     * Converts core specific ControllerException into HTTP ClientException.
185
     */
186
    private function mapException(ControllerException $exception): ClientException
187
    {
188
        switch ($exception->getCode()) {
189
            case ControllerException::BAD_ACTION:
190
            case ControllerException::NOT_FOUND:
191
                return new NotFoundException($exception->getMessage());
192
            case ControllerException::FORBIDDEN:
193
                return new ForbiddenException($exception->getMessage());
194
            case ControllerException::UNAUTHORIZED:
195
                return new UnauthorizedException($exception->getMessage());
196
            default:
197
                return new BadRequestException($exception->getMessage());
198
        }
199
    }
200
}
201