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