Completed
Push — master ( 2ce496...5c1ad5 )
by Sherif
06:14
created

GenerateDoc::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
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
        $routes  = $this->getRoutes();
42
        foreach ($routes as $route) 
43
        {
44
            if ($route) 
45
            {
46
                $actoinArray      = explode('@', $route['action']);
47
                $controller       = $actoinArray[0];
48
                $method           = $actoinArray[1];
49
                $route['name']    = $method !== 'index' ? $method : 'list';
50
                
51
                $reflectionClass  = new \ReflectionClass($controller);
52
                $reflectionMethod = $reflectionClass->getMethod($method);
53
                $classProperties  = $reflectionClass->getDefaultProperties();
54
                $skipLoginCheck   = array_key_exists('skipLoginCheck', $classProperties) ? $classProperties['skipLoginCheck'] : false;
55
                $validationRules  = array_key_exists('validationRules', $classProperties) ? $classProperties['validationRules'] : false;
56
57
                $this->processDocBlock($route, $reflectionMethod);
58
                $this->getHeaders($route, $reflectionClass, $method, $skipLoginCheck);
59
                $this->getPostData($route, $reflectionMethod, $validationRules);
60
61
                preg_match('/api\/v1\/([^#]+)\//iU', $route['uri'], $module);
62
                preg_match('/api\/v1\/' . $module[1] . '\/([^#]+)\//iU', $route['uri'], $model);
63
                $docData['modules'][$module[1]][$model[1]][] = $route;
64
            }
65
        }
66
        $docData['errors'] = $this->getErrors();
67
        \File::put(app_path('Modules/V1/Core/Resources/api.json'), json_encode($docData));
68
    }
69
70
    /**
71
     * Get list of all registered routes.
72
     * 
73
     * @return collection
74
     */
75
    protected function getRoutes()
76
    {
77
        return collect(\Route::getRoutes())->map(function ($route) {
78
            if (strpos($route->uri(), 'api/v') !== false) 
79
            {
80
                return [
81
                    'method' => $route->methods()[0],
82
                    'uri'    => $route->uri(),
83
                    'action' => $route->getActionName()
84
                ];
85
            }
86
            return false;
87
        })->all();
88
    }
89
90
    /**
91
     * Generate headers for the given route.
92
     * 
93
     * @param  array  &$route
94
     * @param  object $reflectionClass
95
     * @param  string $method
96
     * @param  array  $skipLoginCheck
97
     * @return void
98
     */
99
    protected function getHeaders(&$route, $reflectionClass, $method, $skipLoginCheck)
100
    {
101
        $route['headers'] = [
102
        'Accept'         => 'application/json',
103
        'Content-Type'   => 'application/json',
104
        'locale'         => 'The language of the returned data: ar, en or all.',
105
        'time-zone-diff' => 'Timezone difference between UTC and Local Time',
106
        ];
107
108
109
        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...
110
        {
111
            $route['headers']['Authrization'] = 'bearer {token}';
112
        }
113
    }
114
115
    /**
116
     * Generate description and params for the given route
117
     * based on the docblock.
118
     * 
119
     * @param  array  &$route
120
     * @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...
121
     * @return void
122
     */
123
    protected function processDocBlock(&$route, $reflectionMethod)
124
    {
125
        $factory              = \phpDocumentor\Reflection\DocBlockFactory::createInstance();
126
        $docblock             = $factory->create($reflectionMethod->getDocComment());
127
        $route['description'] = trim(preg_replace('/\s+/', ' ', $docblock->getSummary()));
128
        $params               = $docblock->getTagsByName('param');
129
        foreach ($params as $param) 
130
        {
131
            $name = $param->getVariableName();
132
            if ($name !== 'request') 
133
            {
134
                $route['parametars'][$param->getVariableName()] = $param->getDescription()->render();
135
            }
136
        }
137
    }
138
139
    /**
140
     * Generate post body for the given route.
141
     * 
142
     * @param  array  &$route
143
     * @param  object $reflectionMethod
144
     * @param  array  $validationRules
145
     * @return void
146
     */
147
    protected function getPostData(&$route, $reflectionMethod, $validationRules)
148
    {
149
        if ($route['method'] == 'POST') 
150
        {
151
            $body = $this->getMethodBody($reflectionMethod);
152
153
            preg_match('/\$this->validate\(\$request,([^#]+)\);/iU', $body, $match);
154
            if (count($match)) 
155
            {
156
                if ($match[1] == '$this->validationRules')
157
                {
158
                    $route['body'] = $validationRules;
159
                }
160
                else
161
                {
162
                    $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...
163
                }
164
165
                foreach ($route['body'] as &$rule) 
166
                {
167
                    if(strpos($rule, 'unique'))
168
                    {
169
                        $rule = substr($rule, 0, strpos($rule, 'unique') + 6);
170
                    }
171
                    elseif(strpos($rule, 'exists'))
172
                    {
173
                        $rule = substr($rule, 0, strpos($rule, 'exists') - 1);
174
                    }
175
                }
176
            }
177
            else
178
            {
179
                $route['body'] = 'conditions';
180
            }
181
        }
182
    }
183
184
    /**
185
     * Generate application errors.
186
     * 
187
     * @return array
188
     */
189
    protected function getErrors()
190
    {
191
        $errors          = [];
192
        $reflectionClass = new \ReflectionClass('App\Modules\V1\Core\Utl\ErrorHandler');
193
        foreach ($reflectionClass->getMethods() as $method) 
194
        {
195
            $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...
196
            $reflectionMethod = $reflectionClass->getMethod($methodName);
197
            $body             = $this->getMethodBody($reflectionMethod);
198
199
            preg_match('/\$error=\[\'status\'=>([^#]+)\,/iU', $body, $match);
200
201
            if (count($match)) 
202
            {
203
                $errors[$match[1]][] = $methodName;
204
            }
205
        }
206
207
        return $errors;
208
    }
209
210
    /**
211
     * Get the fiven method body code.
212
     * 
213
     * @param  object $reflectionMethod
214
     * @return string
215
     */
216
    protected function getMethodBody($reflectionMethod)
217
    {
218
        $filename   = $reflectionMethod->getFileName();
219
        $start_line = $reflectionMethod->getStartLine() - 1;
220
        $end_line   = $reflectionMethod->getEndLine();
221
        $length     = $end_line - $start_line;         
222
        $source     = file($filename);
223
        $body       = implode("", array_slice($source, $start_line, $length));
224
        $body       = trim(preg_replace('/\s+/', '', $body));
225
226
        return $body;
227
    }
228
}
229