Completed
Push — master ( 0ed20a...e8c824 )
by
unknown
10:05 queued 08:45
created

Generator::fetchMetadata()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

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