Completed
Push — master ( fed21d...0c3b23 )
by Sherif
10:50
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
     * 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)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $skipLoginCheck of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

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.

Loading history...
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