RpcRequests::transformResponseToRpc()   A
last analyzed

Complexity

Conditions 4
Paths 4

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 4

Importance

Changes 0
Metric Value
eloc 5
c 0
b 0
f 0
dl 0
loc 10
ccs 6
cts 6
cp 1
rs 10
cc 4
nc 4
nop 1
crap 4
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Boomdraw\RpcCore\Concerns;
6
7
use Boomdraw\RpcCore\Exceptions\MethodNotFoundRpcException;
8
use Boomdraw\RpcCore\Handler;
9
use Boomdraw\RpcCore\Request as RpcRequest;
10
use Boomdraw\RpcCore\Responses\RpcBaseResponse;
11
use Boomdraw\RpcCore\Responses\RpcEmptyResponse;
12
use Boomdraw\RpcCore\Responses\RpcResponse;
13
use Closure;
14
use Illuminate\Contracts\Support\Responsable;
15
use Illuminate\Http\Request;
16
use Illuminate\Http\Response;
17
use Illuminate\Support\Str;
18
use Psr\Http\Message\ResponseInterface as PsrResponseInterface;
19
use SplFileInfo;
20
use Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory;
21
use Symfony\Component\HttpFoundation\BinaryFileResponse;
22
use Symfony\Component\HttpFoundation\Exception\BadRequestException;
23
use Symfony\Component\HttpFoundation\Request as SymfonyRequest;
24
use Symfony\Component\HttpFoundation\Response as SymfonyResponse;
25
use Throwable;
26
27
trait RpcRequests
28
{
29
    /**
30
     * Boots the registered providers.
31
     */
32
    abstract public function boot();
33
34
    /**
35
     * Call the given Closure / class@method and inject its dependencies.
36
     *
37
     * @param callable|string $callback
38
     * @param array<string, mixed> $parameters
39
     * @param string|null $defaultMethod
40
     * @return mixed
41
     */
42
    abstract public function call($callback, array $parameters = [], $defaultMethod = null);
43
44
    /**
45
     * Register an existing instance as shared in the container.
46
     *
47
     * @param string $abstract
48
     * @param mixed $instance
49
     * @return mixed
50
     */
51
    abstract public function instance($abstract, $instance);
52
53
    /**
54
     * Resolve the given type from the container.
55
     *
56
     * @param string $abstract
57
     * @param array $parameters
58
     * @return mixed
59
     */
60
    abstract public function make($abstract, array $parameters = []);
61
62
    /**
63
     * Gather the full class names for the middleware short-cut string.
64
     *
65
     * @param mixed $middleware
66
     * @return array
67
     */
68
    abstract protected function gatherMiddlewareClassNames($middleware);
69
70
    /**
71
     * Prepare the given request instance for use with the application.
72
     *
73
     * @param SymfonyRequest $request
74
     * @return Request
75
     */
76
    abstract protected function prepareRequest(SymfonyRequest $request);
77
78
    /**
79
     * Send the exception to the handler and return the response.
80
     *
81
     * @param Throwable $e
82
     * @return SymfonyResponse
83
     */
84
    abstract protected function sendExceptionToHandler(Throwable $e);
85
86
    /**
87
     * Send the request through the pipeline with the given callback.
88
     *
89
     * @param array $middleware
90
     * @param Closure $then
91
     * @return mixed
92
     */
93
    abstract protected function sendThroughPipeline(array $middleware, Closure $then);
94
95
    /**
96
     * Dispatch the incoming request.
97
     *
98
     * @param SymfonyRequest|null $request
99
     * @return SymfonyResponse
100
     * @throws BadRequestException
101
     */
102 31
    public function rpcDispatch(?SymfonyRequest $request = null): SymfonyResponse
103
    {
104
        try {
105 31
            if ($request) {
106 31
                $request = RpcRequest::createFromBase($request);
107
            }
108 26
            $method = $this->parseIncomingRpcRequest($request);
109 26
            $this->boot();
110
111 26
            return $this->sendThroughPipeline($this->middleware, function (Request $request) use ($method) {
112 26
                $this->instance(Request::class, $request);
113
114 26
                return $this->proceedHandler($method);
115 26
            });
116 13
        } catch (Throwable $e) {
117 13
            return $this->prepareRpcResponse($this->sendExceptionToHandler($e));
118
        }
119
    }
120
121
    /**
122
     * Parse the incoming request and return the method and path info.
123
     *
124
     * @param RpcRequest|null $request
125
     * @return string
126
     */
127 27
    public function parseIncomingRpcRequest(?RpcRequest $request = null): string
128
    {
129 27
        if (! $request) {
130 1
            $request = RpcRequest::capture();
131
        }
132
133 26
        $this->instance(Request::class, $this->prepareRequest($request));
134
135 26
        return $request->getRpcMethod();
136
    }
137
138
    /**
139
     * Prepare and call handler.
140
     *
141
     * @param string $method
142
     * @return SymfonyResponse|mixed
143
     * @throws MethodNotFoundRpcException
144
     */
145 26
    public function proceedHandler(string $method)
146
    {
147 26
        [$class, $method] = $this->extractCallable($method);
148 26
        if (empty($method) || ! class_exists($class) || ! method_exists($instance = $this->make($class), $method)) {
149 3
            throw new MethodNotFoundRpcException();
150
        }
151 23
        if ($instance instanceof Handler) {
152 22
            return $this->callCoreHandler($instance, $method);
153
        }
154
155 2
        return $this->callHandlerCallable([$instance, $method]);
156
    }
157
158
    /**
159
     * Extract instance and method.
160
     *
161
     * @param string $method
162
     * @return array
163
     */
164 26
    protected function extractCallable(string $method): array
165
    {
166 26
        $callable = explode('.', $method, 2);
167
168 26
        $class = $this->getHandlerClass($callable[0]);
169 26
        $method = $callable[1] ?? '__invoke';
170
171 26
        return [$class, $method];
172
    }
173
174 26
    protected function getHandlerClass(string $handler): string
175
    {
176 26
        $handler = Str::studly($handler);
177 26
        $namespace = config('rpc.handler.path', 'App\Rpc\Handlers');
178 26
        $namespace = trim($namespace, " \t\n\r\0\x0B\\");
179 26
        $suffix = config('rpc.handler.suffix', 'Handler');
180 26
        $suffix = trim($suffix, " \t\n\r\0\x0B\\");
181
182 26
        $class = $namespace.'\\'.$handler.$suffix;
183 26
        $class = config("rpc.handlers.$handler", $class);
184
185 26
        return $class;
186
    }
187
188
    /**
189
     * Send the request through a Lumen handler.
190
     *
191
     * @param mixed $instance
192
     * @param string $method
193
     * @return mixed
194
     */
195 22
    protected function callCoreHandler($instance, $method)
196
    {
197 22
        $middleware = $instance->getMiddlewareForMethod($method);
198
199 22
        if (count($middleware) > 0) {
200 1
            return $this->callCoreHandlerWithMiddleware(
201 1
                $instance, $method, $middleware
202
            );
203
        }
204
205 21
        return $this->callHandlerCallable([$instance, $method]);
206
    }
207
208
    /**
209
     * Send the request through a set of handler middleware.
210
     *
211
     * @param mixed $instance
212
     * @param string $method
213
     * @param array $middleware
214
     * @return mixed
215
     */
216 1
    protected function callCoreHandlerWithMiddleware($instance, $method, $middleware)
217
    {
218
        /** @psalm-suppress InvalidArgument */
219 1
        $middleware = $this->gatherMiddlewareClassNames($middleware);
220
221 1
        return $this->sendThroughPipeline($middleware, function () use ($instance, $method) {
222 1
            return $this->callHandlerCallable([$instance, $method]);
223 1
        });
224
    }
225
226
    /**
227
     * Call a handler callable and return the response.
228
     *
229
     * @param callable $callable
230
     * @return SymfonyResponse
231
     */
232 23
    protected function callHandlerCallable(callable $callable): SymfonyResponse
233
    {
234 23
        return $this->prepareRpcResponse(
235 23
            $this->call($callable, [])
236
        );
237
    }
238
239
    /**
240
     * Prepare the response for sending.
241
     *
242
     * @param mixed $response
243
     * @return Response|BinaryFileResponse|SymfonyResponse|RpcResponse
244
     */
245 31
    public function prepareRpcResponse($response): SymfonyResponse
246
    {
247 31
        $request = app(Request::class);
248 31
        if ($response instanceof Responsable) {
249 1
            $response = $response->toResponse($request);
250 30
        } elseif ($response instanceof PsrResponseInterface) {
251 1
            $response = (new HttpFoundationFactory)->createResponse($response);
252 29
        } elseif ($response instanceof SplFileInfo) {
253 1
            $response = new BinaryFileResponse($response);
254
        }
255 31
        if ($response instanceof BinaryFileResponse) {
256 1
            return $response->prepare(Request::capture());
257
        }
258 30
        if (! $response instanceof RpcBaseResponse) {
259 17
            $response = $this->transformResponseToRpc($response);
260
        }
261
262 30
        return $response->prepare($request);
263
    }
264
265
    /**
266
     * Transform raw content or Symfony response to RPC response.
267
     *
268
     * @param $response
269
     * @return RpcBaseResponse
270
     */
271 17
    protected function transformResponseToRpc($response): RpcBaseResponse
272
    {
273 17
        if ($response instanceof SymfonyResponse) {
274 2
            $response = $response->getContent() ?: null;
275
        }
276 17
        if (null === $response) {
277 1
            return new RpcEmptyResponse();
278
        }
279
280 16
        return new RpcResponse($response);
281
    }
282
}
283