Passed
Push — master ( 035a69...ef3bdc )
by Kirill
03:22
created

CoreHandler::withVerbActions()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 3
c 1
b 0
f 0
dl 0
loc 6
rs 10
cc 1
nc 1
nop 1
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\Traits\JsonTrait;
27
use Spiral\Router\Exception\HandlerException;
28
29
final class CoreHandler implements RequestHandlerInterface
30
{
31
    use JsonTrait;
32
33
    /** @var CoreInterface */
34
    private $core;
35
36
    /** @var ScopeInterface */
37
    private $scope;
38
39
    /** @var string|null */
40
    private $controller;
41
42
    /** @var string|null */
43
    private $action;
44
45
    /** @var bool */
46
    private $verbActions;
47
48
    /** @var array|null */
49
    private $parameters;
50
51
    /** @var ResponseFactoryInterface */
52
    private $responseFactory;
53
54
    /**
55
     * @param CoreInterface            $core
56
     * @param ScopeInterface           $scope
57
     * @param ResponseFactoryInterface $responseFactory
58
     */
59
    public function __construct(
60
        CoreInterface $core,
61
        ScopeInterface $scope,
62
        ResponseFactoryInterface $responseFactory
63
    ) {
64
        $this->core = $core;
65
        $this->scope = $scope;
66
        $this->responseFactory = $responseFactory;
67
    }
68
69
    /**
70
     * @param string      $controller
71
     * @param string|null $action
72
     * @param array       $parameters
73
     * @return CoreHandler
74
     */
75
    public function withContext(string $controller, string $action, array $parameters): CoreHandler
76
    {
77
        $handler = clone $this;
78
        $handler->controller = $controller;
79
        $handler->action = $action;
80
        $handler->parameters = $parameters;
81
82
        return $handler;
83
    }
84
85
    /**
86
     * Disable or enable HTTP prefix for actions.
87
     *
88
     * @param bool $verbActions
89
     * @return CoreHandler
90
     */
91
    public function withVerbActions(bool $verbActions): CoreHandler
92
    {
93
        $handler = clone $this;
94
        $handler->verbActions = $verbActions;
95
96
        return $handler;
97
    }
98
99
    /**
100
     * @inheritdoc
101
     *
102
     * @throws \Throwable
103
     */
104
    public function handle(Request $request): Response
105
    {
106
        if ($this->controller === null) {
107
            throw new HandlerException('Controller and action pair is not set');
108
        }
109
110
        $outputLevel = ob_get_level();
111
        ob_start();
112
113
        $output = $result = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $result is dead and can be removed.
Loading history...
114
115
        $response = $this->responseFactory->createResponse(200);
116
        try {
117
            // run the core withing PSR-7 Request/Response scope
118
            $result = $this->scope->runScope(
119
                [
120
                    Request::class  => $request,
121
                    Response::class => $response
122
                ],
123
                function () use ($request) {
124
                    return $this->core->callAction(
125
                        $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

125
                        /** @scrutinizer ignore-type */ $this->controller,
Loading history...
126
                        $this->getAction($request),
127
                        $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

127
                        /** @scrutinizer ignore-type */ $this->parameters
Loading history...
128
                    );
129
                }
130
            );
131
        } catch (ControllerException $e) {
132
            ob_get_clean();
133
            throw $this->mapException($e);
134
        } catch (\Throwable $e) {
135
            ob_get_clean();
136
            throw $e;
137
        } finally {
138
            while (ob_get_level() > $outputLevel + 1) {
139
                $output = ob_get_clean() . $output;
140
            }
141
        }
142
143
        return $this->wrapResponse(
144
            $response,
145
            $result,
146
            ob_get_clean() . $output
147
        );
148
    }
149
150
    /**
151
     * @param Request $request
152
     * @return string
153
     */
154
    private function getAction(Request $request): string
155
    {
156
        if ($this->verbActions) {
157
            return strtolower($request->getMethod()) . ucfirst($this->action);
158
        }
159
160
        return $this->action;
161
    }
162
163
    /**
164
     * Convert endpoint result into valid response.
165
     *
166
     * @param Response $response Initial pipeline response.
167
     * @param mixed    $result   Generated endpoint output.
168
     * @param string   $output   Buffer output.
169
     * @return Response
170
     */
171
    private function wrapResponse(Response $response, $result = null, string $output = ''): Response
172
    {
173
        if ($result instanceof Response) {
174
            if ($output !== '' && $result->getBody()->isWritable()) {
175
                $result->getBody()->write($output);
176
            }
177
178
            return $result;
179
        }
180
181
        if (is_array($result) || $result instanceof \JsonSerializable) {
182
            $response = $this->writeJson($response, $result);
183
        } else {
184
            $response->getBody()->write((string)$result);
185
        }
186
187
        //Always glue buffered output
188
        $response->getBody()->write($output);
189
190
        return $response;
191
    }
192
193
    /**
194
     * Converts core specific ControllerException into HTTP ClientException.
195
     *
196
     * @param ControllerException $exception
197
     * @return ClientException
198
     */
199
    private function mapException(ControllerException $exception): ClientException
200
    {
201
        switch ($exception->getCode()) {
202
            case ControllerException::BAD_ACTION:
203
            case ControllerException::NOT_FOUND:
204
                return new NotFoundException($exception->getMessage());
205
            case ControllerException::FORBIDDEN:
206
                return new ForbiddenException($exception->getMessage());
207
            case ControllerException::UNAUTHORIZED:
208
                return new UnauthorizedException($exception->getMessage());
209
            default:
210
                return new BadRequestException($exception->getMessage());
211
        }
212
    }
213
}
214