• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

voku / Simple-PHP-Code-Parser / 5703591739

pending completion
5703591739

push

github

voku
Merge branch 'master' of ssh://github.com/voku/Simple-PHP-Code-Parser

* 'master' of ssh://github.com/voku/Simple-PHP-Code-Parser:
  Update actions/cache action to v3
  Apply fixes from StyleCI
  Update codecov/codecov-action action to v3
  Update shivammathur/setup-php action to v2.24.0
  Update actions/cache action to v2.1.8

3 of 3 new or added lines in 1 file covered. (100.0%)

1293 of 1572 relevant lines covered (82.25%)

20.24 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.11
/src/voku/SimplePhpParser/Model/PHPClass.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace voku\SimplePhpParser\Model;
6

7
use PhpParser\Comment\Doc;
8
use PhpParser\Node\Stmt\Class_;
9
use ReflectionClass;
10
use voku\SimplePhpParser\Parsers\Helper\Utils;
11

12
class PHPClass extends BasePHPClass
13
{
14
    /**
15
     * @var string
16
     *
17
     * @phpstan-var class-string
18
     */
19
    public $name;
20

21
    /**
22
     * @var string|null
23
     *
24
     * @phpstan-var class-string|null
25
     */
26
    public $parentClass;
27

28
    /**
29
     * @var string[]
30
     *
31
     * @phpstan-var class-string[]
32
     */
33
    public $interfaces = [];
34

35
    /**
36
     * @param Class_ $node
37
     * @param null   $dummy
38
     *
39
     * @return $this
40
     */
41
    public function readObjectFromPhpNode($node, $dummy = null): self
42
    {
43
        $this->prepareNode($node);
41✔
44

45
        $this->name = static::getFQN($node);
41✔
46

47
        $this->is_final = $node->isFinal();
41✔
48

49
        $this->is_abstract = $node->isAbstract();
41✔
50

51
        $this->is_anonymous = $node->isAnonymous();
41✔
52

53
        $classExists = false;
41✔
54
        try {
55
            if (\class_exists($this->name, true)) {
41✔
56
                $classExists = true;
41✔
57
            }
58
        } catch (\Exception $e) {
×
59
            // nothing
60
        }
61
        if ($classExists) {
41✔
62
            $reflectionClass = Utils::createClassReflectionInstance($this->name);
35✔
63
            $this->readObjectFromReflection($reflectionClass);
35✔
64
        }
65

66
        $this->collectTags($node);
41✔
67

68
        if (!empty($node->extends)) {
41✔
69
            $classExtended = '';
26✔
70
            foreach ($node->extends->parts as $part) {
26✔
71
                $classExtended .= "\\" . $part;
26✔
72
            }
73
            /** @noinspection PhpSillyAssignmentInspection - hack for phpstan */
74
            /** @var class-string $classExtended */
75
            $classExtended = \ltrim($classExtended, '\\');
26✔
76
            $this->parentClass = $classExtended;
26✔
77
        }
78

79
        $docComment = $node->getDocComment();
41✔
80
        if ($docComment) {
41✔
81
            $this->readPhpDocProperties($docComment->getText());
38✔
82
        }
83

84
        foreach ($node->getProperties() as $property) {
41✔
85
            $propertyNameTmp = $this->getConstantFQN($property, $property->props[0]->name->name);
25✔
86

87
            if (isset($this->properties[$propertyNameTmp])) {
25✔
88
                $this->properties[$propertyNameTmp] = $this->properties[$propertyNameTmp]->readObjectFromPhpNode($property, $this->name);
25✔
89
            } else {
90
                $this->properties[$propertyNameTmp] = (new PHPProperty($this->parserContainer))->readObjectFromPhpNode($property, $this->name);
×
91
            }
92
        }
93

94
        foreach ($node->getMethods() as $method) {
41✔
95
            $methodNameTmp = $method->name->name;
35✔
96

97
            if (isset($this->methods[$methodNameTmp])) {
35✔
98
                $this->methods[$methodNameTmp] = $this->methods[$methodNameTmp]->readObjectFromPhpNode($method, $this->name);
32✔
99
            } else {
100
                $this->methods[$methodNameTmp] = (new PHPMethod($this->parserContainer))->readObjectFromPhpNode($method, $this->name);
3✔
101
            }
102

103
            if (!$this->methods[$methodNameTmp]->file) {
35✔
104
                $this->methods[$methodNameTmp]->file = $this->file;
3✔
105
            }
106
        }
107

108
        if (!empty($node->implements)) {
41✔
109
            foreach ($node->implements as $interfaceObject) {
9✔
110
                $interfaceFQN = '';
9✔
111
                foreach ($interfaceObject->parts as $interface) {
9✔
112
                    $interfaceFQN .= "\\" . $interface;
9✔
113
                }
114
                $interfaceFQN = \ltrim($interfaceFQN, '\\');
9✔
115
                /** @noinspection PhpSillyAssignmentInspection - hack for phpstan */
116
                /** @var class-string $interfaceFQN */
117
                $interfaceFQN = $interfaceFQN;
9✔
118
                $this->interfaces[$interfaceFQN] = $interfaceFQN;
9✔
119
            }
120
        }
121

122
        return $this;
41✔
123
    }
124

125
    /**
126
     * @param ReflectionClass $clazz
127
     *
128
     * @return $this
129
     */
130
    public function readObjectFromReflection($clazz): self
131
    {
132
        $this->name = $clazz->getName();
35✔
133

134
        if (!$this->line) {
35✔
135
            $lineTmp = $clazz->getStartLine();
26✔
136
            if ($lineTmp !== false) {
26✔
137
                $this->line = $lineTmp;
14✔
138
            }
139
        }
140

141
        $file = $clazz->getFileName();
35✔
142
        if ($file) {
35✔
143
            $this->file = $file;
35✔
144
        }
145

146
        $this->is_final = $clazz->isFinal();
35✔
147

148
        $this->is_abstract = $clazz->isAbstract();
35✔
149

150
        $this->is_anonymous = $clazz->isAnonymous();
35✔
151

152
        $this->is_cloneable = $clazz->isCloneable();
35✔
153

154
        $this->is_instantiable = $clazz->isInstantiable();
35✔
155

156
        $this->is_iterable = $clazz->isIterable();
35✔
157

158
        $parent = $clazz->getParentClass();
35✔
159
        if ($parent) {
35✔
160
            $this->parentClass = $parent->getName();
26✔
161

162
            $classExists = false;
26✔
163
            try {
164
                if (
165
                    !$this->parserContainer->getClass($this->parentClass)
26✔
166
                    &&
167
                    \class_exists($this->parentClass, true)
26✔
168
                ) {
169
                    $classExists = true;
26✔
170
                }
171
            } catch (\Exception $e) {
×
172
                // nothing
173
            }
174
            if ($classExists) {
26✔
175
                $reflectionClass = Utils::createClassReflectionInstance($this->parentClass);
26✔
176
                $class = (new self($this->parserContainer))->readObjectFromReflection($reflectionClass);
26✔
177
                $this->parserContainer->addClass($class);
26✔
178
            }
179
        }
180

181
        foreach ($clazz->getProperties() as $property) {
35✔
182
            $propertyPhp = (new PHPProperty($this->parserContainer))->readObjectFromReflection($property);
32✔
183
            $this->properties[$propertyPhp->name] = $propertyPhp;
32✔
184
        }
185

186
        foreach ($clazz->getInterfaceNames() as $interfaceName) {
35✔
187
            /** @noinspection PhpSillyAssignmentInspection - hack for phpstan */
188
            /** @var class-string $interfaceName */
189
            $interfaceName = $interfaceName;
17✔
190
            $this->interfaces[$interfaceName] = $interfaceName;
17✔
191
        }
192

193
        foreach ($clazz->getMethods() as $method) {
35✔
194
            $methodNameTmp = $method->getName();
35✔
195

196
            $this->methods[$methodNameTmp] = (new PHPMethod($this->parserContainer))->readObjectFromReflection($method);
35✔
197

198
            if (!$this->methods[$methodNameTmp]->file) {
35✔
199
                $this->methods[$methodNameTmp]->file = $this->file;
×
200
            }
201
        }
202

203
        foreach ($clazz->getReflectionConstants() as $constant) {
35✔
204
            $constantNameTmp = $constant->getName();
23✔
205

206
            $this->constants[$constantNameTmp] = (new PHPConst($this->parserContainer))->readObjectFromReflection($constant);
23✔
207

208
            if (!$this->constants[$constantNameTmp]->file) {
23✔
209
                $this->constants[$constantNameTmp]->file = $this->file;
×
210
            }
211
        }
212

213
        return $this;
35✔
214
    }
215

216
    /**
217
     * @param string[] $access
218
     * @param bool     $skipMethodsWithLeadingUnderscore
219
     *
220
     * @return array
221
     *
222
     * @psalm-return array<string, array{type: null|string, typeFromPhpDocMaybeWithComment: null|string, typeFromPhpDoc: null|string, typeFromPhpDocSimple: null|string, typeFromPhpDocExtended: null|string, typeFromDefaultValue: null|string}>
223
     */
224
    public function getPropertiesInfo(
225
        array $access = ['public', 'protected', 'private'],
226
        bool $skipMethodsWithLeadingUnderscore = false
227
    ): array {
228
        // init
229
        $allInfo = [];
3✔
230

231
        foreach ($this->properties as $property) {
3✔
232
            if (!\in_array($property->access, $access, true)) {
3✔
233
                continue;
×
234
            }
235

236
            if ($skipMethodsWithLeadingUnderscore && \strpos($property->name, '_') === 0) {
3✔
237
                continue;
×
238
            }
239

240
            $types = [];
3✔
241
            $types['type'] = $property->type;
3✔
242
            $types['typeFromPhpDocMaybeWithComment'] = $property->typeFromPhpDocMaybeWithComment;
3✔
243
            $types['typeFromPhpDoc'] = $property->typeFromPhpDoc;
3✔
244
            $types['typeFromPhpDocSimple'] = $property->typeFromPhpDocSimple;
3✔
245
            $types['typeFromPhpDocExtended'] = $property->typeFromPhpDocExtended;
3✔
246
            $types['typeFromDefaultValue'] = $property->typeFromDefaultValue;
3✔
247

248
            $allInfo[$property->name] = $types;
3✔
249
        }
250

251
        return $allInfo;
3✔
252
    }
253

254
    /**
255
     * @param string[] $access
256
     * @param bool     $skipDeprecatedMethods
257
     * @param bool     $skipMethodsWithLeadingUnderscore
258
     *
259
     * @return array<mixed>
260
     *
261
     * @psalm-return array<string, array{
262
     *     fullDescription: string,
263
     *     line: null|int,
264
     *     file: null|string,
265
     *     error: string,
266
     *     is_deprecated: bool,
267
     *     is_static: null|bool,
268
     *     is_meta: bool,
269
     *     is_internal: bool,
270
     *     is_removed: bool,
271
     *     paramsTypes: array<string,
272
     *         array{
273
     *              type?: null|string,
274
     *              typeFromPhpDoc?: null|string,
275
     *              typeFromPhpDocExtended?: null|string,
276
     *              typeFromPhpDocSimple?: null|string,
277
     *              typeFromPhpDocMaybeWithComment?: null|string,
278
     *              typeFromDefaultValue?: null|string
279
     *         }
280
     *     >,
281
     *     returnTypes: array{
282
     *         type: null|string,
283
     *         typeFromPhpDoc: null|string,
284
     *         typeFromPhpDocExtended: null|string,
285
     *         typeFromPhpDocSimple: null|string,
286
     *         typeFromPhpDocMaybeWithComment: null|string
287
     *     },
288
     *     paramsPhpDocRaw: array<string, null|string>,
289
     *     returnPhpDocRaw: null|string
290
     * }>
291
     */
292
    public function getMethodsInfo(
293
        array $access = ['public', 'protected', 'private'],
294
        bool $skipDeprecatedMethods = false,
295
        bool $skipMethodsWithLeadingUnderscore = false
296
    ): array {
297
        // init
298
        $allInfo = [];
6✔
299

300
        foreach ($this->methods as $method) {
6✔
301
            if (!\in_array($method->access, $access, true)) {
6✔
302
                continue;
×
303
            }
304

305
            if ($skipDeprecatedMethods && $method->hasDeprecatedTag) {
6✔
306
                continue;
×
307
            }
308

309
            if ($skipMethodsWithLeadingUnderscore && \strpos($method->name, '_') === 0) {
6✔
310
                continue;
×
311
            }
312

313
            $paramsTypes = [];
6✔
314
            foreach ($method->parameters as $tagParam) {
6✔
315
                $paramsTypes[$tagParam->name]['type'] = $tagParam->type;
6✔
316
                $paramsTypes[$tagParam->name]['typeFromPhpDocMaybeWithComment'] = $tagParam->typeFromPhpDocMaybeWithComment;
6✔
317
                $paramsTypes[$tagParam->name]['typeFromPhpDoc'] = $tagParam->typeFromPhpDoc;
6✔
318
                $paramsTypes[$tagParam->name]['typeFromPhpDocSimple'] = $tagParam->typeFromPhpDocSimple;
6✔
319
                $paramsTypes[$tagParam->name]['typeFromPhpDocExtended'] = $tagParam->typeFromPhpDocExtended;
6✔
320
                $paramsTypes[$tagParam->name]['typeFromDefaultValue'] = $tagParam->typeFromDefaultValue;
6✔
321
            }
322

323
            $returnTypes = [];
6✔
324
            $returnTypes['type'] = $method->returnType;
6✔
325
            $returnTypes['typeFromPhpDocMaybeWithComment'] = $method->returnTypeFromPhpDocMaybeWithComment;
6✔
326
            $returnTypes['typeFromPhpDoc'] = $method->returnTypeFromPhpDoc;
6✔
327
            $returnTypes['typeFromPhpDocSimple'] = $method->returnTypeFromPhpDocSimple;
6✔
328
            $returnTypes['typeFromPhpDocExtended'] = $method->returnTypeFromPhpDocExtended;
6✔
329

330
            $paramsPhpDocRaw = [];
6✔
331
            foreach ($method->parameters as $tagParam) {
6✔
332
                $paramsPhpDocRaw[$tagParam->name] = $tagParam->phpDocRaw;
6✔
333
            }
334

335
            $infoTmp = [];
6✔
336
            $infoTmp['fullDescription'] = \trim($method->summary . "\n\n" . $method->description);
6✔
337
            $infoTmp['paramsTypes'] = $paramsTypes;
6✔
338
            $infoTmp['returnTypes'] = $returnTypes;
6✔
339
            $infoTmp['paramsPhpDocRaw'] = $paramsPhpDocRaw;
6✔
340
            $infoTmp['returnPhpDocRaw'] = $method->returnPhpDocRaw;
6✔
341
            $infoTmp['line'] = $method->line ?? $this->line;
6✔
342
            $infoTmp['file'] = $method->file ?? $this->file;
6✔
343
            $infoTmp['error'] = \implode("\n", $method->parseError);
6✔
344
            foreach ($method->parameters as $parameter) {
6✔
345
                $infoTmp['error'] .= ($infoTmp['error'] ? "\n" : '') . \implode("\n", $parameter->parseError);
6✔
346
            }
347
            $infoTmp['is_deprecated'] = $method->hasDeprecatedTag;
6✔
348
            $infoTmp['is_static'] = $method->is_static;
6✔
349
            $infoTmp['is_meta'] = $method->hasMetaTag;
6✔
350
            $infoTmp['is_internal'] = $method->hasInternalTag;
6✔
351
            $infoTmp['is_removed'] = $method->hasRemovedTag;
6✔
352

353
            $allInfo[$method->name] = $infoTmp;
6✔
354
        }
355

356
        \asort($allInfo);
6✔
357

358
        return $allInfo;
6✔
359
    }
360

361
    /**
362
     * @param Doc|string $doc
363
     *
364
     * @return void
365
     */
366
    private function readPhpDocProperties($doc): void
367
    {
368
        if ($doc instanceof Doc) {
38✔
369
            $docComment = $doc->getText();
×
370
        } else {
371
            $docComment = $doc;
38✔
372
        }
373
        if ($docComment === '') {
38✔
374
            return;
×
375
        }
376

377
        try {
378
            $phpDoc = Utils::createDocBlockInstance()->create($docComment);
38✔
379

380
            $parsedPropertyTags = $phpDoc->getTagsByName('property')
38✔
381
                               + $phpDoc->getTagsByName('property-read')
38✔
382
                               + $phpDoc->getTagsByName('property-write');
38✔
383

384
            if (!empty($parsedPropertyTags)) {
38✔
385
                foreach ($parsedPropertyTags as $parsedPropertyTag) {
38✔
386
                    if (
387
                        $parsedPropertyTag instanceof \phpDocumentor\Reflection\DocBlock\Tags\PropertyRead
18✔
388
                        ||
389
                        $parsedPropertyTag instanceof \phpDocumentor\Reflection\DocBlock\Tags\PropertyWrite
18✔
390
                        ||
391
                        $parsedPropertyTag instanceof \phpDocumentor\Reflection\DocBlock\Tags\Property
18✔
392
                    ) {
393
                        $propertyPhp = new PHPProperty($this->parserContainer);
18✔
394

395
                        $nameTmp = $parsedPropertyTag->getVariableName();
18✔
396
                        if (!$nameTmp) {
18✔
397
                            continue;
×
398
                        }
399

400
                        $propertyPhp->name = $nameTmp;
18✔
401

402
                        $propertyPhp->access = 'public';
18✔
403

404
                        $type = $parsedPropertyTag->getType();
18✔
405

406
                        $propertyPhp->typeFromPhpDoc = Utils::normalizePhpType($type . '');
18✔
407

408
                        $typeFromPhpDocMaybeWithCommentTmp = \trim((string) $parsedPropertyTag);
18✔
409
                        if (
410
                            $typeFromPhpDocMaybeWithCommentTmp
18✔
411
                            &&
412
                            \strpos($typeFromPhpDocMaybeWithCommentTmp, '$') !== 0
18✔
413
                        ) {
414
                            $propertyPhp->typeFromPhpDocMaybeWithComment = $typeFromPhpDocMaybeWithCommentTmp;
18✔
415
                        }
416

417
                        $typeTmp = Utils::parseDocTypeObject($type);
18✔
418
                        if ($typeTmp !== '') {
18✔
419
                            $propertyPhp->typeFromPhpDocSimple = $typeTmp;
18✔
420
                        }
421

422
                        if ($propertyPhp->typeFromPhpDoc) {
18✔
423
                            $propertyPhp->typeFromPhpDocExtended = Utils::modernPhpdoc($propertyPhp->typeFromPhpDoc);
18✔
424
                        }
425

426
                        $this->properties[$propertyPhp->name] = $propertyPhp;
18✔
427
                    }
428
                }
429
            }
430
        } catch (\Exception $e) {
×
431
            $tmpErrorMessage = ($this->name ?: '?') . ':' . ($this->line ?? '?') . ' | ' . \print_r($e->getMessage(), true);
×
432
            $this->parseError[\md5($tmpErrorMessage)] = $tmpErrorMessage;
×
433
        }
434
    }
435
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc