Completed
Push — master ( 392340...6e5734 )
by Sherif
07:20
created

GenerateDoc::getErrors()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 20
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 20
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 11
nc 3
nop 0
1
<?php
2
3
namespace App\Modules\V1\Core\Console\Commands;
4
5
use Illuminate\Console\Command;
6
7
class GenerateDoc extends Command
8
{
9
    /**
10
     * The name and signature of the console command.
11
     *
12
     * @var string
13
     */
14
    protected $signature = 'doc:generate';
15
16
    /**
17
     * The console command description.
18
     *
19
     * @var string
20
     */
21
    protected $description = 'Generate api documentation';
22
23
    /**
24
     * Create a new command instance.
25
     *
26
     * @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...
27
     */
28
    public function __construct()
29
    {
30
        parent::__construct();
31
    }
32
33
    /**
34
     * Execute the console command.
35
     *
36
     * @return mixed
37
     */
38
    public function handle()
39
    {
40
        $docData           = [];
41
        $docData['models'] = [];
42
        $routes            = $this->getRoutes();
43
        foreach ($routes as $route) 
44
        {
45
            if ($route) 
46
            {
47
                $actoinArray       = explode('@', $route['action']);
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    = array_key_exists('skipLoginCheck', $classProperties) ? $classProperties['skipLoginCheck'] : false;
56
                $validationRules   = array_key_exists('validationRules', $classProperties) ? $classProperties['validationRules'] : false;
57
                
58
                $route['response'] = $this->getResponseObject($classProperties['model'], $route['name']);
59
60
                $this->processDocBlock($route, $reflectionMethod);
61
                $this->getHeaders($route, $reflectionClass, $method, $skipLoginCheck);
62
                $this->getPostData($route, $reflectionMethod, $validationRules);
63
64
                preg_match('/api\/v1\/([^#]+)\//iU', $route['uri'], $module);
65
                preg_match('/api\/v1\/' . $module[1] . '\/([^#]+)\//iU', $route['uri'], $model);
66
                $docData['modules'][$module[1]][$model[1]][] = $route;
67
68
                $this->getModels($classProperties['model'], $docData);
69
            }
70
        }
71
        
72
        $docData['errors'] = $this->getErrors();
73
        \File::put(app_path('Modules/V1/Core/Resources/api.json'), json_encode($docData));
74
    }
75
76
    /**
77
     * Get list of all registered routes.
78
     * 
79
     * @return collection
80
     */
81
    protected function getRoutes()
82
    {
83
        return collect(\Route::getRoutes())->map(function ($route) {
84
            if (strpos($route->uri(), 'api/v') !== false) 
85
            {
86
                return [
87
                    'method' => $route->methods()[0],
88
                    'uri'    => $route->uri(),
89
                    'action' => $route->getActionName()
90
                ];
91
            }
92
            return false;
93
        })->all();
94
    }
95
96
    /**
97
     * Generate headers for the given route.
98
     * 
99
     * @param  array  &$route
100
     * @param  object $reflectionClass
101
     * @param  string $method
102
     * @param  array  $skipLoginCheck
103
     * @return void
104
     */
105
    protected function getHeaders(&$route, $reflectionClass, $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-diff' => 'Timezone difference between UTC and Local Time',
112
        ];
113
114
115
        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...
116
        {
117
            $route['headers']['Authrization'] = '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  object $reflectionMethod]
0 ignored issues
show
Documentation introduced by
There is no parameter named $reflectionMethod]. Did you maybe mean $reflectionMethod?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
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
        foreach ($params as $param) 
136
        {
137
            $name = $param->getVariableName();
138
            if ($name !== 'request') 
139
            {
140
                $route['parametars'][$param->getVariableName()] = $param->getDescription()->render();
141
            }
142
        }
143
    }
144
145
    /**
146
     * Generate post body for the given route.
147
     * 
148
     * @param  array  &$route
149
     * @param  object $reflectionMethod
150
     * @param  array  $validationRules
151
     * @return void
152
     */
153
    protected function getPostData(&$route, $reflectionMethod, $validationRules)
154
    {
155
        if ($route['method'] == 'POST') 
156
        {
157
            $body = $this->getMethodBody($reflectionMethod);
158
159
            preg_match('/\$this->validate\(\$request,([^#]+)\);/iU', $body, $match);
160
            if (count($match)) 
161
            {
162
                if ($match[1] == '$this->validationRules')
163
                {
164
                    $route['body'] = $validationRules;
165
                }
166
                else
167
                {
168
                    $route['body'] = eval('return ' . $match[1] . ';');
0 ignored issues
show
Coding Style introduced by
It is generally not recommended to use eval unless absolutely required.

On one hand, eval might be exploited by malicious users if they somehow manage to inject dynamic content. On the other hand, with the emergence of faster PHP runtimes like the HHVM, eval prevents some optimization that they perform.

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