Generator::iterateThroughStrategies()   B
last analyzed

Complexity

Conditions 7
Paths 6

Size

Total Lines 58

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 58
rs 7.983
c 0
b 0
f 0
cc 7
nc 6
nop 3

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\Tools\DocumentationConfig;
9
use Mpociot\ApiDoc\Tools\Utils;
10
use ReflectionClass;
11
use ReflectionMethod;
12
13
class Generator
14
{
15
    /**
16
     * @var DocumentationConfig
17
     */
18
    private $config;
19
20
    public function __construct(DocumentationConfig $config = null)
21
    {
22
        // If no config is injected, pull from global
23
        $this->config = $config ?: new DocumentationConfig(config('apidoc'));
24
    }
25
26
    /**
27
     * @param Route $route
28
     *
29
     * @return mixed
30
     */
31
    public function getUri(Route $route)
32
    {
33
        return $route->uri();
34
    }
35
36
    /**
37
     * @param Route $route
38
     *
39
     * @return mixed
40
     */
41
    public function getMethods(Route $route)
42
    {
43
        return array_diff($route->methods(), ['HEAD']);
44
    }
45
46
    /**
47
     * @param \Illuminate\Routing\Route $route
48
     * @param array $routeRules Rules to apply when generating documentation for this route
49
     *
50
     * @throws \ReflectionException
51
     *
52
     * @return array
53
     */
54
    public function processRoute(Route $route, array $routeRules = [])
55
    {
56
        [$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...
57
        $controller = new ReflectionClass($controllerName);
58
        $method = $controller->getMethod($methodName);
59
60
        $parsedRoute = [
61
            'id' => md5($this->getUri($route) . ':' . implode($this->getMethods($route))),
62
            'methods' => $this->getMethods($route),
63
            'uri' => $this->getUri($route),
64
        ];
65
        $metadata = $this->fetchMetadata($controller, $method, $route, $routeRules, $parsedRoute);
66
        $parsedRoute['metadata'] = $metadata;
67
68
        $urlParameters = $this->fetchUrlParameters($controller, $method, $route, $routeRules, $parsedRoute);
69
        $parsedRoute['urlParameters'] = $urlParameters;
70
        $parsedRoute['cleanUrlParameters'] = $this->cleanParams($urlParameters);
71
        $parsedRoute['boundUri'] = Utils::getFullUrl($route, $parsedRoute['cleanUrlParameters']);
72
73
        $queryParameters = $this->fetchQueryParameters($controller, $method, $route, $routeRules, $parsedRoute);
74
        $parsedRoute['queryParameters'] = $queryParameters;
75
        $parsedRoute['cleanQueryParameters'] = $this->cleanParams($queryParameters);
76
77
        $headers = $this->fetchRequestHeaders($controller, $method, $route, $routeRules, $parsedRoute);
78
        $parsedRoute['headers'] = $headers;
79
80
        $bodyParameters = $this->fetchBodyParameters($controller, $method, $route, $routeRules, $parsedRoute);
81
        $parsedRoute['bodyParameters'] = $bodyParameters;
82
        $parsedRoute['cleanBodyParameters'] = $this->cleanParams($bodyParameters);
83
84
        $responses = $this->fetchResponses($controller, $method, $route, $routeRules, $parsedRoute);
85
        $parsedRoute['responses'] = $responses;
86
        $parsedRoute['showresponse'] = ! empty($responses);
87
88
        return $parsedRoute;
89
    }
90
91
    protected function fetchMetadata(ReflectionClass $controller, ReflectionMethod $method, Route $route, array $rulesToApply, array $context = [])
92
    {
93
        $context['metadata'] = [
94
            'groupName' => $this->config->get('default_group', ''),
95
            'groupDescription' => '',
96
            'title' => '',
97
            'description' => '',
98
            'authenticated' => false,
99
        ];
100
101
        return $this->iterateThroughStrategies('metadata', $context, [$route, $controller, $method, $rulesToApply]);
102
    }
103
104
    protected function fetchUrlParameters(ReflectionClass $controller, ReflectionMethod $method, Route $route, array $rulesToApply, array $context = [])
105
    {
106
        return $this->iterateThroughStrategies('urlParameters', $context, [$route, $controller, $method, $rulesToApply]);
107
    }
108
109
    protected function fetchQueryParameters(ReflectionClass $controller, ReflectionMethod $method, Route $route, array $rulesToApply, array $context = [])
110
    {
111
        return $this->iterateThroughStrategies('queryParameters', $context, [$route, $controller, $method, $rulesToApply]);
112
    }
113
114
    protected function fetchBodyParameters(ReflectionClass $controller, ReflectionMethod $method, Route $route, array $rulesToApply, array $context = [])
115
    {
116
        return $this->iterateThroughStrategies('bodyParameters', $context, [$route, $controller, $method, $rulesToApply]);
117
    }
118
119
    protected function fetchResponses(ReflectionClass $controller, ReflectionMethod $method, Route $route, array $rulesToApply, array $context = [])
120
    {
121
        $responses = $this->iterateThroughStrategies('responses', $context, [$route, $controller, $method, $rulesToApply]);
122
        if (count($responses)) {
123
            return array_filter($responses, function ($response) {
124
                return $response['content'] != null;
125
            });
126
        }
127
128
        return [];
129
    }
130
131
    protected function fetchRequestHeaders(ReflectionClass $controller, ReflectionMethod $method, Route $route, array $rulesToApply, array $context = [])
132
    {
133
        $headers = $this->iterateThroughStrategies('headers', $context, [$route, $controller, $method, $rulesToApply]);
134
135
        return array_filter($headers);
136
    }
137
138
    protected function iterateThroughStrategies(string $stage, array $context, array $arguments)
139
    {
140
        $defaultStrategies = [
141
            'metadata' => [
142
                \Mpociot\ApiDoc\Extracting\Strategies\Metadata\GetFromDocBlocks::class,
143
            ],
144
            'urlParameters' => [
145
                \Mpociot\ApiDoc\Extracting\Strategies\UrlParameters\GetFromUrlParamTag::class,
146
            ],
147
            'queryParameters' => [
148
                \Mpociot\ApiDoc\Extracting\Strategies\QueryParameters\GetFromQueryParamTag::class,
149
            ],
150
            'headers' => [
151
                \Mpociot\ApiDoc\Extracting\Strategies\RequestHeaders\GetFromRouteRules::class,
152
            ],
153
            'bodyParameters' => [
154
                \Mpociot\ApiDoc\Extracting\Strategies\BodyParameters\GetFromBodyParamTag::class,
155
            ],
156
            'responses' => [
157
                \Mpociot\ApiDoc\Extracting\Strategies\Responses\UseTransformerTags::class,
158
                \Mpociot\ApiDoc\Extracting\Strategies\Responses\UseResponseTag::class,
159
                \Mpociot\ApiDoc\Extracting\Strategies\Responses\UseResponseFileTag::class,
160
                \Mpociot\ApiDoc\Extracting\Strategies\Responses\UseApiResourceTags::class,
161
                \Mpociot\ApiDoc\Extracting\Strategies\Responses\ResponseCalls::class,
162
            ],
163
        ];
164
165
        // Use the default strategies for the stage, unless they were explicitly set
166
        $strategies = $this->config->get("strategies.$stage", $defaultStrategies[$stage]);
167
        $context[$stage] = $context[$stage] ?? [];
168
        foreach ($strategies as $strategyClass) {
169
            $strategy = new $strategyClass($stage, $this->config);
170
            $strategyArgs = $arguments;
171
            $strategyArgs[] = $context;
172
            $results = $strategy(...$strategyArgs);
173
            if (! is_null($results)) {
174
                foreach ($results as $index => $item) {
175
                    if ($stage == 'responses') {
176
                        // Responses are additive
177
                        $context[$stage][] = $item;
178
                        continue;
179
                    }
180
                    // Using a for loop rather than array_merge or +=
181
                    // so it does not renumber numeric keys
182
                    // and also allows values to be overwritten
183
184
                    // Don't allow overwriting if an empty value is trying to replace a set one
185
                    if (! in_array($context[$stage], [null, ''], true) && in_array($item, [null, ''], true)) {
186
                        continue;
187
                    } else {
188
                        $context[$stage][$index] = $item;
189
                    }
190
                }
191
            }
192
        }
193
194
        return $context[$stage];
195
    }
196
197
    /**
198
     * Create samples at index 0 for array parameters.
199
     * Also filter out parameters which were excluded from having examples.
200
     *
201
     * @param array $params
202
     *
203
     * @return array
204
     */
205
    protected function cleanParams(array $params)
206
    {
207
        $values = [];
208
209
        // Remove params which have no examples.
210
        $params = array_filter($params, function ($details) {
211
            return ! is_null($details['value']);
212
        });
213
214
        foreach ($params as $paramName => $details) {
215
            $this->generateConcreteSampleForArrayKeys(
216
                $paramName,
217
                $details['value'],
218
                $values
219
            );
220
        }
221
222
        return $values;
223
    }
224
225
    /**
226
     * For each array notation parameter (eg user.*, item.*.name, object.*.*, user[])
227
     * generate concrete sample (user.0, item.0.name, object.0.0, user.0) with example as value.
228
     *
229
     * @param string $paramName
230
     * @param mixed $paramExample
231
     * @param array $values The array that holds the result
232
     *
233
     * @return void
234
     */
235
    protected function generateConcreteSampleForArrayKeys($paramName, $paramExample, array &$values = [])
236
    {
237
        if (Str::contains($paramName, '[')) {
238
            // Replace usages of [] with dot notation
239
            $paramName = str_replace(['][', '[', ']', '..'], ['.', '.', '', '.*.'], $paramName);
240
        }
241
        // Then generate a sample item for the dot notation
242
        Arr::set($values, str_replace(['.*', '*.'], ['.0','0.'], $paramName), $paramExample);
243
    }
244
}
245