Passed
Push — master ( 33d274...f605e0 )
by Fran
10:20
created

DocumentorService   D

Complexity

Total Complexity 117

Size/Duplication

Total Lines 736
Duplicated Lines 1.9 %

Coupling/Cohesion

Components 2
Dependencies 10

Test Coverage

Coverage 4.97%

Importance

Changes 0
Metric Value
dl 14
loc 736
ccs 17
cts 342
cp 0.0497
rs 4.4444
c 0
b 0
f 0
wmc 117
lcom 2
cbo 10

22 Methods

Rating   Name   Duplication   Size   Complexity  
B getModules() 0 22 6
B extractApiEndpoints() 0 21 5
B extractApiInfo() 0 21 7
A extractRoute() 0 7 1
A extractApi() 0 7 1
A checkDeprecated() 0 4 1
A extractVisibility() 0 10 2
B extractDescription() 4 15 5
A extractVarType() 0 11 3
A extractPayload() 0 15 2
A extractDtoProperties() 0 10 2
D extractReturn() 0 29 9
D extractModelFields() 0 34 9
B extractMethodInfo() 0 33 6
C translateSwaggerFormats() 0 42 14
B extractSwaggerDefinition() 0 24 3
B swaggerResponses() 0 45 6
D swaggerFormatter() 10 105 19
A extractDtoName() 0 10 2
B setQueryParams() 0 30 3
B setRequestHeaders() 0 32 5
B setRequestParams() 0 23 6

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like DocumentorService often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use DocumentorService, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace PSFS\services;
4
5
use Propel\Runtime\Map\ColumnMap;
6
use PSFS\base\config\Config;
7
use PSFS\base\Logger;
8
use PSFS\base\Request;
9
use PSFS\base\Router;
10
use PSFS\base\Service;
11
use PSFS\base\types\helpers\GeneratorHelper;
12
use PSFS\base\types\helpers\I18nHelper;
13
use PSFS\base\types\helpers\InjectorHelper;
14
use PSFS\base\types\helpers\RouterHelper;
15
use Symfony\Component\Finder\Finder;
16
17
/**
18
 * Class DocumentorService
19
 * @package PSFS\services
20
 */
21
class DocumentorService extends Service
22
{
23
    public static $nativeMethods = [
24
        'modelList', // Api list
25
        'get', // Api get
26
        'post', // Api post
27
        'put', // Api put
28
        'delete', // Api delete
29
    ];
30
31
    const DTO_INTERFACE = '\\PSFS\\base\\dto\\Dto';
32
    const MODEL_INTERFACE = '\\Propel\\Runtime\\ActiveRecord\\ActiveRecordInterface';
33
34
    private $classes = [];
0 ignored issues
show
Unused Code introduced by
The property $classes is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
35
36
    /**
37
     * @Injectable
38
     * @var \PSFS\base\Router route
39
     */
40
    protected $route;
41
42
    /**
43
     * Method that extract all modules
44
     * @param string $requestModule
45
     * @return array
46
     */
47
    public function getModules($requestModule)
48
    {
49
        $modules = [];
50
        $domains = $this->route->getDomains();
51
        if (count($domains)) {
52
            foreach ($domains as $module => $info) {
53
                try {
54
                    $module = preg_replace('/(@|\/)/', '', $module);
55
                    if (!preg_match('/^ROOT/i', $module) && $module == $requestModule) {
56
                        $modules = [
57
                            'name' => $module,
58
                            'path' => realpath($info['template'] . DIRECTORY_SEPARATOR . '..'),
59
                        ];
60
                    }
61
                } catch (\Exception $e) {
62
                    $modules[] = $e->getMessage();
63
                }
64
            }
65
        }
66
67
        return $modules;
68
    }
69
70
    /**
71
     * Method that extract all endpoints for each module
72
     *
73
     * @param array $module
74
     *
75
     * @return array
76
     */
77
    public function extractApiEndpoints(array $module)
78
    {
79
        $module_path = $module['path'] . DIRECTORY_SEPARATOR . 'Api';
80
        $module_name = $module['name'];
81
        $endpoints = [];
82
        if (file_exists($module_path)) {
83
            $finder = new Finder();
84
            $finder->files()->in($module_path)->depth(0)->name('*.php');
85
            if (count($finder)) {
86
                /** @var \SplFileInfo $file */
87
                foreach ($finder as $file) {
88
                    $namespace = "\\{$module_name}\\Api\\" . str_replace('.php', '', $file->getFilename());
89
                    $info = $this->extractApiInfo($namespace, $module_name);
90
                    if (!empty($info)) {
91
                        $endpoints[$namespace] = $info;
92
                    }
93
                }
94
            }
95
        }
96
        return $endpoints;
97
    }
98
99
    /**
100
     * Method that extract all the endpoit information by reflection
101
     *
102
     * @param string $namespace
103
     * @param string $module
104
     * @return array
105
     */
106
    public function extractApiInfo($namespace, $module)
107
    {
108
        $info = [];
109
        if (Router::exists($namespace) && !I18nHelper::checkI18Class($namespace)) {
110
            $reflection = new \ReflectionClass($namespace);
111
            $visible = InjectorHelper::checkIsVisible($reflection->getDocComment());
112
            if($visible) {
113
                foreach ($reflection->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
114
                    try {
115
                        $mInfo = $this->extractMethodInfo($namespace, $method, $reflection, $module);
116
                        if (NULL !== $mInfo) {
117
                            $info[] = $mInfo;
118
                        }
119
                    } catch (\Exception $e) {
120
                        Logger::getInstance()->errorLog($e->getMessage());
121
                    }
122
                }
123
            }
124
        }
125
        return $info;
126
    }
127
128
    /**
129
     * Extract route from doc comments
130
     *
131
     * @param string $comments
132
     *
133
     * @return string
134
     */
135
    protected function extractRoute($comments = '')
136
    {
137
        $route = '';
138
        preg_match('/@route\ (.*)\n/i', $comments, $route);
139
140
        return $route[1];
141
    }
142
143
    /**
144
     * Extract api from doc comments
145
     *
146
     * @param string $comments
147
     *
148
     * @return string
149
     */
150
    protected function extractApi($comments = '')
151
    {
152
        $api = '';
153
        preg_match('/@api\ (.*)\n/i', $comments, $api);
154
155
        return $api[1];
156
    }
157
158
    /**
159
     * Extract api from doc comments
160
     *
161
     * @param string $comments
162
     *
163
     * @return boolean
164
     */
165
    protected function checkDeprecated($comments = '')
166
    {
167
        return false != preg_match('/@deprecated\n/i', $comments);
168
    }
169
170
    /**
171
     * Extract visibility from doc comments
172
     *
173
     * @param string $comments
174
     *
175
     * @return boolean
176
     */
177
    protected function extractVisibility($comments = '')
178
    {
179
        $visible = TRUE;
180
        preg_match('/@visible\ (true|false)\n/i', $comments, $visibility);
181
        if (count($visibility)) {
182
            $visible = !('false' == $visibility[1]);
183
        }
184
185
        return $visible;
186
    }
187
188
    /**
189
     * Method that extract the description for the endpoint
190
     *
191
     * @param string $comments
192
     *
193
     * @return string
194
     */
195
    protected function extractDescription($comments = '')
196
    {
197
        $description = '';
198
        $docs = explode("\n", $comments);
199
        if (count($docs)) {
200
            foreach ($docs as &$doc) {
201 View Code Duplication
                if (!preg_match('/(\*\*|\@)/i', $doc) && preg_match('/\*\ /i', $doc)) {
202
                    $doc = explode('* ', $doc);
203
                    $description = $doc[1];
204
                }
205
            }
206
        }
207
208
        return $description;
209
    }
210
211
    /**
212
     * Method that extract the type of a variable
213
     *
214
     * @param string $comments
215
     *
216
     * @return string
217
     */
218
    public static function extractVarType($comments = '')
219
    {
220
        $type = 'string';
221
        preg_match('/@var\ (.*) (.*)\n/i', $comments, $varType);
222
        if (count($varType)) {
223
            $aux = trim($varType[1]);
224
            $type = str_replace(' ', '', strlen($aux) > 0 ? $varType[1] : $varType[2]);
225
        }
226
227
        return $type;
228
    }
229
230
    /**
231
     * Method that extract the payload for the endpoint
232
     *
233
     * @param string $model
234
     * @param string $comments
235
     *
236
     * @return array
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<string|array>.

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...
237
     */
238
    protected function extractPayload($model, $comments = '')
239
    {
240
        $payload = [];
241
        preg_match('/@payload\ (.*)\n/i', $comments, $doc);
242
        if (count($doc)) {
243
            $namespace = str_replace('{__API__}', $model, $doc[1]);
244
            $payload = $this->extractModelFields($namespace);
245
            $reflector = new \ReflectionClass($namespace);
246
            $namespace = $reflector->getShortName();
247
        } else {
248
            $namespace = $model;
249
        }
250
251
        return [$namespace, $payload];
252
    }
253
254
    /**
255
     * Extract all the properties from Dto class
256
     *
257
     * @param string $class
258
     *
259
     * @return array
260
     */
261
    protected function extractDtoProperties($class)
262
    {
263
        $properties = [];
264
        $reflector = new \ReflectionClass($class);
265
        if ($reflector->isSubclassOf(self::DTO_INTERFACE)) {
266
            $properties = array_merge($properties, InjectorHelper::extractVariables($reflector));
267
        }
268
269
        return $properties;
270
    }
271
272
    /**
273
     * Extract return class for api endpoint
274
     *
275
     * @param string $model
276
     * @param string $comments
277
     *
278
     * @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...
279
     */
280
    protected function extractReturn($model, $comments = '')
281
    {
282
        $modelDto = [];
283
        preg_match('/\@return\ (.*)\((.*)\)\n/i', $comments, $returnTypes);
284
        if (count($returnTypes)) {
285
            // Extract principal DTO information
286
            if (array_key_exists(1, $returnTypes)) {
287
                $modelDto = $this->extractDtoProperties($returnTypes[1]);
288
            }
289
            if (array_key_exists(2, $returnTypes)) {
290
                $subDtos = preg_split('/,?\ /', str_replace('{__API__}', $model, $returnTypes[2]));
291
                if (count($subDtos)) {
292
                    foreach ($subDtos as $subDto) {
293
                        $isArray = false;
294
                        list($field, $dtoName) = explode('=', $subDto);
295
                        if (false !== strpos($dtoName, '[') && false !== strpos($dtoName, ']')) {
296
                            $dtoName = str_replace(']', '', str_replace('[', '', $dtoName));
297
                            $isArray = true;
298
                        }
299
                        $dto = $this->extractModelFields($dtoName);
300
                        $modelDto[$field] = ($isArray) ? [$dto] : $dto;
301
                        $modelDto['objects'][$dtoName] = $dto;
302
                    }
303
                }
304
            }
305
        }
306
307
        return $modelDto;
308
    }
309
310
    /**
311
     * Extract all fields from a ActiveResource model
312
     *
313
     * @param string $namespace
314
     *
315
     * @return mixed
316
     */
317
    protected function extractModelFields($namespace)
318
    {
319
        $payload = [];
320
        try {
321
            $reflector = new \ReflectionClass($namespace);
322
            // Checks if reflector is a subclass of propel ActiveRecords
323
            if (NULL !== $reflector && $reflector->isSubclassOf(self::MODEL_INTERFACE)) {
324
                $tableMap = $namespace::TABLE_MAP;
325
                $tableMap = $tableMap::getTableMap();
326
                /** @var ColumnMap $field */
327
                foreach ($tableMap->getColumns() as $field) {
328
                    list($type, $format) = DocumentorService::translateSwaggerFormats($field->getType());
329
                    $info = [
330
                        "type" => $type,
331
                        "required" => $field->isNotNull(),
332
                        'format' => $format,
333
                    ];
334
                    if(count($field->getValueSet())) {
335
                        $info['enum'] = array_values($field->getValueSet());
336
                    }
337
                    if(null !== $field->getDefaultValue()) {
338
                        $info['default'] = $field->getDefaultValue();
339
                    }
340
                    $payload[$field->getPhpName()] = $info;
341
                }
342
            } elseif (null !== $reflector && $reflector->isSubclassOf(self::DTO_INTERFACE)) {
343
                $payload = $this->extractDtoProperties($namespace);
344
            }
345
        } catch (\Exception $e) {
346
            Logger::getInstance()->errorLog($e->getMessage());
347
        }
348
349
        return $payload;
350
    }
351
352
    /**
353
     * Method that extract all the needed info for each method in each API
354
     *
355
     * @param string $namespace
356
     * @param \ReflectionMethod $method
357
     * @param \ReflectionClass $reflection
358
     * @param string $module
359
     *
360
     * @return array
361
     */
362
    protected function extractMethodInfo($namespace, \ReflectionMethod $method, \ReflectionClass $reflection, $module)
363
    {
364
        $methodInfo = NULL;
365
        $docComments = $method->getDocComment();
366
        if (FALSE !== $docComments && preg_match('/\@route\ /i', $docComments)) {
367
            $api = self::extractApi($reflection->getDocComment());
368
            list($route, $info) = RouterHelper::extractRouteInfo($method, $api, $module);
369
            $route = explode('#|#', $route);
370
            $modelNamespace = str_replace('Api', 'Models', $namespace);
371
            if ($info['visible'] && !self::checkDeprecated($docComments)) {
372
                try {
373
                    $return = $this->extractReturn($modelNamespace, $docComments);
374
                    $url = array_pop($route);
375
                    $methodInfo = [
376
                        'url' => str_replace("/" . $module . "/api", '', $url),
377
                        'method' => $info['http'],
378
                        'description' => $info['label'],
379
                        'return' => $return,
380
                        'objects' => $return['objects'],
381
                        'class' => $reflection->getShortName(),
382
                    ];
383
                    unset($methodInfo['return']['objects']);
384
                    $this->setRequestParams($method, $methodInfo, $modelNamespace, $docComments);
385
                    $this->setQueryParams($method, $methodInfo);
386
                    $this->setRequestHeaders($reflection, $methodInfo);
387
                } catch (\Exception $e) {
388
                    Logger::getInstance()->errorLog($e->getMessage());
389
                }
390
            }
391
        }
392
393
        return $methodInfo;
394
    }
395
396
    /**
397
     * Translator from php types to swagger types
398
     * @param string $format
399
     *
400
     * @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...
401
     */
402 1
    public static function translateSwaggerFormats($format)
403
    {
404 1
        switch (strtolower($format)) {
405 1
            case 'bool':
406 1
            case 'boolean':
407
                $swaggerType = 'boolean';
408
                $swaggerFormat = '';
409
                break;
410
            default:
411 1
            case 'string':
412 1
            case 'varchar':
413 1
                $swaggerType = 'string';
414 1
                $swaggerFormat = '';
415 1
                break;
416 1
            case 'binary':
417 1
            case 'varbinary':
418
                $swaggerType = 'string';
419
                $swaggerFormat = 'password';
420
                break;
421 1
            case 'int':
422 1
            case 'integer':
423 1
                $swaggerType = 'integer';
424 1
                $swaggerFormat = 'int32';
425 1
                break;
426
            case 'float':
427
            case 'double':
428
                $swaggerType = 'number';
429
                $swaggerFormat = strtolower($format);
430
                break;
431
            case 'date':
432
                $swaggerType = 'string';
433
                $swaggerFormat = 'date';
434
                break;
435
            case 'timestamp':
436
            case 'datetime':
437
                $swaggerType = 'string';
438
                $swaggerFormat = 'date-time';
439
                break;
440
441
        }
442 1
        return [$swaggerType, $swaggerFormat];
443
    }
444
445
    /**
446
     * Method that parse the definitions for the api's
447
     * @param string $name
448
     * @param array $fields
449
     *
450
     * @return array
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<string,array<string,string|array>>.

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...
451
     */
452
    public static function extractSwaggerDefinition($name, array $fields)
453
    {
454
        $definition = [
455
            $name => [
456
                "type" => "object",
457
                "properties" => [],
458
            ],
459
        ];
460
        foreach ($fields as $field => $info) {
461
            list($type, $format) = self::translateSwaggerFormats($info['type']);
462
            $dto['properties'][$field] = [
0 ignored issues
show
Coding Style Comprehensibility introduced by
$dto was never initialized. Although not strictly required by PHP, it is generally a good practice to add $dto = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
463
                "type" => $type,
464
                "required" => $info['required'],
465
            ];
466
            $definition[$name]['properties'][$field] = [
467
                "type" => $type,
468
                "required" => $info['required'],
469
            ];
470
            if (strlen($format)) {
471
                $definition[$name]['properties'][$field]['format'] = $format;
472
            }
473
        }
474
        return $definition;
475
    }
476
477
    /**
478
     * @return array
479
     */
480
    private static function swaggerResponses()
481
    {
482
        $codes = [200, 400, 404, 500];
483
        $responses = [];
484
        foreach ($codes as $code) {
485
            switch ($code) {
486
                default:
487
                case 200:
488
                    $message = _('Successful response');
489
                    break;
490
                case 400:
491
                    $message = _('Client error in request');
492
                    break;
493
                case 404:
494
                    $message = _('Service not found');
495
                    break;
496
                case 500:
497
                    $message = _('Server error');
498
                    break;
499
            }
500
            $responses[$code] = [
501
                'description' => $message,
502
                'schema' => [
503
                    'type' => 'object',
504
                    'properties' => [
505
                        'success' => [
506
                            'type' => 'boolean'
507
                        ],
508
                        'data' => [
509
                            'type' => 'boolean',
510
                        ],
511
                        'total' => [
512
                            'type' => 'integer',
513
                            'format' => 'int32',
514
                        ],
515
                        'pages' => [
516
                            'type' => 'integer',
517
                            'format' => 'int32',
518
                        ]
519
                    ]
520
                ]
521
            ];
522
        }
523
        return $responses;
524
    }
525
526
    /**
527
     * Method that export
528
     * @param array $module
529
     *
530
     * @return array
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<string,string|array>.

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...
531
     */
532
    public static function swaggerFormatter(array $module)
533
    {
534
        $formatted = [
535
            "swagger" => "2.0",
536
            "host" => preg_replace('/^(http|https)\:\/\/(.*)\/$/i', '$2', Router::getInstance()->getRoute('', true)),
537
            "basePath" => '/' . $module['name'] . '/api',
538
            "schemes" => [Request::getInstance()->getServer('HTTPS') == 'on' ? "https" : "http"],
539
            "info" => [
540
                "title" => _('Documentación API módulo ') . $module['name'],
541
                "version" => Config::getParam('api.version', '1.0.0'),
542
                "contact" => [
543
                    "name" => Config::getParam("author", "Fran López"),
544
                    "email" => Config::getParam("author.email", "[email protected]"),
545
                ]
546
            ]
547
        ];
548
        $dtos = $paths = [];
549
        $endpoints = DocumentorService::getInstance()->extractApiEndpoints($module);
550
        foreach ($endpoints as $model) {
551
            foreach ($model as $endpoint) {
552
                if (!preg_match('/^\/(admin|api)\//i', $endpoint['url']) && strlen($endpoint['url'])) {
553
                    $url = preg_replace('/\/' . $module['name'] . '\/api/i', '', $endpoint['url']);
554
                    $description = $endpoint['description'];
555
                    $method = strtolower($endpoint['method']);
556
                    $paths[$url][$method] = [
557
                        'summary' => $description,
558
                        'produces' => ['application/json'],
559
                        'consumes' => ['application/json'],
560
                        'responses' => self::swaggerResponses(),
561
                        'parameters' => [],
562
                    ];
563
                    if (array_key_exists('parameters', $endpoint)) {
564
                        foreach ($endpoint['parameters'] as $parameter => $type) {
565
                            list($type, $format) = self::translateSwaggerFormats($type);
566
                            $paths[$url][$method]['parameters'][] = [
567
                                'in' => 'path',
568
                                'required' => true,
569
                                'name' => $parameter,
570
                                'type' => $type,
571
                                'format' => $format,
572
                            ];
573
                        }
574
                    }
575 View Code Duplication
                    if (array_key_exists('query', $endpoint)) {
576
                        foreach ($endpoint['query'] as $query) {
577
                            $paths[$url][$method]['parameters'][] = $query;
578
                        }
579
                    }
580 View Code Duplication
                    if (array_key_exists('headers', $endpoint)) {
581
                        foreach ($endpoint['headers'] as $query) {
582
                            $paths[$url][$method]['parameters'][] = $query;
583
                        }
584
                    }
585
                    foreach ($endpoint['objects'] as $name => $object) {
586
                        if (class_exists($name)) {
587
                            $class = GeneratorHelper::extractClassFromNamespace($name);
588
                            if(array_key_exists('data', $endpoint['return']) && count(array_keys($object)) === count(array_keys($endpoint['return']['data']))) {
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 160 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...
589
                                $classDefinition = [
590
                                    'type' => 'object',
591
                                    '$ref' => '#/definitions/' . $class,
592
                                ];
593
                            } else {
594
                                $classDefinition = [
595
                                    'type' => 'array',
596
                                    'items' => [
597
                                        '$ref' => '#/definitions/' . $class,
598
                                    ],
599
                                ];
600
                            }
601
602
                            $paths[$url][$method]['responses'][200]['schema']['properties']['data'] = $classDefinition;
603
                            $dtos += self::extractSwaggerDefinition($class, $object);
604
                            if (array_key_exists('payload', $endpoint)) {
605
                                $dtos[$endpoint['payload']['type']] = [
606
                                    'type' => 'object',
607
                                    'properties' => $endpoint['payload']['properties'],
608
                                ];
609
                                $paths[$url][$method]['parameters'][] = [
610
                                    'in' => 'body',
611
                                    'name' => $endpoint['payload']['type'],
612
                                    'required' => true,
613
                                    'schema' => [
614
                                        'type' => 'object',
615
                                        '$ref' => '#/definitions/' . $endpoint['payload']['type'],
616
                                    ],
617
                                ];
618
                            }
619
                        }
620
                        if (!isset($paths[$url][$method]['tags']) || !in_array($endpoint['class'], $paths[$url][$method]['tags'])) {
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 132 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...
621
                            $paths[$url][$method]['tags'][] = $endpoint['class'];
622
                        }
623
                    }
624
                }
625
            }
626
        }
627
        ksort($dtos);
628
        uasort($paths, function($path1, $path2) {
629
            $key1 = array_keys($path1)[0];
630
            $key2 = array_keys($path2)[0];
631
            return strcmp($path1[$key1]['tags'][0], $path2[$key2]['tags'][0]);
632
        });
633
        $formatted['definitions'] = $dtos;
634
        $formatted['paths'] = $paths;
635
        return $formatted;
636
    }
637
638
    /**
639
     * Method that extract the Dto class for the api documentation
640
     * @param string $dto
641
     * @param boolean $isArray
642
     *
643
     * @return string
644
     */
645
    protected function extractDtoName($dto, $isArray = false)
646
    {
647
        $dto = explode('\\', $dto);
648
        $modelDto = array_pop($dto) . "Dto";
649
        if ($isArray) {
650
            $modelDto .= "List";
651
        }
652
653
        return $modelDto;
654
    }
655
656
    /**
657
     * @param \ReflectionMethod $method
658
     * @param $methodInfo
659
     */
660
    protected function setQueryParams(\ReflectionMethod $method, &$methodInfo)
661
    {
662
        if (in_array($methodInfo['method'], ['GET']) && in_array($method->getShortName(), self::$nativeMethods)) {
663
            $methodInfo['query'] = [];
664
            $methodInfo['query'][] = [
665
                "name" => "__limit",
666
                "in" => "query",
667
                "description" => _("Límite de registros a devolver, -1 para devolver todos los registros"),
668
                "required" => false,
669
                "type" => "integer",
670
            ];
671
            $methodInfo['query'][] = [
672
                "name" => "__page",
673
                "in" => "query",
674
                "description" => _("Página a devolver"),
675
                "required" => false,
676
                "type" => "integer",
677
            ];
678
            $methodInfo['query'][] = [
679
                "name" => "__fields",
680
                "in" => "query",
681
                "description" => _("Campos a devolver"),
682
                "required" => false,
683
                "type" => "array",
684
                "items" => [
685
                    "type" => "string",
686
                ]
687
            ];
688
        }
689
    }
690
    /**
691
     * @param \ReflectionClass $reflection
692
     * @param $methodInfo
693
     */
694
    protected function setRequestHeaders(\ReflectionClass $reflection, &$methodInfo)
695
    {
696
697
        $methodInfo['headers'] = [];
698
        foreach($reflection->getProperties() as $property) {
699
            $doc = $property->getDocComment();
700
            preg_match('/@header\ (.*)\n/i', $doc, $headers);
701
            if(count($headers)) {
702
                $header = [
703
                    "name" => $headers[1],
704
                    "in" => "header",
705
                    "required" => true,
706
                ];
707
708
                // Extract var type
709
                $header['type'] = $this->extractVarType($doc);
710
711
                // Extract description
712
                preg_match('/@label\ (.*)\n/i', $doc, $label);
713
                if(count($label)) {
714
                    $header['description'] = _($label[1]);
715
                }
716
717
                // Extract default value
718
                preg_match('/@default\ (.*)\n/i', $doc, $default);
719
                if(count($default)) {
720
                    $header['default'] = $default[1];
721
                }
722
                $methodInfo['headers'][] = $header;
723
            }
724
        }
725
    }
726
727
    /**
728
     * @param \ReflectionMethod $method
729
     * @param array $methodInfo
730
     * @param string $modelNamespace
731
     * @param string $docComments
732
     */
733
    protected function setRequestParams(\ReflectionMethod $method, &$methodInfo, $modelNamespace, $docComments)
734
    {
735
        if (in_array($methodInfo['method'], ['POST', 'PUT'])) {
736
            list($payloadNamespace, $payloadDto) = $this->extractPayload($modelNamespace, $docComments);
737
            if (count($payloadDto)) {
738
                $methodInfo['payload'] = [
739
                    'type' => $payloadNamespace,
740
                    'properties' => $payloadDto,
741
                ];
742
            }
743
        }
744
        if ($method->getNumberOfParameters() > 0) {
745
            $methodInfo['parameters'] = [];
746
            foreach ($method->getParameters() as $parameter) {
747
                $parameterName = $parameter->getName();
748
                $types = [];
749
                preg_match_all('/\@param\ (.*)\ \$' . $parameterName . '$/im', $docComments, $types);
750
                if (count($types) > 1) {
751
                    $methodInfo['parameters'][$parameterName] = $types[1][0];
752
                }
753
            }
754
        }
755
    }
756
}
757