Passed
Push — master ( 9cde44...0a9e87 )
by Fran
03:13
created

DocumentorService::checkDeprecated()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
    namespace PSFS\services;
3
4
    use Propel\Runtime\Map\TableMap;
5
    use PSFS\base\Logger;
6
    use PSFS\base\Service;
7
    use PSFS\base\types\helpers\InjectorHelper;
8
    use PSFS\base\types\helpers\RouterHelper;
9
    use Symfony\Component\Finder\Finder;
10
11
    /**
12
     * Class DocumentorService
13
     * @package PSFS\services
14
     */
15
    class DocumentorService extends Service
16
    {
17
        const DTO_INTERFACE = '\\PSFS\\base\\dto\\Dto';
18
        const MODEL_INTERFACE = '\\Propel\\Runtime\\ActiveRecord\\ActiveRecordInterface';
19
        /**
20
         * @Inyectable
21
         * @var \PSFS\base\Router route
22
         */
23
        protected $route;
24
25
        /**
26
         * Method that extract all modules
27
         * @return array
28
         */
29
        public function getModules()
30
        {
31
            $modules = [];
32
            $domains = $this->route->getDomains();
33
            if (count($domains)) {
34
                foreach ($domains as $module => $info) {
35
                    try {
36
                        $module = str_replace('/', '', str_replace('@', '', $module));
37
                        if (!preg_match('/^ROOT/i', $module)) {
38
                            $modules[] = [
39
                                'name' => $module,
40
                                'path' => realpath($info['template'] . DIRECTORY_SEPARATOR . '..'),
41
                            ];
42
                        }
43
                    } catch (\Exception $e) {
44
                        $modules[] = $e->getMessage();
45
                    }
46
                }
47
            }
48
49
            return $modules;
50
        }
51
52
        /**
53
         * Method that extract all endpoints for each module
54
         *
55
         * @param array $module
56
         *
57
         * @return array
58
         */
59
        public function extractApiEndpoints(array $module)
60
        {
61
            $module_path = $module['path'] . DIRECTORY_SEPARATOR . 'Api';
62
            $module_name = $module['name'];
63
            $endpoints = [];
64
            if (file_exists($module_path)) {
65
                $finder = new Finder();
66
                $finder->files()->in($module_path)->depth(0)->name('*.php');
67
                if (count($finder)) {
68
                    /** @var \SplFileInfo $file */
69
                    foreach ($finder as $file) {
70
                        $namespace = "\\{$module_name}\\Api\\" . str_replace('.php', '', $file->getFilename());
71
                        $endpoints[$namespace] = $this->extractApiInfo($namespace, $module_name);
72
                    }
73
                }
74
            }
75
            return $endpoints;
76
        }
77
78
        /**
79
         * Method that extract all the endpoit information by reflection
80
         *
81
         * @param string $namespace
82
         *
83
         * @return array
84
         */
85
        public function extractApiInfo($namespace, $module)
86
        {
87
            $info = [];
88
            $reflection = new \ReflectionClass($namespace);
89
            foreach ($reflection->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
90
                try {
91
                    $mInfo = $this->extractMethodInfo($namespace, $method, $reflection, $module);
92
                    if (NULL !== $mInfo) {
93
                        $info[] = $mInfo;
94
                    }
95
                } catch (\Exception $e) {
96
                    Logger::getInstance()->errorLog($e->getMessage());
97
                }
98
            }
99
            return $info;
100
        }
101
102
        /**
103
         * Extract route from doc comments
104
         *
105
         * @param string $comments
106
         *
107
         * @return string
108
         */
109
        protected function extractRoute($comments = '')
110
        {
111
            $route = '';
112
            preg_match('/@route\ (.*)\n/i', $comments, $route);
113
114
            return $route[1];
115
        }
116
117
        /**
118
         * Extract api from doc comments
119
         *
120
         * @param string $comments
121
         *
122
         * @return string
123
         */
124
        protected function extractApi($comments = '')
125
        {
126
            $api = '';
127
            preg_match('/@api\ (.*)\n/i', $comments, $api);
128
129
            return $api[1];
130
        }
131
132
        /**
133
         * Extract api from doc comments
134
         *
135
         * @param string $comments
136
         *
137
         * @return boolean
138
         */
139
        protected function checkDeprecated($comments = '')
140
        {
141
            return false != preg_match('/@deprecated\n/i', $comments);
1 ignored issue
show
Bug Best Practice introduced by
It seems like you are loosely comparing preg_match('/@deprecated\\n/i', $comments) of type integer to the boolean false. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
142
        }
143
144
        /**
145
         * Extract visibility from doc comments
146
         *
147
         * @param string $comments
148
         *
149
         * @return boolean
150
         */
151
        protected function extractVisibility($comments = '')
152
        {
153
            $visible = TRUE;
154
            preg_match('/@visible\ (true|false)\n/i', $comments, $visibility);
155
            if (count($visibility)) {
156
                $visible = !('false' == $visibility[1]);
157
            }
158
159
            return $visible;
160
        }
161
162
        /**
163
         * Method that extract the description for the endpoint
164
         *
165
         * @param string $comments
166
         *
167
         * @return string
168
         */
169
        protected function extractDescription($comments = '')
170
        {
171
            $description = '';
172
            $docs = explode("\n", $comments);
173
            if (count($docs)) {
174
                foreach ($docs as &$doc) {
175 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...
176
                        $doc = explode('* ', $doc);
177
                        $description = $doc[1];
178
                    }
179
                }
180
            }
181
182
            return $description;
183
        }
184
185
        /**
186
         * Method that extract the type of a variable
187
         *
188
         * @param string $comments
189
         *
190
         * @return string
191
         */
192
        public static function extractVarType($comments = '')
193
        {
194
            $type = 'string';
195
            preg_match('/@var\ (.*) (.*)\n/i', $comments, $varType);
196
            if (count($varType)) {
197
                $aux = trim($varType[1]);
198
                $type = str_replace(' ', '', strlen($aux) > 0 ? $varType[1] : $varType[2]);
199
            }
200
201
            return $type;
202
        }
203
204
        /**
205
         * Method that extract the payload for the endpoint
206
         *
207
         * @param string $model
208
         * @param string $comments
209
         *
210
         * @return array
211
         */
212
        protected function extractPayload($model, $comments = '')
213
        {
214
            $payload = [];
215
            preg_match('/@payload\ (.*)\n/i', $comments, $doc);
216
            if (count($doc)) {
217
                $namespace = str_replace('{__API__}', $model, $doc[1]);
218
                $payload = $this->extractModelFields($namespace);
219
            }
220
221
            return $payload;
222
        }
223
224
        /**
225
         * Extract all the properties from Dto class
226
         *
227
         * @param string $class
228
         *
229
         * @return array
230
         */
231
        protected function extractDtoProperties($class)
232
        {
233
            $properties = [];
234
            $reflector = new \ReflectionClass($class);
235
            if ($reflector->isSubclassOf(self::DTO_INTERFACE)) {
236
	            $properties = array_merge($properties, InjectorHelper::extractProperties($reflector, \ReflectionMethod::IS_PUBLIC));
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 129 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...
237
            }
238
239
            return $properties;
240
        }
241
242
        /**
243
         * Extract return class for api endpoint
244
         *
245
         * @param string $model
246
         * @param string $comments
247
         *
248
         * @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...
249
         */
250
        protected function extractReturn($model, $comments = '')
251
        {
252
            $modelDto  = [];
253
            preg_match('/\@return\ (.*)\((.*)\)\n/i', $comments, $returnTypes);
254
            if (count($returnTypes)) {
255
                // Extract principal DTO information
256
                if (array_key_exists(1, $returnTypes)) {
257
                    $modelDto = $this->extractDtoProperties($returnTypes[1]);
258
                }
259
                if (array_key_exists(2, $returnTypes)) {
260
                    $subDtos = preg_split('/,?\ /', str_replace('{__API__}', $model, $returnTypes[2]));
261
                    if (count($subDtos)) {
262
                        foreach ($subDtos as $subDto) {
263
                            $isArray = false;
264
                            list($field, $dto) = explode('=', $subDto);
265
                            if (false !== strpos($dto, '[') && false !== strpos($dto, ']')) {
266
                                $dto = str_replace(']', '', str_replace('[', '', $dto));
267
                                $isArray = true;
268
                            }
269
                            $dto = $this->extractModelFields($dto);
270
                            $modelDto[$field] = ($isArray) ? [$dto] : $dto;
271
                        }
272
                    }
273
                }
274
            }
275
276
            return $modelDto;
277
        }
278
279
        /**
280
         * Extract all fields from a ActiveResource model
281
         *
282
         * @param string $namespace
283
         *
284
         * @return mixed
285
         */
286
        protected function extractModelFields($namespace)
287
        {
288
            $payload = [];
289
            try {
290
                $reflector = new \ReflectionClass($namespace);
291
                // Checks if reflector is a subclass of propel ActiveRecords
292
                if (NULL !== $reflector && $reflector->isSubclassOf(self::MODEL_INTERFACE)) {
293
                    $tableMap = $namespace::TABLE_MAP;
294
                    $fieldNames = $tableMap::getFieldNames(TableMap::TYPE_FIELDNAME);
295
                    if (count($fieldNames)) {
296
                        foreach ($fieldNames as $field) {
297
                            $variable = $reflector->getProperty(strtolower($field));
298
                            $varDoc = $variable->getDocComment();
299
                            $payload[$tableMap::translateFieldName($field, TableMap::TYPE_FIELDNAME, TableMap::TYPE_PHPNAME)] = $this->extractVarType($varDoc);
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 159 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...
300
                        }
301
                    }
302
                } elseif (null !== $reflector && $reflector->isSubclassOf(self::DTO_INTERFACE)) {
303
                    $payload = $this->extractDtoProperties($namespace);
304
                }
305
            } catch (\Exception $e) {
306
                Logger::getInstance()->errorLog($e->getMessage());
307
            }
308
309
            return $payload;
310
        }
311
312
        /**
313
         * Method that extract all the needed info for each method in each API
314
         *
315
         * @param string $namespace
316
         * @param \ReflectionMethod $method
317
         * @param \ReflectionClass $reflection
318
         * @param string $module
319
         *
320
         * @return array
0 ignored issues
show
Documentation introduced by
Should the return type not be 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...
321
         */
322
        protected function extractMethodInfo($namespace, \ReflectionMethod $method, \ReflectionClass $reflection, $module)
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...
323
        {
324
            $methodInfo = NULL;
325
            $docComments = $method->getDocComment();
326
            if (FALSE !== $docComments && preg_match('/\@route\ /i', $docComments)) {
327
                $api = self::extractApi($reflection->getDocComment());
328
                list($route, $info) = RouterHelper::extractRouteInfo($method, $api, $module);
329
                $route = explode('#|#', $route);
330
                $modelNamespace = str_replace('Api', 'Models', $namespace);
331
                if ($info['visible'] && !self::checkDeprecated($docComments)) {
332
                    try {
333
                        $methodInfo = [
334
                            'url'         => array_pop($route),
335
                            'method'      => $info['http'],
336
                            'description' => $info['label'],
337
                            'return'      => $this->extractReturn($modelNamespace, $docComments),
338
                        ];
339
                        if (in_array($methodInfo['method'], ['POST', 'PUT'])) {
340
                            $methodInfo['payload'] = $this->extractPayload($modelNamespace, $docComments);
341
                        }
342
                    } catch (\Exception $e) {
343
                        jpre($e->getMessage());
344
                        Logger::getInstance()->errorLog($e->getMessage());
345
                    }
346
                }
347
            }
348
349
            return $methodInfo;
350
        }
351
352
        /**
353
         * Translator from php types to swagger types
354
         * @param string $format
355
         *
356
         * @return array
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use string[].

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
357
         */
358
        public static function translateSwaggerFormats($format)
359
        {
360
            switch(strtolower($format)) {
361
                case 'bool':
362
                case 'boolean':
363
                    $swaggerType = 'boolean';
364
                    $swaggerFormat = '';
365
                    break;
366
                default:
367
                case 'string':
368
                case 'varchar':
369
                    $swaggerType = 'string';
370
                    $swaggerFormat = '';
371
                    break;
372
                case 'binary':
373
                case 'varbinary':
374
                    $swaggerType = 'string';
375
                    $swaggerFormat = 'binary';
376
                    break;
377
                case 'int':
378
                case 'integer':
379
                case 'float':
380
                case 'double':
381
                    $swaggerType = 'integer';
382
                    $swaggerFormat = 'int32';
383
                    break;
384
                case 'date':
385
                    $swaggerType = 'string';
386
                    $swaggerFormat = 'date';
387
                    break;
388
                case 'datetime':
389
                    $swaggerType = 'string';
390
                    $swaggerFormat = 'date-time	';
391
                    break;
392
393
            }
394
            return [$swaggerType, $swaggerFormat];
395
        }
396
397
        /**
398
         * Method that parse the definitions for the api's
399
         * @param array $endpoint
400
         *
401
         * @return array
402
         */
403
        public static function extractSwaggerDefinition(array $endpoint)
404
        {
405
            $definitions = [];
406
            if (array_key_exists('definitions', $endpoint)) {
407
                foreach ($endpoint['definitions'] as $dtoName => $definition) {
408
                    $dto = [
409
                        "type" => "object",
410
                        "properties" => [],
411
                    ];
412
                    foreach ($definition as $field => $format) {
413
                        if (is_array($format)) {
414
                            $subDtoName = preg_replace('/Dto$/', '', $dtoName);
415
                            $subDtoName = preg_replace('/DtoList$/', '', $subDtoName);
416
                            $subDto = self::extractSwaggerDefinition(['definitions' => [
417
                                $subDtoName => $format,
418
                            ]]);
419
                            if (array_key_exists($subDtoName, $subDto)) {
420
                                $definitions = $subDto;
421
                            } else {
422
                                $definitions[$subDtoName] = $subDto;
423
                            }
424
                            $dto['properties'][$field] = [
425
                                '$ref' => "#/definitions/" . $subDtoName,
426
                            ];
427
                        } else {
428
                            list($type, $format) = self::translateSwaggerFormats($format);
429
                            $dto['properties'][$field] = [
430
                                "type" => $type,
431
                            ];
432
                            if (strlen($format)) {
433
                               $dto['properties'][$field]['format'] = $format;
434
                            }
435
                        }
436
                    }
437
                    $definitions[$dtoName] = $dto;
438
                }
439
            }
440
            return $definitions;
441
        }
442
443
        /**
444
         * Method that export
445
         * @param array $modules
446
         *
447
         * @return array
448
         */
449
        public static function swaggerFormatter(array $modules = [])
450
        {
451
            $endpoints = [];
452
            pre($modules, true);
453
            $dtos = [];
454
            $formatted = [
455
                "swagger" => "2.0",
456
                "host" => Router::getInstance()->getRoute(''),
457
                "basePath" => "/api",
458
                "schemes" => ["http", "https"],
459
                "externalDocs" => [
460
                    "description" => "Principal Url",
461
                    "url" => Router::getInstance()->getRoute(''),
462
                ]
463
            ];
464
            foreach($endpoints as $model) {
465
                foreach ($model as $endpoint) {
466
                    $dtos += self::extractSwaggerDefinition($endpoint);
467
                }
468
            }
469
            $formatted['definitions'] = $dtos;
470
            return $formatted;
471
        }
472
473
        /**
474
         * Method that extract the Dto class for the api documentation
475
         * @param string $dto
476
         * @param boolean $isArray
477
         *
478
         * @return string
479
         */
480
        protected function extractDtoName($dto, $isArray = false)
481
        {
482
            $dto = explode('\\', $dto);
483
            $modelDto = array_pop($dto) . "Dto";
484
            if ($isArray) {
485
                $modelDto .= "List";
486
            }
487
488
            return $modelDto;
489
        }
490
    }
491