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