Completed
Pull Request — master (#1645)
by Yanick
04:57
created

BatchEndpointAction::convertResponse()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 5
nc 1
nop 1
1
<?php
2
3
/*
4
 * This file is part of the API Platform project.
5
 *
6
 * (c) Kévin Dunglas <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
declare(strict_types=1);
13
14
namespace ApiPlatform\Core\Action;
15
use Symfony\Component\HttpFoundation\Request;
16
use Symfony\Component\HttpFoundation\Response;
17
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
18
use Symfony\Component\HttpKernel\HttpKernelInterface;
19
20
/**
21
 * Provides an endpoint for batch processing by splitting up
22
 *
23
 * @author Yanick Witschi <[email protected]>
24
 */
25
class BatchEndpointAction
26
{
27
    /**
28
     * @var HttpKernelInterface
29
     */
30
    private $kernel;
31
32
    /**
33
     * @var array
34
     */
35
    private $validKeys = [
36
        'path',
37
        'method',
38
        'headers',
39
        'body',
40
    ];
41
42
    /**
43
     * @param HttpKernelInterface $kernel
44
     */
45
    public function __construct(HttpKernelInterface $kernel)
46
    {
47
        $this->kernel = $kernel;
48
    }
49
50
    /**
51
     * Execute multiple subrequests from batch request.
52
     *
53
     * @param Request $request
54
     * @param array   $data
55
     *
56
     * @return array
57
     */
58
    public function __invoke(Request $request, array $data = null): array
59
    {
60
        if (!$this->validateBatchData((array) $data)) {
61
            throw new BadRequestHttpException('Batch request data not accepted.');
62
        }
63
64
        $result = [];
65
66
        foreach ($data as $k => $item) {
0 ignored issues
show
Bug introduced by
The expression $data of type null|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
67
68
            // Copy current headers if no specific provided for simplicity
69
            // otherwise one would have to provide Content-Type with every
70
            // single entry.
71
            if (!isset($item['headers'])) {
72
                $item['headers'] = $request->headers->all();
73
            }
74
75
            // Make sure Content-Length is always correct
76
            $item['headers']['content-length'] = strlen($item['body']);
77
78
            $result[] = $this->convertResponse(
79
                $this->executeSubRequest(
80
                    $k,
81
                    $item['path'],
82
                    $item['method'],
83
                    $item['headers'],
84
                    $item['body']
85
                )
86
            );
87
        }
88
89
        return $result;
90
    }
91
92
    /**
93
     * Converts a response into an array.
94
     *
95
     * @param Response $response
96
     *
97
     * @return array
98
     */
99
    private function convertResponse(Response $response): array
100
    {
101
        return [
102
            'status' => $response->getStatusCode(),
103
            'body' => $response->getContent(),
104
            'headers' => $response->headers->all(),
105
        ];
106
    }
107
108
    /**
109
     * Validates that the keys are all correctly present.
110
     *
111
     * @param array $data
112
     *
113
     * @return bool
114
     */
115
    private function validateBatchData(array $data)
116
    {
117
        if (0 === count($data)) {
118
            return false;
119
        }
120
121
        foreach ($data as $item) {
122
            if (0 !== count(array_diff(array_keys($item), $this->validKeys))) {
123
                return false;
124
            }
125
        }
126
127
        return true;
128
    }
129
130
    /**
131
     * Executes a subrequest.
132
     *
133
     * @param int    $index
134
     * @param string $path
135
     * @param string $method
136
     * @param array  $headers
137
     * @param string $body
138
     *
139
     * @return Response
140
     */
141
    private function executeSubRequest(int $index, string $path, string $method, array $headers, string $body): Response
142
    {
143
        $subRequest = Request::create($path, $method, [], [], [], [], $body);
144
        $subRequest->headers->replace($headers);
145
146
        try {
147
            return $this->kernel->handle($subRequest, HttpKernelInterface::SUB_REQUEST);
148
        } catch (\Exception $e) {
149
            return Response::create(sprintf('Batch element #%d failed, check the log files.', $index), 400);
150
        }
151
    }
152
}
153