Passed
Pull Request — master (#736)
by Abdul Malik
07:17 queued 45s
created

CoreHandler::withVerbActions()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 6
ccs 4
cts 4
cp 1
crap 1
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
    private CoreInterface $core;
35
36
    private ScopeInterface $scope;
37
38
    private ?string $controller = null;
39
40
    private ?string $action = null;
41
42
    private ?bool $verbActions = null;
43
44
    private ?array $parameters = null;
45
46
    private ResponseFactoryInterface $responseFactory;
47
48 73
    public function __construct(
49
        CoreInterface $core,
50
        ScopeInterface $scope,
51
        ResponseFactoryInterface $responseFactory
52
    ) {
53 73
        $this->core = $core;
54 73
        $this->scope = $scope;
55 73
        $this->responseFactory = $responseFactory;
56
    }
57
58
    /**
59
     * @param string|null $action
60
     */
61 71
    public function withContext(string $controller, string $action, array $parameters): CoreHandler
62
    {
63 71
        $handler = clone $this;
64 71
        $handler->controller = $controller;
65 71
        $handler->action = $action;
66 71
        $handler->parameters = $parameters;
67
68 71
        return $handler;
69
    }
70
71
    /**
72
     * Disable or enable HTTP prefix for actions.
73
     */
74 71
    public function withVerbActions(bool $verbActions): CoreHandler
75
    {
76 71
        $handler = clone $this;
77 71
        $handler->verbActions = $verbActions;
78
79 71
        return $handler;
80
    }
81
82
    /**
83
     * @inheritdoc
84
     *
85
     * @psalm-suppress UnusedVariable
86
     * @throws \Throwable
87
     */
88 71
    public function handle(Request $request): Response
89
    {
90 71
        if ($this->controller === null) {
91 1
            throw new HandlerException('Controller and action pair is not set');
92
        }
93
94 70
        $outputLevel = ob_get_level();
95 70
        ob_start();
96
97 70
        $output = $result = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $result is dead and can be removed.
Loading history...
98
99 70
        $response = $this->responseFactory->createResponse(200);
100
        try {
101
            // run the core withing PSR-7 Request/Response scope
102 70
            $result = $this->scope->runScope(
103
                [
104 70
                    Request::class  => $request,
105
                    Response::class => $response,
106
                ],
107 70
                fn () => $this->core->callAction(
108 70
                    $this->controller,
109 70
                    $this->getAction($request),
110 70
                    $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

110
                    /** @scrutinizer ignore-type */ $this->parameters
Loading history...
111
                )
112
            );
113 11
        } catch (ControllerException $e) {
114 6
            ob_get_clean();
115 6
            throw $this->mapException($e);
116 5
        } catch (\Throwable $e) {
117 5
            ob_get_clean();
118 5
            throw $e;
119 59
        } finally {
120 70
            while (ob_get_level() > $outputLevel + 1) {
121 3
                $output = ob_get_clean() . $output;
122
            }
123
        }
124
125 59
        return $this->wrapResponse(
126
            $response,
127
            $result,
128 59
            ob_get_clean() . $output
129
        );
130
    }
131
132 70
    private function getAction(Request $request): string
133
    {
134 70
        if ($this->verbActions) {
135 1
            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

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