Passed
Push — master ( f697cc...d7bbf2 )
by Fran
03:27
created

DocumentorService   D

Complexity

Total Complexity 113

Size/Duplication

Total Lines 720
Duplicated Lines 1.94 %

Coupling/Cohesion

Components 2
Dependencies 10

Test Coverage

Coverage 5.17%

Importance

Changes 0
Metric Value
dl 14
loc 720
ccs 17
cts 329
cp 0.0517
rs 4.4444
c 0
b 0
f 0
wmc 113
lcom 2
cbo 10

22 Methods

Rating   Name   Duplication   Size   Complexity  
C translateSwaggerFormats() 0 41 13
B extractSwaggerDefinition() 0 24 3
B swaggerResponses() 0 45 6
D swaggerFormatter() 10 99 19
A extractDtoName() 0 10 2
B setQueryParams() 0 30 3
B setRequestHeaders() 0 32 5
B setRequestParams() 0 23 6
B getModules() 0 22 6
B extractApiEndpoints() 0 21 5
B extractApiInfo() 0 18 6
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
C extractModelFields() 0 28 7
B extractMethodInfo() 0 33 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
            foreach ($reflection->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
112
                try {
113
                    $mInfo = $this->extractMethodInfo($namespace, $method, $reflection, $module);
114
                    if (NULL !== $mInfo) {
115
                        $info[] = $mInfo;
116
                    }
117
                } catch (\Exception $e) {
118
                    Logger::getInstance()->errorLog($e->getMessage());
119
                }
120
            }
121
        }
122
        return $info;
123
    }
124
125
    /**
126
     * Extract route from doc comments
127
     *
128
     * @param string $comments
129
     *
130
     * @return string
131
     */
132
    protected function extractRoute($comments = '')
133
    {
134
        $route = '';
135
        preg_match('/@route\ (.*)\n/i', $comments, $route);
136
137
        return $route[1];
138
    }
139
140
    /**
141
     * Extract api from doc comments
142
     *
143
     * @param string $comments
144
     *
145
     * @return string
146
     */
147
    protected function extractApi($comments = '')
148
    {
149
        $api = '';
150
        preg_match('/@api\ (.*)\n/i', $comments, $api);
151
152
        return $api[1];
153
    }
154
155
    /**
156
     * Extract api from doc comments
157
     *
158
     * @param string $comments
159
     *
160
     * @return boolean
161
     */
162
    protected function checkDeprecated($comments = '')
163
    {
164
        return false != preg_match('/@deprecated\n/i', $comments);
165
    }
166
167
    /**
168
     * Extract visibility from doc comments
169
     *
170
     * @param string $comments
171
     *
172
     * @return boolean
173
     */
174
    protected function extractVisibility($comments = '')
175
    {
176
        $visible = TRUE;
177
        preg_match('/@visible\ (true|false)\n/i', $comments, $visibility);
178
        if (count($visibility)) {
179
            $visible = !('false' == $visibility[1]);
180
        }
181
182
        return $visible;
183
    }
184
185
    /**
186
     * Method that extract the description for the endpoint
187
     *
188
     * @param string $comments
189
     *
190
     * @return string
191
     */
192
    protected function extractDescription($comments = '')
193
    {
194
        $description = '';
195
        $docs = explode("\n", $comments);
196
        if (count($docs)) {
197
            foreach ($docs as &$doc) {
198 View Code Duplication
                if (!preg_match('/(\*\*|\@)/i', $doc) && preg_match('/\*\ /i', $doc)) {
199
                    $doc = explode('* ', $doc);
200
                    $description = $doc[1];
201
                }
202
            }
203
        }
204
205
        return $description;
206
    }
207
208
    /**
209
     * Method that extract the type of a variable
210
     *
211
     * @param string $comments
212
     *
213
     * @return string
214
     */
215
    public static function extractVarType($comments = '')
216
    {
217
        $type = 'string';
218
        preg_match('/@var\ (.*) (.*)\n/i', $comments, $varType);
219
        if (count($varType)) {
220
            $aux = trim($varType[1]);
221
            $type = str_replace(' ', '', strlen($aux) > 0 ? $varType[1] : $varType[2]);
222
        }
223
224
        return $type;
225
    }
226
227
    /**
228
     * Method that extract the payload for the endpoint
229
     *
230
     * @param string $model
231
     * @param string $comments
232
     *
233
     * @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...
234
     */
235
    protected function extractPayload($model, $comments = '')
236
    {
237
        $payload = [];
238
        preg_match('/@payload\ (.*)\n/i', $comments, $doc);
239
        if (count($doc)) {
240
            $namespace = str_replace('{__API__}', $model, $doc[1]);
241
            $payload = $this->extractModelFields($namespace);
242
            $reflector = new \ReflectionClass($namespace);
243
            $namespace = $reflector->getShortName();
244
        } else {
245
            $namespace = $model;
246
        }
247
248
        return [$namespace, $payload];
249
    }
250
251
    /**
252
     * Extract all the properties from Dto class
253
     *
254
     * @param string $class
255
     *
256
     * @return array
257
     */
258
    protected function extractDtoProperties($class)
259
    {
260
        $properties = [];
261
        $reflector = new \ReflectionClass($class);
262
        if ($reflector->isSubclassOf(self::DTO_INTERFACE)) {
263
            $properties = array_merge($properties, InjectorHelper::extractVariables($reflector));
264
        }
265
266
        return $properties;
267
    }
268
269
    /**
270
     * Extract return class for api endpoint
271
     *
272
     * @param string $model
273
     * @param string $comments
274
     *
275
     * @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...
276
     */
277
    protected function extractReturn($model, $comments = '')
278
    {
279
        $modelDto = [];
280
        preg_match('/\@return\ (.*)\((.*)\)\n/i', $comments, $returnTypes);
281
        if (count($returnTypes)) {
282
            // Extract principal DTO information
283
            if (array_key_exists(1, $returnTypes)) {
284
                $modelDto = $this->extractDtoProperties($returnTypes[1]);
285
            }
286
            if (array_key_exists(2, $returnTypes)) {
287
                $subDtos = preg_split('/,?\ /', str_replace('{__API__}', $model, $returnTypes[2]));
288
                if (count($subDtos)) {
289
                    foreach ($subDtos as $subDto) {
290
                        $isArray = false;
291
                        list($field, $dtoName) = explode('=', $subDto);
292
                        if (false !== strpos($dtoName, '[') && false !== strpos($dtoName, ']')) {
293
                            $dtoName = str_replace(']', '', str_replace('[', '', $dtoName));
294
                            $isArray = true;
295
                        }
296
                        $dto = $this->extractModelFields($dtoName);
297
                        $modelDto[$field] = ($isArray) ? [$dto] : $dto;
298
                        $modelDto['objects'][$dtoName] = $dto;
299
                    }
300
                }
301
            }
302
        }
303
304
        return $modelDto;
305
    }
306
307
    /**
308
     * Extract all fields from a ActiveResource model
309
     *
310
     * @param string $namespace
311
     *
312
     * @return mixed
313
     */
314
    protected function extractModelFields($namespace)
315
    {
316
        $payload = [];
317
        try {
318
            $reflector = new \ReflectionClass($namespace);
319
            // Checks if reflector is a subclass of propel ActiveRecords
320
            if (NULL !== $reflector && $reflector->isSubclassOf(self::MODEL_INTERFACE)) {
321
                $tableMap = $namespace::TABLE_MAP;
322
                $tableMap = $tableMap::getTableMap();
323
                /** @var ColumnMap $field */
324
                foreach ($tableMap->getColumns() as $field) {
325
                    list($type, $format) = DocumentorService::translateSwaggerFormats($field->getType());
326
                    $info = [
327
                        "type" => $type,
328
                        "required" => $field->isNotNull(),
329
                        'format' => $format,
330
                    ];
331
                    $payload[$field->getPhpName()] = $info;
332
                }
333
            } elseif (null !== $reflector && $reflector->isSubclassOf(self::DTO_INTERFACE)) {
334
                $payload = $this->extractDtoProperties($namespace);
335
            }
336
        } catch (\Exception $e) {
337
            Logger::getInstance()->errorLog($e->getMessage());
338
        }
339
340
        return $payload;
341
    }
342
343
    /**
344
     * Method that extract all the needed info for each method in each API
345
     *
346
     * @param string $namespace
347
     * @param \ReflectionMethod $method
348
     * @param \ReflectionClass $reflection
349
     * @param string $module
350
     *
351
     * @return array
352
     */
353
    protected function extractMethodInfo($namespace, \ReflectionMethod $method, \ReflectionClass $reflection, $module)
354
    {
355
        $methodInfo = NULL;
356
        $docComments = $method->getDocComment();
357
        if (FALSE !== $docComments && preg_match('/\@route\ /i', $docComments)) {
358
            $api = self::extractApi($reflection->getDocComment());
359
            list($route, $info) = RouterHelper::extractRouteInfo($method, $api, $module);
360
            $route = explode('#|#', $route);
361
            $modelNamespace = str_replace('Api', 'Models', $namespace);
362
            if ($info['visible'] && !self::checkDeprecated($docComments)) {
363
                try {
364
                    $return = $this->extractReturn($modelNamespace, $docComments);
365
                    $url = array_pop($route);
366
                    $methodInfo = [
367
                        'url' => str_replace("/" . $module . "/api", '', $url),
368
                        'method' => $info['http'],
369
                        'description' => $info['label'],
370
                        'return' => $return,
371
                        'objects' => $return['objects'],
372
                        'class' => $reflection->getShortName(),
373
                    ];
374
                    unset($methodInfo['return']['objects']);
375
                    $this->setRequestParams($method, $methodInfo, $modelNamespace, $docComments);
376
                    $this->setQueryParams($method, $methodInfo);
377
                    $this->setRequestHeaders($reflection, $methodInfo);
378
                } catch (\Exception $e) {
379
                    Logger::getInstance()->errorLog($e->getMessage());
380
                }
381
            }
382
        }
383
384
        return $methodInfo;
385
    }
386
387
    /**
388
     * Translator from php types to swagger types
389
     * @param string $format
390
     *
391
     * @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...
392
     */
393 1
    public static function translateSwaggerFormats($format)
394
    {
395 1
        switch (strtolower($format)) {
396 1
            case 'bool':
397 1
            case 'boolean':
398
                $swaggerType = 'boolean';
399
                $swaggerFormat = '';
400
                break;
401
            default:
402 1
            case 'string':
403 1
            case 'varchar':
404 1
                $swaggerType = 'string';
405 1
                $swaggerFormat = '';
406 1
                break;
407 1
            case 'binary':
408 1
            case 'varbinary':
409
                $swaggerType = 'string';
410
                $swaggerFormat = 'binary';
411
                break;
412 1
            case 'int':
413 1
            case 'integer':
414 1
                $swaggerType = 'integer';
415 1
                $swaggerFormat = 'int32';
416 1
                break;
417
            case 'float':
418
            case 'double':
419
                $swaggerType = 'number';
420
                $swaggerFormat = strtolower($format);
421
                break;
422
            case 'date':
423
                $swaggerType = 'string';
424
                $swaggerFormat = 'date';
425
                break;
426
            case 'datetime':
427
                $swaggerType = 'string';
428
                $swaggerFormat = 'date-time';
429
                break;
430
431
        }
432 1
        return [$swaggerType, $swaggerFormat];
433
    }
434
435
    /**
436
     * Method that parse the definitions for the api's
437
     * @param string $name
438
     * @param array $fields
439
     *
440
     * @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...
441
     */
442
    public static function extractSwaggerDefinition($name, array $fields)
443
    {
444
        $definition = [
445
            $name => [
446
                "type" => "object",
447
                "properties" => [],
448
            ],
449
        ];
450
        foreach ($fields as $field => $info) {
451
            list($type, $format) = self::translateSwaggerFormats($info['type']);
452
            $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...
453
                "type" => $type,
454
                "required" => $info['required'],
455
            ];
456
            $definition[$name]['properties'][$field] = [
457
                "type" => $type,
458
                "required" => $info['required'],
459
            ];
460
            if (strlen($format)) {
461
                $definition[$name]['properties'][$field]['format'] = $format;
462
            }
463
        }
464
        return $definition;
465
    }
466
467
    /**
468
     * @return array
469
     */
470
    private static function swaggerResponses()
471
    {
472
        $codes = [200, 400, 404, 500];
473
        $responses = [];
474
        foreach ($codes as $code) {
475
            switch ($code) {
476
                default:
477
                case 200:
478
                    $message = _('Successful response');
479
                    break;
480
                case 400:
481
                    $message = _('Client error in request');
482
                    break;
483
                case 404:
484
                    $message = _('Service not found');
485
                    break;
486
                case 500:
487
                    $message = _('Server error');
488
                    break;
489
            }
490
            $responses[$code] = [
491
                'description' => $message,
492
                'schema' => [
493
                    'type' => 'object',
494
                    'properties' => [
495
                        'success' => [
496
                            'type' => 'boolean'
497
                        ],
498
                        'data' => [
499
                            'type' => 'boolean',
500
                        ],
501
                        'total' => [
502
                            'type' => 'integer',
503
                            'format' => 'int32',
504
                        ],
505
                        'pages' => [
506
                            'type' => 'integer',
507
                            'format' => 'int32',
508
                        ]
509
                    ]
510
                ]
511
            ];
512
        }
513
        return $responses;
514
    }
515
516
    /**
517
     * Method that export
518
     * @param array $module
519
     *
520
     * @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...
521
     */
522
    public static function swaggerFormatter(array $module)
523
    {
524
        $formatted = [
525
            "swagger" => "2.0",
526
            "host" => preg_replace('/^(http|https)\:\/\/(.*)\/$/i', '$2', Router::getInstance()->getRoute('', true)),
527
            "basePath" => '/' . $module['name'] . '/api',
528
            "schemes" => [Request::getInstance()->getServer('HTTPS') == 'on' ? "https" : "http"],
529
            "info" => [
530
                "title" => _('Documentación API módulo ') . $module['name'],
531
                "version" => Config::getParam('api.version', '1.0'),
532
                "contact" => [
533
                    "name" => Config::getParam("author", "Fran López"),
534
                    "email" => Config::getParam("author_email", "[email protected]"),
535
                ]
536
            ]
537
        ];
538
        $dtos = $paths = [];
539
        $endpoints = DocumentorService::getInstance()->extractApiEndpoints($module);
540
        foreach ($endpoints as $model) {
541
            foreach ($model as $endpoint) {
542
                if (!preg_match('/^\/(admin|api)\//i', $endpoint['url']) && strlen($endpoint['url'])) {
543
                    $url = preg_replace('/\/' . $module['name'] . '\/api/i', '', $endpoint['url']);
544
                    $description = $endpoint['description'];
545
                    $method = strtolower($endpoint['method']);
546
                    $paths[$url][$method] = [
547
                        'summary' => $description,
548
                        'produces' => ['application/json'],
549
                        'consumes' => ['application/json'],
550
                        'responses' => self::swaggerResponses(),
551
                        'parameters' => [],
552
                    ];
553
                    if (array_key_exists('parameters', $endpoint)) {
554
                        foreach ($endpoint['parameters'] as $parameter => $type) {
555
                            list($type, $format) = self::translateSwaggerFormats($type);
556
                            $paths[$url][$method]['parameters'][] = [
557
                                'in' => 'path',
558
                                'required' => true,
559
                                'name' => $parameter,
560
                                'type' => $type,
561
                                'format' => $format,
562
                            ];
563
                        }
564
                    }
565 View Code Duplication
                    if (array_key_exists('query', $endpoint)) {
566
                        foreach ($endpoint['query'] as $query) {
567
                            $paths[$url][$method]['parameters'][] = $query;
568
                        }
569
                    }
570 View Code Duplication
                    if (array_key_exists('headers', $endpoint)) {
571
                        foreach ($endpoint['headers'] as $query) {
572
                            $paths[$url][$method]['parameters'][] = $query;
573
                        }
574
                    }
575
                    foreach ($endpoint['objects'] as $name => $object) {
576
                        if (class_exists($name)) {
577
                            $class = GeneratorHelper::extractClassFromNamespace($name);
578
                            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...
579
                                $classDefinition = [
580
                                    'type' => 'object',
581
                                    '$ref' => '#/definitions/' . $class,
582
                                ];
583
                            } else {
584
                                $classDefinition = [
585
                                    'type' => 'array',
586
                                    'items' => [
587
                                        '$ref' => '#/definitions/' . $class,
588
                                    ],
589
                                ];
590
                            }
591
592
                            $paths[$url][$method]['responses'][200]['schema']['properties']['data'] = $classDefinition;
593
                            $dtos += self::extractSwaggerDefinition($class, $object);
594
                            if (array_key_exists('payload', $endpoint)) {
595
                                $dtos[$endpoint['payload']['type']] = [
596
                                    'type' => 'object',
597
                                    'properties' => $endpoint['payload']['properties'],
598
                                ];
599
                                $paths[$url][$method]['parameters'][] = [
600
                                    'in' => 'body',
601
                                    'name' => $endpoint['payload']['type'],
602
                                    'required' => true,
603
                                    'schema' => [
604
                                        'type' => 'object',
605
                                        '$ref' => '#/definitions/' . $endpoint['payload']['type'],
606
                                    ],
607
                                ];
608
                            }
609
                        }
610
                        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...
611
                            $paths[$url][$method]['tags'][] = $endpoint['class'];
612
                        }
613
                    }
614
                }
615
            }
616
        }
617
        $formatted['definitions'] = $dtos;
618
        $formatted['paths'] = $paths;
619
        return $formatted;
620
    }
621
622
    /**
623
     * Method that extract the Dto class for the api documentation
624
     * @param string $dto
625
     * @param boolean $isArray
626
     *
627
     * @return string
628
     */
629
    protected function extractDtoName($dto, $isArray = false)
630
    {
631
        $dto = explode('\\', $dto);
632
        $modelDto = array_pop($dto) . "Dto";
633
        if ($isArray) {
634
            $modelDto .= "List";
635
        }
636
637
        return $modelDto;
638
    }
639
640
    /**
641
     * @param \ReflectionMethod $method
642
     * @param $methodInfo
643
     */
644
    protected function setQueryParams(\ReflectionMethod $method, &$methodInfo)
645
    {
646
        if (in_array($methodInfo['method'], ['GET']) && in_array($method->getShortName(), self::$nativeMethods)) {
647
            $methodInfo['query'] = [];
648
            $methodInfo['query'][] = [
649
                "name" => "__limit",
650
                "in" => "query",
651
                "description" => _("Límite de registros a devolver, -1 para devolver todos los registros"),
652
                "required" => false,
653
                "type" => "integer",
654
            ];
655
            $methodInfo['query'][] = [
656
                "name" => "__page",
657
                "in" => "query",
658
                "description" => _("Página a devolver"),
659
                "required" => false,
660
                "type" => "integer",
661
            ];
662
            $methodInfo['query'][] = [
663
                "name" => "__fields",
664
                "in" => "query",
665
                "description" => _("Campos a devolver"),
666
                "required" => false,
667
                "type" => "array",
668
                "items" => [
669
                    "type" => "string",
670
                ]
671
            ];
672
        }
673
    }
674
    /**
675
     * @param \ReflectionClass $reflection
676
     * @param $methodInfo
677
     */
678
    protected function setRequestHeaders(\ReflectionClass $reflection, &$methodInfo)
679
    {
680
681
        $methodInfo['headers'] = [];
682
        foreach($reflection->getProperties() as $property) {
683
            $doc = $property->getDocComment();
684
            preg_match('/@header\ (.*)\n/i', $doc, $headers);
685
            if(count($headers)) {
686
                $header = [
687
                    "name" => $headers[1],
688
                    "in" => "header",
689
                    "required" => true,
690
                ];
691
692
                // Extract var type
693
                $header['type'] = $this->extractVarType($doc);
694
695
                // Extract description
696
                preg_match('/@label\ (.*)\n/i', $doc, $label);
697
                if(count($label)) {
698
                    $header['description'] = _($label[1]);
699
                }
700
701
                // Extract default value
702
                preg_match('/@default\ (.*)\n/i', $doc, $default);
703
                if(count($default)) {
704
                    $header['default'] = $default[1];
705
                }
706
                $methodInfo['headers'][] = $header;
707
            }
708
        }
709
    }
710
711
    /**
712
     * @param \ReflectionMethod $method
713
     * @param array $methodInfo
714
     * @param string $modelNamespace
715
     * @param string $docComments
716
     */
717
    protected function setRequestParams(\ReflectionMethod $method, &$methodInfo, $modelNamespace, $docComments)
718
    {
719
        if (in_array($methodInfo['method'], ['POST', 'PUT'])) {
720
            list($payloadNamespace, $payloadDto) = $this->extractPayload($modelNamespace, $docComments);
721
            if (count($payloadDto)) {
722
                $methodInfo['payload'] = [
723
                    'type' => $payloadNamespace,
724
                    'properties' => $payloadDto,
725
                ];
726
            }
727
        }
728
        if ($method->getNumberOfParameters() > 0) {
729
            $methodInfo['parameters'] = [];
730
            foreach ($method->getParameters() as $parameter) {
731
                $parameterName = $parameter->getName();
732
                $types = [];
733
                preg_match_all('/\@param\ (.*)\ \$' . $parameterName . '$/im', $docComments, $types);
734
                if (count($types) > 1) {
735
                    $methodInfo['parameters'][$parameterName] = $types[1][0];
736
                }
737
            }
738
        }
739
    }
740
}
741