Passed
Push — master ( 0ee5cf...8209bc )
by Fran
02:47
created

DocumentorService::extractRoute()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 3
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 6
ccs 0
cts 4
cp 0
crap 2
rs 10
1
<?php
2
3
namespace PSFS\services;
4
5
use Exception;
6
use Propel\Runtime\ActiveRecord\ActiveRecordInterface;
7
use Propel\Runtime\Map\ColumnMap;
8
use PSFS\base\config\Config;
9
use PSFS\base\dto\Dto;
10
use PSFS\base\Logger;
11
use PSFS\base\Request;
12
use PSFS\base\Router;
13
use PSFS\base\Service;
14
use PSFS\base\types\helpers\ApiHelper;
15
use PSFS\base\types\helpers\DocumentorHelper;
16
use PSFS\base\types\helpers\I18nHelper;
17
use PSFS\base\types\helpers\InjectorHelper;
18
use PSFS\base\types\helpers\RouterHelper;
19
use ReflectionClass;
20
use ReflectionException;
21
use ReflectionMethod;
22
use SplFileInfo;
23
use Symfony\Component\Finder\Finder;
24
25
/**
26
 * Class DocumentorService
27
 * @package PSFS\services
28
 */
29
class DocumentorService extends Service
30
{
31
    public static $nativeMethods = [
32
        'modelList', // Api list
33
        'get', // Api get
34
        'post', // Api post
35
        'put', // Api put
36
        'delete', // Api delete
37
    ];
38
39
    const DTO_INTERFACE = Dto::class;
40
    const MODEL_INTERFACE = ActiveRecordInterface::class;
41
42
    /**
43
     * @Injectable
44
     * @var Router route
45
     */
46
    protected $route;
47
48
49
    /**
50
     * Method that extract all modules
51
     * @param string $requestModule
52
     * @return array
53
     */
54
    public function getModules($requestModule)
55
    {
56
        $modules = [];
57
        $domains = $this->route->getDomains();
58
        if (count($domains)) {
59
            foreach ($domains as $module => $info) {
60
                try {
61
                    $module = preg_replace('/(@|\/)/', '', $module);
62
                    if ($module === $requestModule && !preg_match('/^ROOT/i', $module)) {
63
                        $modules = [
64
                            'name' => $module,
65
                            'path' => realpath(dirname($info['base'] . DIRECTORY_SEPARATOR . '..')),
66
                        ];
67
                    }
68
                } catch (Exception $e) {
69
                    $modules[] = $e->getMessage();
70
                }
71
            }
72
        }
73
74
        return $modules;
75
    }
76
77
    /**
78
     * Method that extract all endpoints for each module
79
     *
80
     * @param array $module
81
     *
82
     * @return array
83
     */
84
    public function extractApiEndpoints(array $module)
85
    {
86
        $modulePath = $module['path'] . DIRECTORY_SEPARATOR . 'Api';
87
        $moduleName = $module['name'];
88
        $endpoints = [];
89
        if (file_exists($modulePath)) {
90
            $finder = new Finder();
91
            $finder->files()->in($modulePath)->depth('< 2')->name('*.php');
92
            if (count($finder)) {
93
                /** @var SplFileInfo $file */
94
                foreach ($finder as $file) {
95
                    $filename = str_replace([$modulePath, '/'], ['', '\\'], $file->getPathname());
96
                    $namespace = "\\{$moduleName}\\Api" . str_replace('.php', '', $filename);
97
                    $info = $this->extractApiInfo($namespace, $moduleName);
98
                    if (!empty($info)) {
99
                        $endpoints[$namespace] = $info;
100
                    }
101
                }
102
            }
103
        }
104
        return $endpoints;
105
    }
106
107
    /**
108
     * @param $namespace
109
     * @param $module
110
     * @return array
111
     * @throws ReflectionException
112
     */
113
    public function extractApiInfo($namespace, $module)
114
    {
115
        $info = [];
116
        if (Router::exists($namespace) && !I18nHelper::checkI18Class($namespace)) {
117
            $reflection = new ReflectionClass($namespace);
118
            $visible = InjectorHelper::checkIsVisible($reflection->getDocComment());
119
            if ($visible) {
120
                foreach ($reflection->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
121
                    try {
122
                        $mInfo = $this->extractMethodInfo($namespace, $method, $reflection, $module);
123
                        if (NULL !== $mInfo) {
124
                            $info[] = $mInfo;
125
                        }
126
                    } catch (Exception $e) {
127
                        Logger::log($e->getMessage(), LOG_ERR);
128
                    }
129
                }
130
            }
131
        }
132
        return $info;
133
    }
134
135
    /**
136
     * Extract api from doc comments
137
     *
138
     * @param string $comments
139
     *
140
     * @return string
141
     */
142
    protected function extractApi($comments = '')
143
    {
144
        $api = '';
145
        preg_match('/@api\ (.*)\n/i', $comments, $api);
0 ignored issues
show
Bug introduced by
$api of type string is incompatible with the type array|null expected by parameter $matches of preg_match(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

145
        preg_match('/@api\ (.*)\n/i', $comments, /** @scrutinizer ignore-type */ $api);
Loading history...
146
147
        return $api[1];
148
    }
149
150
    /**
151
     * Extract api from doc comments
152
     *
153
     * @param string $comments
154
     *
155
     * @return boolean
156
     */
157
    protected function checkDeprecated($comments = '')
158
    {
159
        return false != preg_match('/@deprecated\n/i', $comments);
0 ignored issues
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...
160
    }
161
162
    /**
163
     * Extract visibility from doc comments
164
     *
165
     * @param string $comments
166
     *
167
     * @return boolean
168
     */
169
    protected function extractVisibility($comments = '')
170
    {
171
        $visible = TRUE;
172
        preg_match('/@visible\ (true|false)\n/i', $comments, $visibility);
173
        if (count($visibility)) {
174
            $visible = !('false' === $visibility[1]);
175
        }
176
177
        return $visible;
178
    }
179
180
    /**
181
     * Method that extract the description for the endpoint
182
     *
183
     * @param string $comments
184
     *
185
     * @return string
186
     */
187
    protected function extractDescription($comments = '')
188
    {
189
        $description = '';
190
        $docs = explode("\n", $comments);
191
        if (count($docs)) {
192
            foreach ($docs as &$doc) {
193
                if (!preg_match('/(\*\*|\@)/', $doc) && preg_match('/\*\ /', $doc)) {
194
                    $doc = explode('* ', $doc);
195
                    $description = $doc[1];
196
                }
197
            }
198
        }
199
200
        return $description;
201
    }
202
203
    /**
204
     * Method that extract the type of a variable
205
     *
206
     * @param string $comments
207
     *
208
     * @return string
209
     */
210
    public static function extractVarType($comments = '')
211
    {
212
        $type = 'string';
213
        preg_match('/@var\ (.*) (.*)\n/i', $comments, $varType);
214
        if (count($varType)) {
215
            $aux = trim($varType[1]);
216
            $type = str_replace(' ', '', strlen($aux) > 0 ? $varType[1] : $varType[2]);
217
        }
218
219
        return $type;
220
    }
221
222
    /**
223
     * Method that extract the payload for the endpoint
224
     *
225
     * @param string $model
226
     * @param string $comments
227
     *
228
     * @return array
229
     */
230
    protected function extractPayload($model, $comments = '')
231
    {
232
        $payload = [];
233
        preg_match('/@payload\ (.*)\n/i', $comments, $doc);
234
        $isArray = false;
235
        if (count($doc)) {
236
            $namespace = str_replace('{__API__}', $model, $doc[1]);
237
            if (false !== strpos($namespace, '[') && false !== strpos($namespace, ']')) {
238
                $namespace = str_replace(']', '', str_replace('[', '', $namespace));
239
                $isArray = true;
240
            }
241
            $payload = $this->extractModelFields($namespace);
242
            $reflector = new ReflectionClass($namespace);
243
            $shortName = $reflector->getShortName();
244
        } else {
245
            $namespace = $model;
246
            $shortName = $model;
247
        }
248
249
        return [$namespace, $shortName, $payload, $isArray];
250
    }
251
252
    /**
253
     * Extract all the properties from Dto class
254
     *
255
     * @param string $class
256
     *
257
     * @return array
258
     */
259
    protected function extractDtoProperties($class)
260
    {
261
        $properties = [];
262
        $reflector = new ReflectionClass($class);
263
        if ($reflector->isSubclassOf(self::DTO_INTERFACE)) {
264
            $properties = array_merge($properties, InjectorHelper::extractVariables($reflector));
265
        }
266
267
        return $properties;
268
    }
269
270
    /**
271
     * Extract return class for api endpoint
272
     *
273
     * @param string $model
274
     * @param string $comments
275
     *
276
     * @return array
277
     */
278
    protected function extractReturn($model, $comments = '')
279
    {
280
        $modelDto = [];
281
        preg_match('/\@return\ (.*)\((.*)\)\n/i', $comments, $returnTypes);
282
        if (count($returnTypes)) {
283
            // Extract principal DTO information
284
            if (array_key_exists(1, $returnTypes)) {
285
                $modelDto = $this->extractDtoProperties($returnTypes[1]);
286
            }
287
            if (array_key_exists(2, $returnTypes)) {
288
                $subDtos = preg_split('/,?\ /', str_replace('{__API__}', $model, $returnTypes[2]));
289
                if (count($subDtos)) {
0 ignored issues
show
Bug introduced by
It seems like $subDtos can also be of type false; however, parameter $var of count() does only seem to accept Countable|array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

289
                if (count(/** @scrutinizer ignore-type */ $subDtos)) {
Loading history...
290
                    foreach ($subDtos as $subDto) {
291
                        list($field, $dtoName) = explode('=', $subDto);
292
                        $isArray = false;
293
                        if (false !== strpos($dtoName, '[') && false !== strpos($dtoName, ']')) {
294
                            $dtoName = str_replace(']', '', str_replace('[', '', $dtoName));
295
                            $isArray = true;
296
                        }
297
                        $dto = $this->extractModelFields($dtoName);
298
                        $modelDto[$field] = $isArray ? [$dto] : $dto;
299
                        $modelDto['objects'][$dtoName] = $dto;
300
                        $modelDto = $this->checkDtoAttributes($dto, $modelDto, $dtoName);
301
                    }
302
                }
303
            }
304
        }
305
306
        return $modelDto;
307
    }
308
309
    /**
310
     * Extract all fields from a ActiveResource model
311
     *
312
     * @param string $namespace
313
     *
314
     * @return mixed
315
     */
316
    protected function extractModelFields($namespace)
317
    {
318
        $payload = [];
319
        try {
320
            $reflector = new ReflectionClass($namespace);
321
            // Checks if reflector is a subclass of propel ActiveRecords
322
            if (NULL !== $reflector && $reflector->isSubclassOf(self::MODEL_INTERFACE)) {
323
                $tableMap = $namespace::TABLE_MAP;
324
                $tableMap = $tableMap::getTableMap();
325
                /** @var ColumnMap $field */
326
                foreach ($tableMap->getColumns() as $field) {
327
                    list($type, $format) = DocumentorHelper::translateSwaggerFormats($field->getType());
328
                    $info = [
329
                        "type" => $type,
330
                        "required" => $field->isNotNull(),
331
                        'format' => $format,
332
                    ];
333
                    if (count($field->getValueSet())) {
334
                        $info['enum'] = array_values($field->getValueSet());
335
                    }
336
                    if (null !== $field->getDefaultValue()) {
337
                        $info['default'] = $field->getDefaultValue();
338
                    }
339
                    $payload[ApiHelper::getColumnMapName($field)] = $info;
340
                }
341
            } elseif (null !== $reflector && $reflector->isSubclassOf(self::DTO_INTERFACE)) {
342
                $payload = $this->extractDtoProperties($namespace);
343
            }
344
        } catch (Exception $e) {
345
            Logger::log($e->getMessage(), LOG_ERR);
346
        }
347
348
        return $payload;
349
    }
350
351
    /**
352
     * Method that extract all the needed info for each method in each API
353
     *
354
     * @param string $namespace
355
     * @param ReflectionMethod $method
356
     * @param ReflectionClass $reflection
357
     * @param string $module
358
     *
359
     * @return array
360
     */
361
    protected function extractMethodInfo($namespace, ReflectionMethod $method, ReflectionClass $reflection, $module)
362
    {
363
        $methodInfo = NULL;
364
        $docComments = $method->getDocComment();
365
        if (FALSE !== $docComments && preg_match('/\@route\ /i', $docComments)) {
0 ignored issues
show
Bug introduced by
It seems like $docComments can also be of type true; however, parameter $subject of preg_match() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

365
        if (FALSE !== $docComments && preg_match('/\@route\ /i', /** @scrutinizer ignore-type */ $docComments)) {
Loading history...
366
            $api = $this->extractApi($reflection->getDocComment());
367
            list($route, $info) = RouterHelper::extractRouteInfo($method, $api, $module);
368
            $route = explode('#|#', $route);
369
            $modelNamespace = str_replace('Api', 'Models', $namespace);
370
            if ($info['visible'] && !$this->checkDeprecated($docComments)) {
0 ignored issues
show
Bug introduced by
It seems like $docComments can also be of type true; however, parameter $comments of PSFS\services\DocumentorService::checkDeprecated() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

370
            if ($info['visible'] && !$this->checkDeprecated(/** @scrutinizer ignore-type */ $docComments)) {
Loading history...
371
                try {
372
                    $return = $this->extractReturn($modelNamespace, $docComments);
0 ignored issues
show
Bug introduced by
It seems like $docComments can also be of type true; however, parameter $comments of PSFS\services\DocumentorService::extractReturn() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

372
                    $return = $this->extractReturn($modelNamespace, /** @scrutinizer ignore-type */ $docComments);
Loading history...
373
                    $url = array_pop($route);
374
                    $methodInfo = [
375
                        'url' => str_replace('/' . $module . '/api', '', $url),
376
                        'method' => $info['http'],
377
                        'description' => $info['label'],
378
                        'return' => $return,
379
                        'objects' => array_key_exists('objects', $return) ? $return['objects'] : [],
380
                        'class' => $reflection->getShortName(),
381
                    ];
382
                    unset($methodInfo['return']['objects']);
383
                    $this->setRequestParams($method, $methodInfo, $modelNamespace, $docComments);
0 ignored issues
show
Bug introduced by
It seems like $docComments can also be of type true; however, parameter $docComments of PSFS\services\Documentor...ice::setRequestParams() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

383
                    $this->setRequestParams($method, $methodInfo, $modelNamespace, /** @scrutinizer ignore-type */ $docComments);
Loading history...
384
                    $this->setQueryParams($method, $methodInfo);
385
                    $this->setRequestHeaders($reflection, $methodInfo);
386
                } catch (Exception $e) {
387
                    Logger::log($e->getMessage(), LOG_ERR);
388
                }
389
            }
390
        }
391
392
        return $methodInfo;
393
    }
394
395
    /**
396
     * @return array
397
     */
398
    private static function swaggerResponses()
399
    {
400
        $codes = [200, 400, 404, 500];
401
        $responses = [];
402
        foreach ($codes as $code) {
403
            switch ($code) {
404
                default:
405
                case 200:
406
                    $message = t('Successful response');
407
                    break;
408
                case 400:
409
                    $message = t('Client error in request');
410
                    break;
411
                case 404:
412
                    $message = t('Service not found');
413
                    break;
414
                case 500:
415
                    $message = t('Server error');
416
                    break;
417
            }
418
            $responses[$code] = [
419
                'description' => $message,
420
                'schema' => [
421
                    'type' => 'object',
422
                    'properties' => [
423
                        'success' => [
424
                            'type' => 'boolean'
425
                        ],
426
                        'message' => [
427
                            'type' => 'string'
428
                        ],
429
                        'data' => [
430
                            'type' => 'boolean',
431
                        ],
432
                        'total' => [
433
                            'type' => 'integer',
434
                            'format' => 'int32',
435
                        ],
436
                        'pages' => [
437
                            'type' => 'integer',
438
                            'format' => 'int32',
439
                        ]
440
                    ]
441
                ]
442
            ];
443
        }
444
        return $responses;
445
    }
446
447
    /**
448
     * Method that export
449
     * @param array $module
450
     *
451
     * @return array
452
     */
453
    public static function swaggerFormatter(array $module)
454
    {
455
        $formatted = [
456
            "swagger" => "2.0",
457
            "host" => preg_replace('/^(http|https)\:\/\/(.*)\/$/i', '$2', Router::getInstance()->getRoute('', true)),
458
            "basePath" => '/' . $module['name'] . '/api',
459
            "schemes" => [Request::getInstance()->getServer('HTTPS') === 'on' ? 'https' : 'http'],
460
            "info" => [
461
                "title" => t('Documentación API módulo ') . $module['name'],
462
                "version" => Config::getParam('api.version', '1.0.0'),
463
                "contact" => [
464
                    "name" => Config::getParam("author", "Fran López"),
465
                    "email" => Config::getParam("author.email", "[email protected]"),
466
                ]
467
            ]
468
        ];
469
        $dtos = $paths = [];
470
        $endpoints = DocumentorService::getInstance()->extractApiEndpoints($module);
471
        foreach ($endpoints as $model) {
472
            foreach ($model as $endpoint) {
473
                if (!preg_match('/^\/(admin|api)\//i', $endpoint['url']) && strlen($endpoint['url'])) {
474
                    $url = preg_replace('/\/' . $module['name'] . '\/api/i', '', $endpoint['url']);
475
                    $description = $endpoint['description'];
476
                    $method = strtolower($endpoint['method']);
477
                    $paths[$url][$method] = [
478
                        'summary' => $description,
479
                        'produces' => ['application/json'],
480
                        'consumes' => ['application/json'],
481
                        'responses' => self::swaggerResponses(),
482
                        'parameters' => [],
483
                    ];
484
                    if (array_key_exists('parameters', $endpoint)) {
485
                        foreach ($endpoint['parameters'] as $parameter => $type) {
486
                            list($type, $format) = DocumentorHelper::translateSwaggerFormats($type);
487
                            $paths[$url][$method]['parameters'][] = [
488
                                'in' => 'path',
489
                                'required' => true,
490
                                'name' => $parameter,
491
                                'type' => $type,
492
                                'format' => $format,
493
                            ];
494
                        }
495
                    }
496
                    if (array_key_exists('query', $endpoint)) {
497
                        foreach ($endpoint['query'] as $query) {
498
                            $paths[$url][$method]['parameters'][] = $query;
499
                        }
500
                    }
501
                    if (array_key_exists('headers', $endpoint)) {
502
                        foreach ($endpoint['headers'] as $query) {
503
                            $paths[$url][$method]['parameters'][] = $query;
504
                        }
505
                    }
506
                    $isReturn = true;
507
                    foreach ($endpoint['objects'] as $name => $object) {
508
                        DocumentorHelper::parseObjects($paths, $dtos, $name, $endpoint, $object, $url, $method, $isReturn);
509
                        $isReturn = false;
510
                    }
511
                }
512
            }
513
        }
514
        ksort($dtos);
515
        uasort($paths, function ($path1, $path2) {
516
            $key1 = array_keys($path1)[0];
517
            $key2 = array_keys($path2)[0];
518
            return strcmp($path1[$key1]['tags'][0], $path2[$key2]['tags'][0]);
519
        });
520
        $formatted['definitions'] = $dtos;
521
        $formatted['paths'] = $paths;
522
        return $formatted;
523
    }
524
525
    /**
526
     * Method that extract the Dto class for the api documentation
527
     * @param string $dto
528
     * @param boolean $isArray
529
     *
530
     * @return string
531
     */
532
    protected function extractDtoName($dto, $isArray = false)
533
    {
534
        $dto = explode('\\', $dto);
535
        $modelDto = array_pop($dto) . "Dto";
536
        if ($isArray) {
537
            $modelDto .= "List";
538
        }
539
540
        return $modelDto;
541
    }
542
543
    /**
544
     * @param ReflectionMethod $method
545
     * @param $methodInfo
546
     */
547
    protected function setQueryParams(ReflectionMethod $method, &$methodInfo)
548
    {
549
        if (in_array($methodInfo['method'], ['GET']) && in_array($method->getShortName(), self::$nativeMethods)) {
550
            $methodInfo['query'] = [];
551
            $methodInfo['query'][] = [
552
                "name" => "__limit",
553
                "in" => "query",
554
                "description" => t("Límite de registros a devolver, -1 para devolver todos los registros"),
555
                "required" => false,
556
                "type" => "integer",
557
            ];
558
            $methodInfo['query'][] = [
559
                "name" => "__page",
560
                "in" => "query",
561
                "description" => t("Página a devolver"),
562
                "required" => false,
563
                "type" => "integer",
564
            ];
565
            $methodInfo['query'][] = [
566
                "name" => "__fields",
567
                "in" => "query",
568
                "description" => t("Campos a devolver"),
569
                "required" => false,
570
                "type" => "array",
571
                "items" => [
572
                    "type" => "string",
573
                ]
574
            ];
575
        }
576
    }
577
578
    /**
579
     * @param ReflectionClass $reflection
580
     * @param $methodInfo
581
     */
582
    protected function setRequestHeaders(ReflectionClass $reflection, &$methodInfo)
583
    {
584
585
        $methodInfo['headers'] = [];
586
        foreach ($reflection->getProperties() as $property) {
587
            $doc = $property->getDocComment();
588
            preg_match('/@header\ (.*)\n/i', $doc, $headers);
589
            if (count($headers)) {
590
                $header = [
591
                    "name" => $headers[1],
592
                    "in" => "header",
593
                    "required" => true,
594
                ];
595
596
                // Extract var type
597
                $header['type'] = $this->extractVarType($doc);
598
599
                // Extract description
600
                $header['description'] = InjectorHelper::getLabel($doc);
601
602
                // Extract default value
603
                $header['default'] = InjectorHelper::getDefaultValue($doc);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $header['default'] is correct as PSFS\base\types\helpers\...::getDefaultValue($doc) targeting PSFS\base\types\helpers\...lper::getDefaultValue() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
604
605
                $methodInfo['headers'][] = $header;
606
            }
607
        }
608
    }
609
610
    /**
611
     * @param ReflectionMethod $method
612
     * @param array $methodInfo
613
     * @param string $modelNamespace
614
     * @param string $docComments
615
     */
616
    protected function setRequestParams(ReflectionMethod $method, &$methodInfo, $modelNamespace, $docComments)
617
    {
618
        if (in_array($methodInfo['method'], ['POST', 'PUT'])) {
619
            list($payloadNamespace, $payloadNamespaceShortName, $payloadDto, $isArray) = $this->extractPayload($modelNamespace, $docComments);
620
            if (count($payloadDto)) {
621
                $methodInfo['payload'] = [
622
                    'type' => $payloadNamespaceShortName,
623
                    'properties' => $payloadDto,
624
                    'is_array' => $isArray,
625
                ];
626
                $methodInfo = $this->checkDtoAttributes($payloadDto, $methodInfo, $payloadNamespace);
627
            }
628
        }
629
        if ($method->getNumberOfParameters() > 0) {
630
            $methodInfo['parameters'] = [];
631
            foreach ($method->getParameters() as $parameter) {
632
                $parameterName = $parameter->getName();
633
                $types = [];
634
                preg_match_all('/\@param\ (.*)\ \$' . $parameterName . '$/im', $docComments, $types);
635
                if (count($types) > 1 && count($types[1]) > 0) {
636
                    $methodInfo['parameters'][$parameterName] = $types[1][0];
637
                }
638
            }
639
        }
640
    }
641
642
    /**
643
     * @param $dto
644
     * @param $modelDto
645
     * @param $dtoName
646
     * @return array
647
     */
648
    protected function checkDtoAttributes($dto, $modelDto, $dtoName)
649
    {
650
        foreach ($dto as $param => &$info) {
651
            if (array_key_exists('class', $info)) {
652
                if ($info['is_array']) {
653
                    $modelDto['objects'][$dtoName][$param] = [
654
                        'type' => 'array',
655
                        'items' => [
656
                            '$ref' => '#/definitions/' . $info['type'],
657
                        ]
658
                    ];
659
                } else {
660
                    $modelDto['objects'][$dtoName][$param] = [
661
                        'type' => 'object',
662
                        '$ref' => '#/definitions/' . $info['type'],
663
                    ];
664
                }
665
                $modelDto['objects'][$info['class']] = $info['properties'];
666
                $paramDto = $this->checkDtoAttributes($info['properties'], $info['properties'], $info['class']);
667
                if (array_key_exists('objects', $paramDto)) {
668
                    $modelDto['objects'] = array_merge($modelDto['objects'], $paramDto['objects']);
669
                }
670
            } else {
671
                $modelDto['objects'][$dtoName][$param] = $info;
672
            }
673
        }
674
        return $modelDto;
675
    }
676
}
677