Completed
Push — master ( ec2395...0dfd17 )
by Sherif
02:09
created

GenerateDoc   A

Complexity

Total Complexity 37

Size/Duplication

Total Lines 254
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 0
Metric Value
wmc 37
lcom 1
cbo 5
dl 0
loc 254
rs 9.44
c 0
b 0
f 0

10 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A handle() 0 38 5
A getRoutes() 0 14 2
A getHeaders() 0 14 3
A processDocBlock() 0 14 3
B getPostData() 0 25 7
A getErrors() 0 18 3
A getMethodBody() 0 12 1
A getModels() 0 16 5
B getResponseObject() 0 8 7
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
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
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
                    dd($classProperties);
59
                    $this->processDocBlock($route, $reflectionMethod);
60
                    $this->getHeaders($route, $method, $skipLoginCheck);
61
                    $this->getPostData($route, $reflectionMethod, $validationRules);
62
63
                    $route['response'] = $this->getResponseObject($classProperties['model'], $route['name'], $route['returnDocBlock']);
64
65
                    preg_match('/api\/([^#]+)\//iU', $route['uri'], $module);
66
                    $docData['modules'][$module[1]][substr($route['prefix'], strlen('/api/'.$module[1].'/') - 1)][] = $route;
67
68
                    $this->getModels($classProperties['model'], $docData);
69
                }
70
            }
71
        }
72
        
73
        $docData['errors']  = $this->getErrors();
74
        $docData['reports'] = \Core::reports()->all();
75
        \File::put(app_path('Modules/Core/Resources/api.json'), json_encode($docData));
76
    }
77
78
    /**
79
     * Get list of all registered routes.
80
     *
81
     * @return collection
82
     */
83
    protected function getRoutes()
84
    {
85
        return collect(\Route::getRoutes())->map(function ($route) {
86
            if (strpos($route->uri(), 'api/') !== false) {
87
                return [
88
                    'method' => $route->methods()[0],
89
                    'uri'    => $route->uri(),
90
                    'action' => $route->getActionName(),
91
                    'prefix' => $route->getPrefix()
92
                ];
93
            }
94
            return false;
95
        })->all();
96
    }
97
98
    /**
99
     * Generate headers for the given route.
100
     *
101
     * @param  array  &$route
102
     * @param  string $method
103
     * @param  array  $skipLoginCheck
104
     * @return void
105
     */
106
    protected function getHeaders(&$route, $method, $skipLoginCheck)
107
    {
108
        $route['headers'] = [
109
        'Accept'       => 'application/json',
110
        'Content-Type' => 'application/json',
111
        'locale'       => 'The language of the returned data: ar, en or all.',
112
        'time-zone'    => 'Your locale time zone',
113
        ];
114
115
116
        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...
117
            $route['headers']['Authorization'] = 'Bearer {token}';
118
        }
119
    }
120
121
    /**
122
     * Generate description and params for the given route
123
     * based on the docblock.
124
     *
125
     * @param  array  &$route
126
     * @param  \ReflectionMethod $reflectionMethod
127
     * @return void
128
     */
129
    protected function processDocBlock(&$route, $reflectionMethod)
130
    {
131
        $factory                 = \phpDocumentor\Reflection\DocBlockFactory::createInstance();
132
        $docblock                = $factory->create($reflectionMethod->getDocComment());
133
        $route['description']    = trim(preg_replace('/\s+/', ' ', $docblock->getSummary()));
134
        $params                  = $docblock->getTagsByName('param');
135
        $route['returnDocBlock'] = $docblock->getTagsByName('return')[0]->getType()->getFqsen()->getName();
136
        foreach ($params as $param) {
137
            $name = $param->getVariableName();
138
            if ($name !== 'request') {
139
                $route['parametars'][$param->getVariableName()] = $param->getDescription()->render();
140
            }
141
        }
142
    }
143
144
    /**
145
     * Generate post body for the given route.
146
     *
147
     * @param  array  &$route
148
     * @param  \ReflectionMethod $reflectionMethod
149
     * @param  array  $validationRules
150
     * @return void
151
     */
152
    protected function getPostData(&$route, $reflectionMethod, $validationRules)
153
    {
154
        if ($route['method'] == 'POST') {
155
            $body = $this->getMethodBody($reflectionMethod);
156
157
            preg_match('/\$this->validate\(\$request,([^#]+)\);/iU', $body, $match);
158
            if (count($match)) {
159
                if ($match[1] == '$this->validationRules') {
160
                    $route['body'] = $validationRules;
161
                } else {
162
                    $route['body'] = eval('return '.str_replace(',\'.$request->get(\'id\')', ',{id}\'', $match[1]).';');
163
                }
164
165
                foreach ($route['body'] as &$rule) {
166
                    if (strpos($rule, 'unique')) {
167
                        $rule = substr($rule, 0, strpos($rule, 'unique') + 6);
168
                    } elseif (strpos($rule, 'exists')) {
169
                        $rule = substr($rule, 0, strpos($rule, 'exists') - 1);
170
                    }
171
                }
172
            } else {
173
                $route['body'] = 'conditions';
174
            }
175
        }
176
    }
177
178
    /**
179
     * Generate application errors.
180
     *
181
     * @return array
182
     */
183
    protected function getErrors()
184
    {
185
        $errors          = [];
186
        $reflectionClass = new \ReflectionClass('App\Modules\Core\Utl\ErrorHandler');
187
        foreach ($reflectionClass->getMethods() as $method) {
188
            $methodName       = $method->name;
189
            $reflectionMethod = $reflectionClass->getMethod($methodName);
190
            $body             = $this->getMethodBody($reflectionMethod);
191
192
            preg_match('/\$error=\[\'status\'=>([^#]+)\,/iU', $body, $match);
193
194
            if (count($match)) {
195
                $errors[$match[1]][] = $methodName;
196
            }
197
        }
198
199
        return $errors;
200
    }
201
202
    /**
203
     * Get the given method body code.
204
     *
205
     * @param  object $reflectionMethod
206
     * @return string
207
     */
208
    protected function getMethodBody($reflectionMethod)
209
    {
210
        $filename   = $reflectionMethod->getFileName();
211
        $start_line = $reflectionMethod->getStartLine() - 1;
212
        $end_line   = $reflectionMethod->getEndLine();
213
        $length     = $end_line - $start_line;
214
        $source     = file($filename);
215
        $body       = implode("", array_slice($source, $start_line, $length));
216
        $body       = trim(preg_replace('/\s+/', '', $body));
217
218
        return $body;
219
    }
220
221
    /**
222
     * Get example object of all availble models.
223
     *
224
     * @param  string $modelName
225
     * @param  array  $docData
226
     * @return string
227
     */
228
    protected function getModels($modelName, &$docData)
229
    {
230
        if ($modelName && ! Arr::has($docData['models'], $modelName)) {
231
            $modelClass = call_user_func_array("\Core::{$modelName}", [])->modelClass;
232
            $model      = factory($modelClass)->make();
233
            $modelArr   = $model->toArray();
234
235
            if ($model->trans && ! $model->trans->count()) {
236
                $modelArr['trans'] = [
237
                    'en' => factory($modelClass.'Translation')->make()->toArray()
238
                ];
239
            }
240
241
            $docData['models'][$modelName] = json_encode($modelArr, JSON_PRETTY_PRINT);
242
        }
243
    }
244
245
    /**
246
     * Get the route response object type.
247
     *
248
     * @param  string $modelName
249
     * @param  string $method
250
     * @param  string $returnDocBlock
251
     * @return array
252
     */
253
    protected function getResponseObject($modelName, $method, $returnDocBlock)
254
    {
255
        $config    = \CoreConfig::getConfig();
256
        $relations = Arr::has($config['relations'], $modelName) ? Arr::has($config['relations'][$modelName], $method) ? $config['relations'][$modelName] : false : false;
257
        $modelName = call_user_func_array("\Core::{$returnDocBlock}", []) ? $returnDocBlock : $modelName;
258
259
        return $relations ? [$modelName => $relations && $relations[$method] ? $relations[$method] : []] : false;
260
    }
261
}
262