1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace App\Modules\Core\Console\Commands; |
4
|
|
|
|
5
|
|
|
use Illuminate\Console\Command; |
6
|
|
|
use Illuminate\Support\Arr; |
7
|
|
|
|
8
|
|
|
class GenerateDoc extends Command |
9
|
|
|
{ |
10
|
|
|
/** |
11
|
|
|
* The name and signature of the console command. |
12
|
|
|
* |
13
|
|
|
* @var string |
14
|
|
|
*/ |
15
|
|
|
protected $signature = 'doc:generate'; |
16
|
|
|
|
17
|
|
|
/** |
18
|
|
|
* The console command description. |
19
|
|
|
* |
20
|
|
|
* @var string |
21
|
|
|
*/ |
22
|
|
|
protected $description = 'Generate api documentation'; |
23
|
|
|
|
24
|
|
|
/** |
25
|
|
|
* Create a new command instance. |
26
|
|
|
*/ |
27
|
|
|
public function __construct() |
28
|
|
|
{ |
29
|
|
|
parent::__construct(); |
30
|
|
|
} |
31
|
|
|
|
32
|
|
|
/** |
33
|
|
|
* Execute the console command. |
34
|
|
|
* |
35
|
|
|
* @return mixed |
36
|
|
|
*/ |
37
|
|
|
public function handle() |
38
|
|
|
{ |
39
|
|
|
$docData = []; |
40
|
|
|
$docData['models'] = []; |
41
|
|
|
$routes = $this->getRoutes(); |
42
|
|
|
foreach ($routes as $route) { |
43
|
|
|
if ($route) { |
44
|
|
|
$actoinArray = explode('@', $route['action']); |
45
|
|
|
if (Arr::get($actoinArray, 1, false)) { |
46
|
|
|
$controller = $actoinArray[0]; |
47
|
|
|
$method = $actoinArray[1]; |
48
|
|
|
$route['name'] = $method !== 'index' ? $method : 'list'; |
49
|
|
|
|
50
|
|
|
$reflectionClass = new \ReflectionClass($controller); |
51
|
|
|
$reflectionMethod = $reflectionClass->getMethod($method); |
52
|
|
|
$classProperties = $reflectionClass->getDefaultProperties(); |
53
|
|
|
$skipLoginCheck = Arr::get($classProperties, 'skipLoginCheck', false); |
54
|
|
|
$validationRules = Arr::get($classProperties, 'validationRules', false); |
55
|
|
|
|
56
|
|
|
$this->processDocBlock($route, $reflectionMethod); |
57
|
|
|
$this->getHeaders($route, $method, $skipLoginCheck); |
58
|
|
|
$this->getPostData($route, $reflectionMethod, $validationRules); |
59
|
|
|
|
60
|
|
|
$route['response'] = $this->getResponseObject($classProperties['model'], $route['name'], $route['returnDocBlock']); |
61
|
|
|
|
62
|
|
|
preg_match('/api\/([^#]+)\//iU', $route['uri'], $module); |
63
|
|
|
$docData['modules'][$module[1]][substr($route['prefix'], strlen('/api/'.$module[1].'/') - 1)][] = $route; |
64
|
|
|
|
65
|
|
|
$this->getModels($classProperties['model'], $docData); |
66
|
|
|
} |
67
|
|
|
} |
68
|
|
|
} |
69
|
|
|
|
70
|
|
|
$docData['errors'] = $this->getErrors(); |
71
|
|
|
$docData['reports'] = \Core::reports()->all(); |
72
|
|
|
\File::put(app_path('Modules/Core/Resources/api.json'), json_encode($docData)); |
73
|
|
|
} |
74
|
|
|
|
75
|
|
|
/** |
76
|
|
|
* Get list of all registered routes. |
77
|
|
|
* |
78
|
|
|
* @return collection |
79
|
|
|
*/ |
80
|
|
|
protected function getRoutes() |
81
|
|
|
{ |
82
|
|
|
return collect(\Route::getRoutes())->map(function ($route) { |
83
|
|
|
if (strpos($route->uri(), 'api/') !== false) { |
84
|
|
|
return [ |
85
|
|
|
'method' => $route->methods()[0], |
86
|
|
|
'uri' => $route->uri(), |
87
|
|
|
'action' => $route->getActionName(), |
88
|
|
|
'prefix' => $route->getPrefix() |
89
|
|
|
]; |
90
|
|
|
} |
91
|
|
|
return false; |
92
|
|
|
})->all(); |
93
|
|
|
} |
94
|
|
|
|
95
|
|
|
/** |
96
|
|
|
* Generate headers for the given route. |
97
|
|
|
* |
98
|
|
|
* @param array &$route |
99
|
|
|
* @param string $method |
100
|
|
|
* @param array $skipLoginCheck |
101
|
|
|
* @return void |
102
|
|
|
*/ |
103
|
|
|
protected function getHeaders(&$route, $method, $skipLoginCheck) |
104
|
|
|
{ |
105
|
|
|
$route['headers'] = [ |
106
|
|
|
'Accept' => 'application/json', |
107
|
|
|
'Content-Type' => 'application/json', |
108
|
|
|
'locale' => 'The language of the returned data: ar, en or all.', |
109
|
|
|
'time-zone' => 'Your locale time zone', |
110
|
|
|
]; |
111
|
|
|
|
112
|
|
|
|
113
|
|
|
if (! $skipLoginCheck || ! in_array($method, $skipLoginCheck)) { |
|
|
|
|
114
|
|
|
$route['headers']['Authorization'] = 'Bearer {token}'; |
115
|
|
|
} |
116
|
|
|
} |
117
|
|
|
|
118
|
|
|
/** |
119
|
|
|
* Generate description and params for the given route |
120
|
|
|
* based on the docblock. |
121
|
|
|
* |
122
|
|
|
* @param array &$route |
123
|
|
|
* @param \ReflectionMethod $reflectionMethod |
124
|
|
|
* @return void |
125
|
|
|
*/ |
126
|
|
|
protected function processDocBlock(&$route, $reflectionMethod) |
127
|
|
|
{ |
128
|
|
|
$factory = \phpDocumentor\Reflection\DocBlockFactory::createInstance(); |
129
|
|
|
$docblock = $factory->create($reflectionMethod->getDocComment()); |
130
|
|
|
$route['description'] = trim(preg_replace('/\s+/', ' ', $docblock->getSummary())); |
131
|
|
|
$params = $docblock->getTagsByName('param'); |
132
|
|
|
$route['returnDocBlock'] = $docblock->getTagsByName('return')[0]->getType()->getFqsen()->getName(); |
133
|
|
|
foreach ($params as $param) { |
134
|
|
|
$name = $param->getVariableName(); |
135
|
|
|
if ($name !== 'request') { |
136
|
|
|
$route['parametars'][$param->getVariableName()] = $param->getDescription()->render(); |
137
|
|
|
} |
138
|
|
|
} |
139
|
|
|
} |
140
|
|
|
|
141
|
|
|
/** |
142
|
|
|
* Generate post body for the given route. |
143
|
|
|
* |
144
|
|
|
* @param array &$route |
145
|
|
|
* @param \ReflectionMethod $reflectionMethod |
146
|
|
|
* @param array $validationRules |
147
|
|
|
* @return void |
148
|
|
|
*/ |
149
|
|
|
protected function getPostData(&$route, $reflectionMethod, $validationRules) |
150
|
|
|
{ |
151
|
|
|
if ($route['method'] == 'POST') { |
152
|
|
|
$body = $this->getMethodBody($reflectionMethod); |
153
|
|
|
|
154
|
|
|
preg_match('/\$this->validate\(\$request,([^#]+)\);/iU', $body, $match); |
155
|
|
|
if (count($match)) { |
156
|
|
|
if ($match[1] == '$this->validationRules') { |
157
|
|
|
$route['body'] = $validationRules; |
158
|
|
|
} else { |
159
|
|
|
$route['body'] = eval('return '.str_replace(',\'.$request->get(\'id\')', ',{id}\'', $match[1]).';'); |
160
|
|
|
} |
161
|
|
|
|
162
|
|
|
foreach ($route['body'] as &$rule) { |
163
|
|
|
if (strpos($rule, 'unique')) { |
164
|
|
|
$rule = substr($rule, 0, strpos($rule, 'unique') + 6); |
165
|
|
|
} elseif (strpos($rule, 'exists')) { |
166
|
|
|
$rule = substr($rule, 0, strpos($rule, 'exists') - 1); |
167
|
|
|
} |
168
|
|
|
} |
169
|
|
|
} else { |
170
|
|
|
$route['body'] = 'conditions'; |
171
|
|
|
} |
172
|
|
|
} |
173
|
|
|
} |
174
|
|
|
|
175
|
|
|
/** |
176
|
|
|
* Generate application errors. |
177
|
|
|
* |
178
|
|
|
* @return array |
179
|
|
|
*/ |
180
|
|
|
protected function getErrors() |
181
|
|
|
{ |
182
|
|
|
$errors = []; |
183
|
|
|
$reflectionClass = new \ReflectionClass('App\Modules\Core\Utl\ErrorHandler'); |
184
|
|
|
foreach ($reflectionClass->getMethods() as $method) { |
185
|
|
|
$methodName = $method->name; |
186
|
|
|
$reflectionMethod = $reflectionClass->getMethod($methodName); |
187
|
|
|
$body = $this->getMethodBody($reflectionMethod); |
188
|
|
|
|
189
|
|
|
preg_match('/\$error=\[\'status\'=>([^#]+)\,/iU', $body, $match); |
190
|
|
|
|
191
|
|
|
if (count($match)) { |
192
|
|
|
$errors[$match[1]][] = $methodName; |
193
|
|
|
} |
194
|
|
|
} |
195
|
|
|
|
196
|
|
|
return $errors; |
197
|
|
|
} |
198
|
|
|
|
199
|
|
|
/** |
200
|
|
|
* Get the given method body code. |
201
|
|
|
* |
202
|
|
|
* @param object $reflectionMethod |
203
|
|
|
* @return string |
204
|
|
|
*/ |
205
|
|
|
protected function getMethodBody($reflectionMethod) |
206
|
|
|
{ |
207
|
|
|
$filename = $reflectionMethod->getFileName(); |
208
|
|
|
$start_line = $reflectionMethod->getStartLine() - 1; |
209
|
|
|
$end_line = $reflectionMethod->getEndLine(); |
210
|
|
|
$length = $end_line - $start_line; |
211
|
|
|
$source = file($filename); |
212
|
|
|
$body = implode("", array_slice($source, $start_line, $length)); |
213
|
|
|
$body = trim(preg_replace('/\s+/', '', $body)); |
214
|
|
|
|
215
|
|
|
return $body; |
216
|
|
|
} |
217
|
|
|
|
218
|
|
|
/** |
219
|
|
|
* Get example object of all availble models. |
220
|
|
|
* |
221
|
|
|
* @param string $modelName |
222
|
|
|
* @param array $docData |
223
|
|
|
* @return string |
224
|
|
|
*/ |
225
|
|
|
protected function getModels($modelName, &$docData) |
226
|
|
|
{ |
227
|
|
|
if ($modelName && ! Arr::has($docData['models'], $modelName)) { |
228
|
|
|
$modelClass = call_user_func_array("\Core::{$modelName}", [])->modelClass; |
229
|
|
|
$model = factory($modelClass)->make(); |
230
|
|
|
$modelArr = $model->toArray(); |
231
|
|
|
|
232
|
|
|
if ($model->trans && ! $model->trans->count()) { |
233
|
|
|
$modelArr['trans'] = [ |
234
|
|
|
'en' => factory($modelClass.'Translation')->make()->toArray() |
235
|
|
|
]; |
236
|
|
|
} |
237
|
|
|
|
238
|
|
|
$docData['models'][$modelName] = json_encode($modelArr, JSON_PRETTY_PRINT); |
239
|
|
|
} |
240
|
|
|
} |
241
|
|
|
|
242
|
|
|
/** |
243
|
|
|
* Get the route response object type. |
244
|
|
|
* |
245
|
|
|
* @param string $modelName |
246
|
|
|
* @param string $method |
247
|
|
|
* @param string $returnDocBlock |
248
|
|
|
* @return array |
249
|
|
|
*/ |
250
|
|
|
protected function getResponseObject($modelName, $method, $returnDocBlock) |
251
|
|
|
{ |
252
|
|
|
$config = \CoreConfig::getConfig(); |
253
|
|
|
$relations = Arr::has($config['relations'], $modelName) ? Arr::has($config['relations'][$modelName], $method) ? $config['relations'][$modelName] : false : false; |
254
|
|
|
$modelName = call_user_func_array("\Core::{$returnDocBlock}", []) ? $returnDocBlock : $modelName; |
255
|
|
|
|
256
|
|
|
return $relations ? [$modelName => $relations && $relations[$method] ? $relations[$method] : []] : false; |
257
|
|
|
} |
258
|
|
|
} |
259
|
|
|
|
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.