Completed
Push — master ( fbdce5...2e36c4 )
by Sherif
08:54
created

GenerateDoc::handle()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 39

Duplication

Lines 0
Ratio 0 %

Importance

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