1
+ <?php
2
+
3
+ namespace Mpociot \ApiDoc ;
4
+
5
+ use Illuminate \Foundation \Http \FormRequest ;
6
+ use Illuminate \Routing \Route ;
7
+ use Illuminate \Support \Facades \App ;
8
+ use Illuminate \Support \Facades \Request ;
9
+ use Illuminate \Support \Facades \Validator ;
10
+ use Illuminate \Support \Str ;
11
+ use phpDocumentor \Reflection \DocBlock ;
12
+ use ReflectionClass ;
13
+
14
+ class ApiDocGenerator
15
+ {
16
+
17
+ /**
18
+ * @param Route $route
19
+ * @return array
20
+ */
21
+ public function processRoute (Route $ route )
22
+ {
23
+ $ routeAction = $ route ->getAction ();
24
+ $ response = $ this ->getRouteResponse ($ route );
25
+ $ routeDescription = $ this ->getRouteDescription ($ routeAction ['uses ' ]);
26
+ $ routeData = [
27
+ 'title ' => $ routeDescription ['short ' ],
28
+ 'description ' => $ routeDescription ['long ' ],
29
+ 'methods ' => $ route ->getMethods (),
30
+ 'uri ' => $ route ->getUri (),
31
+ 'parameters ' => [],
32
+ 'response ' => ($ response ->headers ->get ('Content-Type ' ) === 'application/json ' ) ? json_encode (json_decode ($ response ->getContent ()), JSON_PRETTY_PRINT ) : $ response ->getContent ()
33
+ ];
34
+
35
+ $ validator = Validator::make ([], $ this ->getRouteRules ($ routeAction ['uses ' ]));
36
+ foreach ($ validator ->getRules () as $ attribute => $ rules ) {
37
+ $ attributeData = [
38
+ 'required ' => false ,
39
+ 'type ' => 'string ' ,
40
+ 'default ' => '' ,
41
+ 'description ' => []
42
+ ];
43
+ foreach ($ rules as $ rule ) {
44
+ $ this ->parseRule ($ rule , $ attributeData );
45
+ }
46
+ $ routeData ['parameters ' ][$ attribute ] = $ attributeData ;
47
+ }
48
+
49
+ return $ routeData ;
50
+ }
51
+
52
+ /**
53
+ * @param \Illuminate\Routing\Route $route
54
+ * @return \Illuminate\Http\Response
55
+ */
56
+ private function getRouteResponse (Route $ route )
57
+ {
58
+ $ methods = $ route ->getMethods ();
59
+ $ response = $ this ->callRoute (array_shift ($ methods ), $ route ->getUri ());
60
+ return $ response ;
61
+ }
62
+
63
+ /**
64
+ * @param $route
65
+ * @return string
66
+ */
67
+ private function getRouteDescription ($ route )
68
+ {
69
+ list ($ class , $ method ) = explode ('@ ' , $ route );
70
+ $ reflection = new ReflectionClass ($ class );
71
+ $ reflectionMethod = $ reflection ->getMethod ($ method );
72
+
73
+ $ comment = $ reflectionMethod ->getDocComment ();
74
+ $ phpdoc = new DocBlock ($ comment );
75
+ return [
76
+ 'short ' => $ phpdoc ->getShortDescription (),
77
+ 'long ' => $ phpdoc ->getLongDescription ()->getContents ()
78
+ ];
79
+ }
80
+
81
+
82
+ /**
83
+ * @param $route
84
+ * @return array
85
+ */
86
+ private function getRouteRules ($ route )
87
+ {
88
+ list ($ class , $ method ) = explode ('@ ' , $ route );
89
+ $ reflection = new ReflectionClass ($ class );
90
+ $ reflectionMethod = $ reflection ->getMethod ($ method );
91
+
92
+ foreach ($ reflectionMethod ->getParameters () as $ parameter ) {
93
+ $ parameterType = $ parameter ->getType ();
94
+ if (!is_null ($ parameterType ) && class_exists ($ parameterType )) {
95
+ $ className = $ parameterType ->__toString ();
96
+ $ parameterReflection = new $ className ;
97
+ if ($ parameterReflection instanceof FormRequest) {
98
+ if (method_exists ($ parameterReflection , 'validator ' )) {
99
+ return $ parameterReflection ->validator ()->getRules ();
100
+ } else {
101
+ return $ parameterReflection ->rules ();
102
+ }
103
+ }
104
+ }
105
+ }
106
+
107
+ return [];
108
+ }
109
+
110
+ /**
111
+ * @param $rule
112
+ * @param $attributeData
113
+ */
114
+ protected function parseRule ($ rule , &$ attributeData )
115
+ {
116
+ $ parsedRule = $ this ->parseStringRule ($ rule );
117
+ $ parsedRule [0 ] = $ this ->normalizeRule ($ parsedRule [0 ]);
118
+ list ($ rule , $ parameters ) = $ parsedRule ;
119
+
120
+ switch ($ rule ) {
121
+ case 'required ' :
122
+ $ attributeData ['required ' ] = true ;
123
+ break ;
124
+ case 'in ' :
125
+ $ attributeData ['description ' ][] = implode (' or ' , $ parameters );
126
+ break ;
127
+ case 'not_in ' :
128
+ $ attributeData ['description ' ][] = 'Not in: ' . implode (' or ' , $ parameters );
129
+ break ;
130
+ case 'min ' :
131
+ $ attributeData ['description ' ][] = 'Minimum: ` ' . $ parameters [0 ] . '` ' ;
132
+ break ;
133
+ case 'max ' :
134
+ $ attributeData ['description ' ][] = 'Maximum: ` ' . $ parameters [0 ] . '` ' ;
135
+ break ;
136
+ case 'between ' :
137
+ $ attributeData ['description ' ][] = 'Between: ` ' . $ parameters [0 ] . '` and ' . $ parameters [1 ];
138
+ break ;
139
+ case 'date_format ' :
140
+ $ attributeData ['description ' ][] = 'Date format: ' . $ parameters [0 ];
141
+ break ;
142
+ case 'mimetypes ' :
143
+ case 'mimes ' :
144
+ $ attributeData ['description ' ][] = 'Allowed mime types: ' . implode (', ' , $ parameters );
145
+ break ;
146
+ case 'required_if ' :
147
+ $ attributeData ['description ' ][] = 'Required if ` ' . $ parameters [0 ] . '` is ` ' . $ parameters [1 ] . '` ' ;
148
+ break ;
149
+ case 'exists ' :
150
+ $ attributeData ['description ' ][] = 'Valid ' . Str::singular ($ parameters [0 ]) . ' ' . $ parameters [1 ];
151
+ break ;
152
+ case 'active_url ' :
153
+ $ attributeData ['type ' ] = 'url ' ;
154
+ break ;
155
+ case 'boolean ' :
156
+ case 'email ' :
157
+ case 'image ' :
158
+ case 'string ' :
159
+ case 'integer ' :
160
+ case 'json ' :
161
+ case 'numeric ' :
162
+ case 'url ' :
163
+ case 'ip ' :
164
+ $ attributeData ['type ' ] = $ rule ;
165
+ break ;
166
+ }
167
+ }
168
+
169
+ /**
170
+ * Call the given URI and return the Response.
171
+ *
172
+ * @param string $method
173
+ * @param string $uri
174
+ * @param array $parameters
175
+ * @param array $cookies
176
+ * @param array $files
177
+ * @param array $server
178
+ * @param string $content
179
+ * @return \Illuminate\Http\Response
180
+ */
181
+ public function callRoute ($ method , $ uri , $ parameters = [], $ cookies = [], $ files = [], $ server = [], $ content = null )
182
+ {
183
+ $ kernel = App::make ('Illuminate\Contracts\Http\Kernel ' );
184
+ App::instance ('middleware.disable ' , true );
185
+
186
+ $ server = [
187
+ 'CONTENT_TYPE ' => 'application/json ' ,
188
+ 'Accept ' => 'application/json ' ,
189
+ ];
190
+
191
+ $ request = Request::create (
192
+ $ uri , $ method , $ parameters ,
193
+ $ cookies , $ files , $ this ->transformHeadersToServerVars ($ server ), $ content
194
+ );
195
+
196
+ $ response = $ kernel ->handle ($ request );
197
+
198
+ $ kernel ->terminate ($ request , $ response );
199
+
200
+ return $ response ;
201
+ }
202
+
203
+ /**
204
+ * Transform headers array to array of $_SERVER vars with HTTP_* format.
205
+ *
206
+ * @param array $headers
207
+ * @return array
208
+ */
209
+ protected function transformHeadersToServerVars (array $ headers )
210
+ {
211
+ $ server = [];
212
+ $ prefix = 'HTTP_ ' ;
213
+
214
+ foreach ($ headers as $ name => $ value ) {
215
+ $ name = strtr (strtoupper ($ name ), '- ' , '_ ' );
216
+
217
+ if (!starts_with ($ name , $ prefix ) && $ name != 'CONTENT_TYPE ' ) {
218
+ $ name = $ prefix . $ name ;
219
+ }
220
+
221
+ $ server [$ name ] = $ value ;
222
+ }
223
+
224
+ return $ server ;
225
+ }
226
+
227
+ /**
228
+ * Parse a string based rule.
229
+ *
230
+ * @param string $rules
231
+ * @return array
232
+ */
233
+ protected function parseStringRule ($ rules )
234
+ {
235
+ $ parameters = [];
236
+
237
+ // The format for specifying validation rules and parameters follows an
238
+ // easy {rule}:{parameters} formatting convention. For instance the
239
+ // rule "Max:3" states that the value may only be three letters.
240
+ if (strpos ($ rules , ': ' ) !== false ) {
241
+ list ($ rules , $ parameter ) = explode (': ' , $ rules , 2 );
242
+
243
+ $ parameters = $ this ->parseParameters ($ rules , $ parameter );
244
+ }
245
+
246
+ return [strtolower (trim ($ rules )), $ parameters ];
247
+ }
248
+
249
+ /**
250
+ * Parse a parameter list.
251
+ *
252
+ * @param string $rule
253
+ * @param string $parameter
254
+ * @return array
255
+ */
256
+ protected function parseParameters ($ rule , $ parameter )
257
+ {
258
+ if (strtolower ($ rule ) == 'regex ' ) {
259
+ return [$ parameter ];
260
+ }
261
+
262
+ return str_getcsv ($ parameter );
263
+ }
264
+
265
+ /**
266
+ * Normalizes a rule so that we can accept short types.
267
+ *
268
+ * @param string $rule
269
+ * @return string
270
+ */
271
+ protected function normalizeRule ($ rule )
272
+ {
273
+ switch ($ rule ) {
274
+ case 'int ' :
275
+ return 'integer ' ;
276
+ case 'bool ' :
277
+ return 'boolean ' ;
278
+ default :
279
+ return $ rule ;
280
+ }
281
+ }
282
+ }
0 commit comments