Completed
Push — master ( 487d2a...efc144 )
by Fran
03:53
created

DocumentorService::extractApiInfo()   B

Complexity

Conditions 5
Paths 2

Size

Total Lines 21
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 5
eloc 13
c 3
b 0
f 0
nc 2
nop 1
dl 0
loc 21
rs 8.7624
1
<?php
2
    namespace PSFS\services;
3
4
    use PSFS\base\Logger;
5
    use PSFS\base\Service;
6
    use Symfony\Component\Finder\Finder;
7
8
    /**
9
     * Class DocumentorService
10
     * @package PSFS\services
11
     */
12
    class DocumentorService extends Service
13
    {
14
        const DTO_INTERFACE = '\\PSFS\\base\\dto\\Dto';
15
        const MODEL_INTERFACE = '\\Propel\\Runtime\\ActiveRecord\\ActiveRecordInterface';
16
        /**
17
         * @Inyectable
18
         * @var \PSFS\base\Router route
19
         */
20
        protected $route;
21
22
        /**
23
         * Method that extract all modules
24
         * @return array
25
         */
26
        public function getModules()
27
        {
28
            $modules = [];
29
            $domains = $this->route->getDomains();
30
            if (count($domains)) {
31
                foreach (array_keys($domains) as $domain) {
32
                    try {
33
                        if (!preg_match('/^\@ROOT/i', $domain)) {
34
                            $modules[] = str_replace('/', '', str_replace('@', '', $domain));
35
                        }
36
                    } catch (\Exception $e) {
37
                        $modules[] = $e->getMessage();
38
                    }
39
                }
40
            }
41
42
            return $modules;
43
        }
44
45
        /**
46
         * Method that extract all endpoints for each module
47
         *
48
         * @param string $module
49
         *
50
         * @return array
51
         */
52
        public function extractApiEndpoints($module)
53
        {
54
            $module_path = CORE_DIR . DIRECTORY_SEPARATOR . $module . DIRECTORY_SEPARATOR . "Api";
55
            $endpoints = [];
56
            if (file_exists($module_path)) {
57
                $finder = new Finder();
58
                $finder->files()->depth('== 0')->in($module_path)->name('*.php');
59
                if (count($finder)) {
60
                    /** @var \SplFileInfo $file */
61
                    foreach ($finder as $file) {
62
                        $namespace = "\\{$module}\\Api\\" . str_replace('.php', '', $file->getFilename());
63
                        $endpoints[$namespace] = $this->extractApiInfo($namespace);
64
                    }
65
                }
66
            }
67
68
            return $endpoints;
69
        }
70
71
        /**
72
         * Method that extract all the endpoit information by reflection
73
         *
74
         * @param string $namespace
75
         *
76
         * @return array
77
         */
78
        public function extractApiInfo($namespace)
79
        {
80
            $info = [];
81
            $reflection = new \ReflectionClass($namespace);
82
            $publicMethods = $reflection->getMethods(\ReflectionMethod::IS_PUBLIC);
83
            if (count($publicMethods)) {
84
                /** @var \ReflectionMethod $method */
85
                foreach ($publicMethods as $method) {
86
                    try {
87
                        $mInfo = $this->extractMethodInfo($namespace, $method, $reflection);
88
                        if(null !== $mInfo) {
89
                            $info[] = $mInfo;
90
                        }
91
                    } catch (\Exception $e) {
92
                        Logger::getInstance()->errorLog($e->getMessage());
93
                    }
94
                }
95
            }
96
97
            return $info;
98
        }
99
100
        /**
101
         * Extract route from doc comments
102
         *
103
         * @param string $comments
104
         *
105
         * @return string
106
         */
107
        protected function extractRoute($comments = '')
108
        {
109
            $route = '';
110
            preg_match('/@route\ (.*)\n/i', $comments, $route);
111
112
            return $route[1];
113
        }
114
115
        /**
116
         * Extract method from doc comments
117
         *
118
         * @param string $comments
119
         *
120
         * @return string
121
         */
122
        protected function extractMethod($comments = '')
123
        {
124
            $method = 'GET';
125
            preg_match('/@(get|post|put|delete)\n/i', $comments, $method);
126
127
            return strtoupper($method[1]);
128
        }
129
130
        /**
131
         * Extract visibility from doc comments
132
         *
133
         * @param string $comments
134
         *
135
         * @return boolean
136
         */
137
        protected function extractVisibility($comments = '')
138
        {
139
            $visible = TRUE;
140
            preg_match('/@visible\ (true|false)\n/i', $comments, $visibility);
141
            if (count($visibility)) {
142
                $visible = !('false' == $visibility[1]);
143
            }
144
145
            return $visible;
146
        }
147
148
        /**
149
         * Method that extract the description for the endpoint
150
         *
151
         * @param string $comments
152
         *
153
         * @return string
154
         */
155
        protected function extractDescription($comments = '')
156
        {
157
            $description = '';
158
            $docs = explode("\n", $comments);
159
            if (count($docs)) {
160
                foreach ($docs as &$doc) {
161 View Code Duplication
                    if (!preg_match('/(\*\*|\@)/i', $doc) && preg_match('/\*\ /i', $doc)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
162
                        $doc = explode('* ', $doc);
163
                        $description = $doc[1];
164
                    }
165
                }
166
            }
167
168
            return $description;
169
        }
170
171
        /**
172
         * Method that extract the type of a variable
173
         * @param string $comments
174
         *
175
         * @return string
176
         */
177
        protected function extractVarType($comments = '')
178
        {
179
            $type = 'string';
180
            preg_match('/@var\ (.*) (.*)\n/i', $comments, $varType);
181
            if (count($varType)) {
182
                $aux = trim($varType[1]);
183
                $type = str_replace(' ', '', strlen($aux) > 0 ? $varType[1] : $varType[2]);
184
            }
185
            return $type;
186
        }
187
188
        /**
189
         * Method that extract the payload for the endpoint
190
         *
191
         * @param string $model
192
         * @param string $comments
193
         *
194
         * @return array
195
         */
196
        protected function extractPayload($model, $comments = '')
197
        {
198
            $payload = [];
199
            preg_match('/@payload\ (.*)\n/i', $comments, $doc);
200
            if (count($doc)) {
201
                $namespace = str_replace('{__API__}', $model, $doc[1]);
202
                $payload = $this->extractModelFields($namespace);
203
            }
204
            return $payload;
205
        }
206
207
        /**
208
         * Extract return class for api endpoint
209
         * @param string $model
210
         * @param string $comments
211
         *
212
         * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be array? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
213
         */
214
        protected function extractReturn($model, $comments = '')
215
        {
216
            $return = [];
217
            preg_match('/\@return\ (.*)\ (.*)\n/i', $comments, $returnTypes);
218
            if (count($returnTypes)) {
219
                $closure = $modelDto = [];
220
                $isArray = false;
221
                foreach($returnTypes as $returnType) {
222
                    try {
223
                        if (false === strpos($returnType, '@')) {
224
                            $class = str_replace('{__API__}', $model, $returnType);
225
                            if (false !== strpos($class, '[') && false !== strpos($class, ']')) {
226
                                $class = str_replace(']', '', str_replace('[', '', $class));
227
                                $isArray = true;
228
                            }
229
                            if (class_exists($class)) {
230
                                $reflector = new \ReflectionClass($class);
231
                                if ($reflector->isSubclassOf(self::DTO_INTERFACE)) {
232
                                    foreach($reflector->getProperties(\ReflectionMethod::IS_PUBLIC) as $property) {
233
                                        $closure[$property->getName()] = $this->extractVarType($property->getDocComment());
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 123 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
234
                                    }
235
                                } else {
236
                                    $modelDto = $this->extractModelFields($class);
237
                                }
238
                            }
239
                        }
240
                    } catch(\Exception $e) {
241
                        Logger::getInstance()->errorLog($e->getMessage());
242
                    }
243
                }
244
                $closure['data'] = ($isArray) ? [$modelDto] : $modelDto;
245
                $return = $closure;
246
            }
247
            return $return;
248
        }
249
250
        /**
251
         * Extract all fields from a ActiveResource model
252
         * @param string $namespace
253
         *
254
         * @return mixed
255
         */
256
        protected function extractModelFields($namespace)
257
        {
258
            $payload = [];
259
            try {
260
                $reflector = new \ReflectionClass($namespace);
261
                // Checks if reflector is a subclass of propel ActiveRecords
262
                if (NULL !== $reflector && $reflector->isSubclassOf(self::MODEL_INTERFACE)) {
263
                    $tableMap = $namespace::TABLE_MAP;
264
                    $fieldNames = $tableMap::getFieldNames();
265
                    if (count($fieldNames)) {
266
                        foreach ($fieldNames as $field) {
267
                            $variable = $reflector->getProperty(strtolower($field));
268
                            $varDoc = $variable->getDocComment();
269
                            $payload[$field] = $this->extractVarType($varDoc);
270
                        }
271
                    }
272
                }
273
            } catch(\Exception $e) {
274
                Logger::getInstance()->errorLog($e->getMessage());
275
            }
276
277
            return $payload;
278
        }
279
280
        /**
281
         * Method that extract all the needed info for each method in each API
282
         * @param string $namespace
283
         * @param \ReflectionMethod $method
284
         * @param \ReflectionClass $reflection
285
         *
286
         * @return array
0 ignored issues
show
Documentation introduced by
Should the return type not be array<string,string|array>|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
287
         */
288
        protected function extractMethodInfo($namespace, $method, $reflection)
289
        {
290
            $methodInfo = null;
291
            $docComments = $method->getDocComment();
292
            $shortName = $reflection->getShortName();
293
            $modelNamespace = str_replace('Api', 'Models', $namespace);
294
            if (FALSE !== $docComments && preg_match('/\@route\ /i', $docComments)) {
295
                $visibility = $this->extractVisibility($docComments);
296
                $route = str_replace('{__API__}', $shortName, $this->extractRoute($docComments));
297
                if ($visibility && preg_match('/^\/api\//i', $route)) {
298
                    $methodInfo = [
299
                        'url'         => $route,
300
                        'method'      => $this->extractMethod($docComments),
301
                        'description' => str_replace('{__API__}', $shortName, $this->extractDescription($docComments)),
302
                        'return'      => $this->extractReturn($modelNamespace, $docComments),
303
                    ];
304
                    if (in_array($methodInfo['method'], ['POST', 'PUT'])) {
305
                        $methodInfo['payload'] = $this->extractPayload($modelNamespace, $docComments);
306
                    }
307
                }
308
            }
309
310
            return $methodInfo;
311
        }
312
    }
313