Completed
Push — master ( eb2586...ec2395 )
by Sherif
01:59
created

GenerateDoc::handle()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 37

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 37
rs 9.0168
c 0
b 0
f 0
cc 5
nc 5
nop 0
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
     * Init new object.
26
     *
27
     * @return  void
28
     */
29
    public function __construct()
30
    {
31
        parent::__construct();
32
    }
33
34
    /**
35
     * Execute the console command.
36
     *
37
     * @return mixed
38
     */
39
    public function handle()
40
    {
41
        $docData           = [];
42
        $docData['models'] = [];
43
        $routes            = $this->getRoutes();
44
        foreach ($routes as $route) {
45
            if ($route) {
46
                $actoinArray = explode('@', $route['action']);
47
                if (Arr::get($actoinArray, 1, false)) {
48
                    $controller       = $actoinArray[0];
49
                    $method           = $actoinArray[1];
50
                    $route['name']    = $method !== 'index' ? $method : 'list';
51
                    
52
                    $reflectionClass  = new \ReflectionClass($controller);
53
                    $reflectionMethod = $reflectionClass->getMethod($method);
54
                    $classProperties  = $reflectionClass->getDefaultProperties();
55
                    $skipLoginCheck   = Arr::get($classProperties, 'skipLoginCheck', false);
56
                    $validationRules  = Arr::get($classProperties, 'validationRules', false);
57
58
                    $this->processDocBlock($route, $reflectionMethod);
59
                    $this->getHeaders($route, $method, $skipLoginCheck);
60
                    $this->getPostData($route, $reflectionMethod, $validationRules);
61
62
                    $route['response'] = $this->getResponseObject($classProperties['model'], $route['name'], $route['returnDocBlock']);
63
64
                    preg_match('/api\/([^#]+)\//iU', $route['uri'], $module);
65
                    $docData['modules'][$module[1]][substr($route['prefix'], strlen('/api/'.$module[1].'/') - 1)][] = $route;
66
67
                    $this->getModels($classProperties['model'], $docData);
68
                }
69
            }
70
        }
71
        
72
        $docData['errors']  = $this->getErrors();
73
        $docData['reports'] = \Core::reports()->all();
74
        \File::put(app_path('Modules/Core/Resources/api.json'), json_encode($docData));
75
    }
76
77
    /**
78
     * Get list of all registered routes.
79
     *
80
     * @return collection
81
     */
82
    protected function getRoutes()
83
    {
84
        return collect(\Route::getRoutes())->map(function ($route) {
85
            if (strpos($route->uri(), 'api/') !== false) {
86
                return [
87
                    'method' => $route->methods()[0],
88
                    'uri'    => $route->uri(),
89
                    'action' => $route->getActionName(),
90
                    'prefix' => $route->getPrefix()
91
                ];
92
            }
93
            return false;
94
        })->all();
95
    }
96
97
    /**
98
     * Generate headers for the given route.
99
     *
100
     * @param  array  &$route
101
     * @param  string $method
102
     * @param  array  $skipLoginCheck
103
     * @return void
104
     */
105
    protected function getHeaders(&$route, $method, $skipLoginCheck)
106
    {
107
        $route['headers'] = [
108
        'Accept'       => 'application/json',
109
        'Content-Type' => 'application/json',
110
        'locale'       => 'The language of the returned data: ar, en or all.',
111
        'time-zone'    => 'Your locale time zone',
112
        ];
113
114
115
        if (! $skipLoginCheck || ! in_array($method, $skipLoginCheck)) {
116
            $route['headers']['Authorization'] = 'Bearer {token}';
117
        }
118
    }
119
120
    /**
121
     * Generate description and params for the given route
122
     * based on the docblock.
123
     *
124
     * @param  array  &$route
125
     * @param  \ReflectionMethod $reflectionMethod
126
     * @return void
127
     */
128
    protected function processDocBlock(&$route, $reflectionMethod)
129
    {
130
        $factory                 = \phpDocumentor\Reflection\DocBlockFactory::createInstance();
131
        $docblock                = $factory->create($reflectionMethod->getDocComment());
132
        $route['description']    = trim(preg_replace('/\s+/', ' ', $docblock->getSummary()));
133
        $params                  = $docblock->getTagsByName('param');
134
        $route['returnDocBlock'] = $docblock->getTagsByName('return')[0]->getType()->getFqsen()->getName();
135
        foreach ($params as $param) {
136
            $name = $param->getVariableName();
137
            if ($name !== 'request') {
138
                $route['parametars'][$param->getVariableName()] = $param->getDescription()->render();
139
            }
140
        }
141
    }
142
143
    /**
144
     * Generate post body for the given route.
145
     *
146
     * @param  array  &$route
147
     * @param  \ReflectionMethod $reflectionMethod
148
     * @param  array  $validationRules
149
     * @return void
150
     */
151
    protected function getPostData(&$route, $reflectionMethod, $validationRules)
152
    {
153
        if ($route['method'] == 'POST') {
154
            $body = $this->getMethodBody($reflectionMethod);
155
156
            preg_match('/\$this->validate\(\$request,([^#]+)\);/iU', $body, $match);
157
            if (count($match)) {
158
                if ($match[1] == '$this->validationRules') {
159
                    $route['body'] = $validationRules;
160
                } else {
161
                    $route['body'] = eval('return '.str_replace(',\'.$request->get(\'id\')', '$this->get('id')\'', $match[1]).';');
0 ignored issues
show
Bug introduced by
This code did not parse for me. Apparently, there is an error somewhere around this line:

Syntax error, unexpected T_STRING, expecting ',' or ')'
Loading history...
162
                }
163
164
                foreach ($route['body'] as &$rule) {
165
                    if (strpos($rule, 'unique')) {
166
                        $rule = substr($rule, 0, strpos($rule, 'unique') + 6);
167
                    } elseif (strpos($rule, 'exists')) {
168
                        $rule = substr($rule, 0, strpos($rule, 'exists') - 1);
169
                    }
170
                }
171
            } else {
172
                $route['body'] = 'conditions';
173
            }
174
        }
175
    }
176
177
    /**
178
     * Generate application errors.
179
     *
180
     * @return array
181
     */
182
    protected function getErrors()
183
    {
184
        $errors          = [];
185
        $reflectionClass = new \ReflectionClass('App\Modules\Core\Utl\ErrorHandler');
186
        foreach ($reflectionClass->getMethods() as $method) {
187
            $methodName       = $method->name;
188
            $reflectionMethod = $reflectionClass->getMethod($methodName);
189
            $body             = $this->getMethodBody($reflectionMethod);
190
191
            preg_match('/\$error=\[\'status\'=>([^#]+)\,/iU', $body, $match);
192
193
            if (count($match)) {
194
                $errors[$match[1]][] = $methodName;
195
            }
196
        }
197
198
        return $errors;
199
    }
200
201
    /**
202
     * Get the given method body code.
203
     *
204
     * @param  object $reflectionMethod
205
     * @return string
206
     */
207
    protected function getMethodBody($reflectionMethod)
208
    {
209
        $filename   = $reflectionMethod->getFileName();
210
        $start_line = $reflectionMethod->getStartLine() - 1;
211
        $end_line   = $reflectionMethod->getEndLine();
212
        $length     = $end_line - $start_line;
213
        $source     = file($filename);
214
        $body       = implode("", array_slice($source, $start_line, $length));
215
        $body       = trim(preg_replace('/\s+/', '', $body));
216
217
        return $body;
218
    }
219
220
    /**
221
     * Get example object of all availble models.
222
     *
223
     * @param  string $modelName
224
     * @param  array  $docData
225
     * @return string
226
     */
227
    protected function getModels($modelName, &$docData)
228
    {
229
        if ($modelName && ! Arr::has($docData['models'], $modelName)) {
230
            $modelClass = call_user_func_array("\Core::{$modelName}", [])->modelClass;
231
            $model      = factory($modelClass)->make();
232
            $modelArr   = $model->toArray();
233
234
            if ($model->trans && ! $model->trans->count()) {
235
                $modelArr['trans'] = [
236
                    'en' => factory($modelClass.'Translation')->make()->toArray()
237
                ];
238
            }
239
240
            $docData['models'][$modelName] = json_encode($modelArr, JSON_PRETTY_PRINT);
241
        }
242
    }
243
244
    /**
245
     * Get the route response object type.
246
     *
247
     * @param  string $modelName
248
     * @param  string $method
249
     * @param  string $returnDocBlock
250
     * @return array
251
     */
252
    protected function getResponseObject($modelName, $method, $returnDocBlock)
253
    {
254
        $config    = \CoreConfig::getConfig();
255
        $relations = Arr::has($config['relations'], $modelName) ? Arr::has($config['relations'][$modelName], $method) ? $config['relations'][$modelName] : false : false;
256
        $modelName = call_user_func_array("\Core::{$returnDocBlock}", []) ? $returnDocBlock : $modelName;
257
258
        return $relations ? [$modelName => $relations && $relations[$method] ? $relations[$method] : []] : false;
259
    }
260
}
261