Completed
Push — master ( 8b2ccd...ab2185 )
by Sherif
14:39
created

GenerateDocCommand   A

Complexity

Total Complexity 42

Size/Duplication

Total Lines 279
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 0
Metric Value
wmc 42
lcom 1
cbo 5
dl 0
loc 279
rs 9.0399
c 0
b 0
f 0

10 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
B handle() 0 43 6
A getRoutes() 0 14 2
A getHeaders() 0 14 3
A processDocBlock() 0 22 4
B getPostData() 0 23 7
A getErrors() 0 22 4
A getMethodBody() 0 12 1
B getModels() 0 21 7
B getResponseObject() 0 8 7

How to fix   Complexity   

Complex Class

Complex classes like GenerateDocCommand often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use GenerateDocCommand, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace App\Modules\Core\Console\Commands;
4
5
use Illuminate\Console\Command;
6
use Illuminate\Support\Arr;
7
use App\Modules\Reporting\Services\ReportService;
8
9
class GenerateDocCommand extends Command
10
{
11
    /**
12
     * The name and signature of the console command.
13
     *
14
     * @var string
15
     */
16
    protected $signature = 'doc:generate';
17
18
    /**
19
     * The console command description.
20
     *
21
     * @var string
22
     */
23
    protected $description = 'Generate api documentation';
24
25
    /**
26
     * @var ReprotService
27
     */
28
    protected $reportService;
29
30
    /**
31
     * Init new object.
32
     *
33
     * @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...
34
     */
35
    public function __construct(ReportService $reportService)
36
    {
37
        $this->reportService = $reportService;
0 ignored issues
show
Documentation Bug introduced by
It seems like $reportService of type object<App\Modules\Repor...Services\ReportService> is incompatible with the declared type object<App\Modules\Core\...Commands\ReprotService> of property $reportService.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

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