Completed
Pull Request — master (#743)
by Guy
01:26
created

Generator::fetchResponseParameters()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 5
1
<?php
2
3
namespace Mpociot\ApiDoc\Extracting;
4
5
use Illuminate\Routing\Route;
6
use Illuminate\Support\Arr;
7
use Illuminate\Support\Str;
8
use Mpociot\ApiDoc\Extracting\Strategies\ResponseParameters\GetFromResponseParamTag;
9
use Mpociot\ApiDoc\Extracting\Strategies\ResponseParameters\GetFromTransformerParamTag;
10
use Mpociot\ApiDoc\Tools\DocumentationConfig;
11
use Mpociot\ApiDoc\Tools\Utils;
12
use ReflectionClass;
13
use ReflectionMethod;
14
15
class Generator
16
{
17
    /**
18
     * @var DocumentationConfig
19
     */
20
    private $config;
21
22
    public function __construct(DocumentationConfig $config = null)
23
    {
24
        // If no config is injected, pull from global
25
        $this->config = $config ?: new DocumentationConfig(config('apidoc'));
26
    }
27
28
    /**
29
     * @param Route $route
30
     *
31
     * @return mixed
32
     */
33
    public function getUri(Route $route)
34
    {
35
        return $route->uri();
36
    }
37
38
    /**
39
     * @param Route $route
40
     *
41
     * @return mixed
42
     */
43
    public function getMethods(Route $route)
44
    {
45
        return array_diff($route->methods(), ['HEAD']);
46
    }
47
48
    /**
49
     * @param \Illuminate\Routing\Route $route
50
     * @param array $routeRules Rules to apply when generating documentation for this route
51
     *
52
     * @throws \ReflectionException
53
     *
54
     * @return array
55
     */
56
    public function processRoute(Route $route, array $routeRules = [])
57
    {
58
        [$controllerName, $methodName] = Utils::getRouteClassAndMethodNames($route->getAction());
0 ignored issues
show
Bug introduced by
The variable $controllerName does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $methodName does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
59
        $controller = new ReflectionClass($controllerName);
60
        $method = $controller->getMethod($methodName);
61
62
        $parsedRoute = [
63
            'id' => md5($this->getUri($route) . ':' . implode($this->getMethods($route))),
64
            'methods' => $this->getMethods($route),
65
            'uri' => $this->getUri($route),
66
        ];
67
        $metadata = $this->fetchMetadata($controller, $method, $route, $routeRules, $parsedRoute);
68
        $parsedRoute['metadata'] = $metadata;
69
70
        $urlParameters = $this->fetchUrlParameters($controller, $method, $route, $routeRules, $parsedRoute);
71
        $parsedRoute['urlParameters'] = $urlParameters;
72
        $parsedRoute['cleanUrlParameters'] = $this->cleanParams($urlParameters);
73
        $parsedRoute['boundUri'] = Utils::getFullUrl($route, $parsedRoute['cleanUrlParameters']);
74
75
        $queryParameters = $this->fetchQueryParameters($controller, $method, $route, $routeRules, $parsedRoute);
76
        $parsedRoute['queryParameters'] = $queryParameters;
77
        $parsedRoute['cleanQueryParameters'] = $this->cleanParams($queryParameters);
78
79
        $headers = $this->fetchRequestHeaders($controller, $method, $route, $routeRules, $parsedRoute);
80
        $parsedRoute['headers'] = $headers;
81
82
        $bodyParameters = $this->fetchBodyParameters($controller, $method, $route, $routeRules, $parsedRoute);
83
        $parsedRoute['bodyParameters'] = $bodyParameters;
84
        $parsedRoute['cleanBodyParameters'] = $this->cleanParams($bodyParameters);
85
86
        $responseParameters = $this->fetchResponseParameters($controller, $method, $route, $routeRules, $parsedRoute);
87
        $parsedRoute['responseParameters'] = $responseParameters;
88
        $parsedRoute['cleanResponseParameters'] = $this->cleanParams($responseParameters);
89
90
        $responses = $this->fetchResponses($controller, $method, $route, $routeRules, $parsedRoute);
91
        $parsedRoute['responses'] = $responses;
92
        $parsedRoute['showresponse'] = ! empty($responses);
93
94
        return $parsedRoute;
95
    }
96
97
    protected function fetchMetadata(ReflectionClass $controller, ReflectionMethod $method, Route $route, array $rulesToApply, array $context = [])
98
    {
99
        $context['metadata'] = [
100
            'groupName' => $this->config->get('default_group', ''),
101
            'groupDescription' => '',
102
            'title' => '',
103
            'description' => '',
104
            'authenticated' => false,
105
        ];
106
107
        return $this->iterateThroughStrategies('metadata', $context, [$route, $controller, $method, $rulesToApply]);
108
    }
109
110
    protected function fetchUrlParameters(ReflectionClass $controller, ReflectionMethod $method, Route $route, array $rulesToApply, array $context = [])
111
    {
112
        return $this->iterateThroughStrategies('urlParameters', $context, [$route, $controller, $method, $rulesToApply]);
113
    }
114
115
    protected function fetchQueryParameters(ReflectionClass $controller, ReflectionMethod $method, Route $route, array $rulesToApply, array $context = [])
116
    {
117
        return $this->iterateThroughStrategies('queryParameters', $context, [$route, $controller, $method, $rulesToApply]);
118
    }
119
120
    protected function fetchBodyParameters(ReflectionClass $controller, ReflectionMethod $method, Route $route, array $rulesToApply, array $context = [])
121
    {
122
        return $this->iterateThroughStrategies('bodyParameters', $context, [$route, $controller, $method, $rulesToApply]);
123
    }
124
125
    protected function fetchResponseParameters(ReflectionClass $controller, ReflectionMethod $method, Route $route, array $rulesToApply, array $context = [])
126
    {
127
        return $this->iterateThroughStrategies('responseParameters', $context, [$route, $controller, $method, $rulesToApply]);
128
    }
129
130
    protected function fetchResponses(ReflectionClass $controller, ReflectionMethod $method, Route $route, array $rulesToApply, array $context = [])
131
    {
132
        $responses = $this->iterateThroughStrategies('responses', $context, [$route, $controller, $method, $rulesToApply]);
133
        if (count($responses)) {
134
            return array_filter($responses, function ($response) {
135
                return $response['content'] != null;
136
            });
137
        }
138
139
        return [];
140
    }
141
142
    protected function fetchRequestHeaders(ReflectionClass $controller, ReflectionMethod $method, Route $route, array $rulesToApply, array $context = [])
143
    {
144
        $headers = $this->iterateThroughStrategies('headers', $context, [$route, $controller, $method, $rulesToApply]);
145
146
        return array_filter($headers);
147
    }
148
149
    protected function iterateThroughStrategies(string $stage, array $context, array $arguments)
150
    {
151
        $defaultStrategies = [
152
            'metadata' => [
153
                \Mpociot\ApiDoc\Extracting\Strategies\Metadata\GetFromDocBlocks::class,
154
            ],
155
            'urlParameters' => [
156
                \Mpociot\ApiDoc\Extracting\Strategies\UrlParameters\GetFromUrlParamTag::class,
157
            ],
158
            'queryParameters' => [
159
                \Mpociot\ApiDoc\Extracting\Strategies\QueryParameters\GetFromQueryParamTag::class,
160
            ],
161
            'headers' => [
162
                \Mpociot\ApiDoc\Extracting\Strategies\RequestHeaders\GetFromRouteRules::class,
163
            ],
164
            'bodyParameters' => [
165
                \Mpociot\ApiDoc\Extracting\Strategies\BodyParameters\GetFromBodyParamTag::class,
166
            ],
167
            'responseParameters' => [
168
                GetFromResponseParamTag::class,
169
                GetFromTransformerParamTag::class,
170
            ],
171
            'responses' => [
172
                \Mpociot\ApiDoc\Extracting\Strategies\Responses\UseTransformerTags::class,
173
                \Mpociot\ApiDoc\Extracting\Strategies\Responses\UseResponseTag::class,
174
                \Mpociot\ApiDoc\Extracting\Strategies\Responses\UseResponseFileTag::class,
175
                \Mpociot\ApiDoc\Extracting\Strategies\Responses\UseApiResourceTags::class,
176
                \Mpociot\ApiDoc\Extracting\Strategies\Responses\ResponseCalls::class,
177
            ],
178
        ];
179
180
        // Use the default strategies for the stage, unless they were explicitly set
181
        $strategies = $this->config->get("strategies.$stage", $defaultStrategies[$stage]);
182
183
        $context[$stage] = $context[$stage] ?? [];
184
        foreach ($strategies as $strategyClass) {
185
            $strategy = new $strategyClass($stage, $this->config);
186
            $strategyArgs = $arguments;
187
            $strategyArgs[] = $context;
188
            $results = $strategy(...$strategyArgs);
189
            if (! is_null($results)) {
190
                foreach ($results as $index => $item) {
191
                    if ($stage == 'responses') {
192
                        // Responses are additive
193
                        $context[$stage][] = $item;
194
                        continue;
195
                    }
196
                    // Using a for loop rather than array_merge or +=
197
                    // so it does not renumber numeric keys
198
                    // and also allows values to be overwritten
199
200
                    // Don't allow overwriting if an empty value is trying to replace a set one
201
                    if (! in_array($context[$stage], [null, ''], true) && in_array($item, [null, ''], true)) {
202
                        continue;
203
                    } else {
204
                        $context[$stage][$index] = $item;
205
                    }
206
                }
207
            }
208
        }
209
210
        return $context[$stage];
211
    }
212
213
    /**
214
     * Create samples at index 0 for array parameters.
215
     * Also filter out parameters which were excluded from having examples.
216
     *
217
     * @param array $params
218
     *
219
     * @return array
220
     */
221
    protected function cleanParams(array $params)
222
    {
223
        $values = [];
224
225
        // Remove params which have no examples.
226
        $params = array_filter($params, function ($details) {
227
            return ! is_null($details['value']);
228
        });
229
230
        foreach ($params as $paramName => $details) {
231
            $this->generateConcreteSampleForArrayKeys(
232
                $paramName,
233
                $details['value'],
234
                $values
235
            );
236
        }
237
238
        return $values;
239
    }
240
241
    /**
242
     * For each array notation parameter (eg user.*, item.*.name, object.*.*, user[])
243
     * generate concrete sample (user.0, item.0.name, object.0.0, user.0) with example as value.
244
     *
245
     * @param string $paramName
246
     * @param mixed $paramExample
247
     * @param array $values The array that holds the result
248
     *
249
     * @return void
250
     */
251
    protected function generateConcreteSampleForArrayKeys($paramName, $paramExample, array &$values = [])
252
    {
253
        if (Str::contains($paramName, '[')) {
254
            // Replace usages of [] with dot notation
255
            $paramName = str_replace(['][', '[', ']', '..'], ['.', '.', '', '.*.'], $paramName);
256
        }
257
        // Then generate a sample item for the dot notation
258
        Arr::set($values, str_replace(['.*', '*.'], ['.0','0.'], $paramName), $paramExample);
259
    }
260
}
261