Completed
Push — master ( f24760...487d2a )
by Fran
03:44
created

DocumentorService::extractRoute()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 4
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 7
rs 9.4285
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
                    $docComments = $method->getDocComment();
87
                    if (FALSE !== $docComments && preg_match('/\@route\ /i', $docComments)) {
88
                        $visibility = $this->extractVisibility($docComments);
89
                        $route = str_replace('{__API__}', $reflection->getShortName(), $this->extractRoute($docComments));
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 122 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...
90
                        if ($visibility && preg_match('/^\/api\//i', $route)) {
91
                            $methodInfo = [
92
                                'url'         => $route,
93
                                'method'      => $this->extractMethod($docComments),
94
                                'description' => str_replace('{__API__}', $reflection->getShortName(), $this->extractDescription($docComments)),
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 144 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...
95
                                'return' => $this->extractReturn(str_replace('Api', 'Models', $namespace), $docComments),
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 121 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...
96
                            ];
97
                            if (in_array($methodInfo['method'], ['POST', 'PUT'])) {
98
                                $methodInfo['payload'] = $this->extractPayload(str_replace('Api', 'Models', $namespace), $docComments);
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 135 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...
99
                            }
100
                            $info[] = $methodInfo;
101
                        }
102
                    }
103
                }
104
            }
105
106
            return $info;
107
        }
108
109
        /**
110
         * Extract route from doc comments
111
         *
112
         * @param string $comments
113
         *
114
         * @return string
115
         */
116
        protected function extractRoute($comments = '')
117
        {
118
            $route = '';
119
            preg_match('/@route\ (.*)\n/i', $comments, $route);
120
121
            return $route[1];
122
        }
123
124
        /**
125
         * Extract method from doc comments
126
         *
127
         * @param string $comments
128
         *
129
         * @return string
130
         */
131
        protected function extractMethod($comments = '')
132
        {
133
            $method = 'GET';
134
            preg_match('/@(get|post|put|delete)\n/i', $comments, $method);
135
136
            return strtoupper($method[1]);
137
        }
138
139
        /**
140
         * Extract visibility from doc comments
141
         *
142
         * @param string $comments
143
         *
144
         * @return boolean
145
         */
146
        protected function extractVisibility($comments = '')
147
        {
148
            $visible = TRUE;
149
            preg_match('/@visible\ (true|false)\n/i', $comments, $visibility);
150
            if (count($visibility)) {
151
                $visible = !('false' == $visibility[1]);
152
            }
153
154
            return $visible;
155
        }
156
157
        /**
158
         * Method that extract the description for the endpoint
159
         *
160
         * @param string $comments
161
         *
162
         * @return string
163
         */
164
        protected function extractDescription($comments = '')
165
        {
166
            $description = '';
167
            $docs = explode("\n", $comments);
168
            if (count($docs)) {
169
                foreach ($docs as &$doc) {
170 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...
171
                        $doc = explode('* ', $doc);
172
                        $description = $doc[1];
173
                    }
174
                }
175
            }
176
177
            return $description;
178
        }
179
180
        /**
181
         * Method that extract the type of a variable
182
         * @param string $comments
183
         *
184
         * @return string
185
         */
186
        protected function extractVarType($comments = '')
187
        {
188
            $type = 'string';
189
            preg_match('/@var\ (.*) (.*)\n/i', $comments, $varType);
190
            if (count($varType)) {
191
                $aux = trim($varType[1]);
192
                $type = str_replace(' ', '', strlen($aux) > 0 ? $varType[1] : $varType[2]);
193
            }
194
            return $type;
195
        }
196
197
        /**
198
         * Method that extract the payload for the endpoint
199
         *
200
         * @param string $model
201
         * @param string $comments
202
         *
203
         * @return array
204
         */
205
        protected function extractPayload($model, $comments = '')
206
        {
207
            $payload = [];
208
            preg_match('/@payload\ (.*)\n/i', $comments, $doc);
209
            if (count($doc)) {
210
                $namespace = str_replace('{__API__}', $model, $doc[1]);
211
                $payload = $this->extractModelFields($namespace);
212
            }
213
            return $payload;
214
        }
215
216
        /**
217
         * Extract return class for api endpoint
218
         * @param string $model
219
         * @param string $comments
220
         *
221
         * @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...
222
         */
223
        protected function extractReturn($model, $comments = '')
224
        {
225
            $return = [];
226
            preg_match('/\@return\ (.*)\ (.*)\n/i', $comments, $returnTypes);
227
            if (count($returnTypes)) {
228
                $closure = $modelDto = [];
229
                $isArray = false;
230
                foreach($returnTypes as $returnType) {
231
                    try {
232
                        if (false === strpos($returnType, '@')) {
233
                            $class = str_replace('{__API__}', $model, $returnType);
234
                            if (false !== strpos($class, '[') && false !== strpos($class, ']')) {
235
                                $class = str_replace(']', '', str_replace('[', '', $class));
236
                                $isArray = true;
237
                            }
238
                            if (class_exists($class)) {
239
                                $reflector = new \ReflectionClass($class);
240
                                if ($reflector->isSubclassOf(self::DTO_INTERFACE)) {
241
                                    foreach($reflector->getProperties(\ReflectionMethod::IS_PUBLIC) as $property) {
242
                                        $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...
243
                                    }
244
                                } else {
245
                                    $modelDto = $this->extractModelFields($class);
246
                                }
247
                            }
248
                        }
249
                    } catch(\Exception $e) {
250
                        Logger::getInstance()->errorLog($e->getMessage());
251
                    }
252
                }
253
                $closure['data'] = ($isArray) ? [$modelDto] : $modelDto;
254
                $return = $closure;
255
            }
256
            return $return;
257
        }
258
259
        /**
260
         * Extract all fields from a ActiveResource model
261
         * @param string $namespace
262
         *
263
         * @return mixed
264
         */
265
        protected function extractModelFields($namespace)
266
        {
267
            $payload = [];
268
            try {
269
                $reflector = new \ReflectionClass($namespace);
270
                // Checks if reflector is a subclass of propel ActiveRecords
271
                if (NULL !== $reflector && $reflector->isSubclassOf(self::MODEL_INTERFACE)) {
272
                    $tableMap = $namespace::TABLE_MAP;
273
                    $fieldNames = $tableMap::getFieldNames();
274
                    if (count($fieldNames)) {
275
                        foreach ($fieldNames as $field) {
276
                            $variable = $reflector->getProperty(strtolower($field));
277
                            $varDoc = $variable->getDocComment();
278
                            $payload[$field] = $this->extractVarType($varDoc);
279
                        }
280
                    }
281
                }
282
            } catch(\Exception $e) {
283
                Logger::getInstance()->errorLog($e->getMessage());
284
            }
285
286
            return $payload;
287
        }
288
    }
289