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

voku / Simple-PHP-Code-Parser / 5709606856

pending completion
5709606856

push

github

voku
[+]: ci: run test with PHP 8.2

1305 of 1592 relevant lines covered (81.97%)

7.61 hits per line

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

91.26
/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
     * @phpstan-var class-string
16
     */
17
    public string $name;
18

19
    /**
20
     * @phpstan-var class-string|null
21
     */
22
    public ?string $parentClass = null;
23

24
    /**
25
     * @var string[]
26
     *
27
     * @phpstan-var class-string[]
28
     */
29
    public array $interfaces = [];
30

31
    /**
32
     * @param Class_ $node
33
     * @param null   $dummy
34
     *
35
     * @return $this
36
     */
37
    public function readObjectFromPhpNode($node, $dummy = null): self
38
    {
39
        $this->prepareNode($node);
16✔
40

41
        $this->name = static::getFQN($node);
16✔
42

43
        $this->is_final = $node->isFinal();
16✔
44

45
        $this->is_abstract = $node->isAbstract();
16✔
46

47
        if (method_exists($node, 'isReadOnly')) {
16✔
48
            $this->is_readonly = $node->isReadOnly();
16✔
49
        }
50

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

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

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

68
        if (!empty($node->extends)) {
16✔
69
            $classExtended = implode('\\', $node->extends->getParts());
9✔
70
            /** @noinspection PhpSillyAssignmentInspection - hack for phpstan */
71
            /** @var class-string $classExtended */
72
            $classExtended = $classExtended;
9✔
73
            $this->parentClass = $classExtended;
9✔
74
        }
75

76
        $docComment = $node->getDocComment();
16✔
77
        if ($docComment) {
16✔
78
            $this->readPhpDocProperties($docComment->getText());
15✔
79
        }
80

81
        foreach ($node->getProperties() as $property) {
16✔
82
            $propertyNameTmp = $this->getConstantFQN($property, $property->props[0]->name->name);
10✔
83

84
            if (isset($this->properties[$propertyNameTmp])) {
10✔
85
                $this->properties[$propertyNameTmp] = $this->properties[$propertyNameTmp]->readObjectFromPhpNode($property, $this->name);
10✔
86
            } else {
87
                $this->properties[$propertyNameTmp] = (new PHPProperty($this->parserContainer))->readObjectFromPhpNode($property, $this->name);
×
88
            }
89

90
            if ($this->is_readonly) {
10✔
91
                $this->properties[$propertyNameTmp]->is_readonly = true;
2✔
92
            }
93
        }
94

95
        foreach ($node->getMethods() as $method) {
16✔
96
            $methodNameTmp = $method->name->name;
14✔
97

98
            if (isset($this->methods[$methodNameTmp])) {
14✔
99
                $this->methods[$methodNameTmp] = $this->methods[$methodNameTmp]->readObjectFromPhpNode($method, $this->name);
13✔
100
            } else {
101
                $this->methods[$methodNameTmp] = (new PHPMethod($this->parserContainer))->readObjectFromPhpNode($method, $this->name);
1✔
102
            }
103

104
            if (!$this->methods[$methodNameTmp]->file) {
14✔
105
                $this->methods[$methodNameTmp]->file = $this->file;
1✔
106
            }
107
        }
108

109
        if (!empty($node->implements)) {
16✔
110
            foreach ($node->implements as $interfaceObject) {
4✔
111
                $interfaceFQN = implode('\\', $interfaceObject->getParts());
4✔
112
                /** @noinspection PhpSillyAssignmentInspection - hack for phpstan */
113
                /** @var class-string $interfaceFQN */
114
                $interfaceFQN = $interfaceFQN;
4✔
115
                $this->interfaces[$interfaceFQN] = $interfaceFQN;
4✔
116
            }
117
        }
118

119
        return $this;
16✔
120
    }
121

122
    /**
123
     * @param ReflectionClass $clazz
124
     *
125
     * @return $this
126
     */
127
    public function readObjectFromReflection($clazz): self
128
    {
129
        $this->name = $clazz->getName();
14✔
130

131
        if (!$this->line) {
14✔
132
            $lineTmp = $clazz->getStartLine();
9✔
133
            if ($lineTmp !== false) {
9✔
134
                $this->line = $lineTmp;
5✔
135
            }
136
        }
137

138
        $file = $clazz->getFileName();
14✔
139
        if ($file) {
14✔
140
            $this->file = $file;
14✔
141
        }
142

143
        $this->is_final = $clazz->isFinal();
14✔
144

145
        $this->is_abstract = $clazz->isAbstract();
14✔
146

147
        if (method_exists($clazz, 'isReadOnly')) {
14✔
148
            $this->is_readonly = $clazz->isReadOnly();
14✔
149
        }
150

151
        $this->is_anonymous = $clazz->isAnonymous();
14✔
152

153
        $this->is_cloneable = $clazz->isCloneable();
14✔
154

155
        $this->is_instantiable = $clazz->isInstantiable();
14✔
156

157
        $this->is_iterable = $clazz->isIterable();
14✔
158

159
        $parent = $clazz->getParentClass();
14✔
160
        if ($parent) {
14✔
161
            $this->parentClass = $parent->getName();
9✔
162

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

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

186
            if ($this->is_readonly) {
13✔
187
                $this->properties[$propertyPhp->name]->is_readonly = true;
2✔
188
            }
189
        }
190

191
        foreach ($clazz->getInterfaceNames() as $interfaceName) {
14✔
192
            /** @noinspection PhpSillyAssignmentInspection - hack for phpstan */
193
            /** @var class-string $interfaceName */
194
            $interfaceName = $interfaceName;
7✔
195
            $this->interfaces[$interfaceName] = $interfaceName;
7✔
196
        }
197

198
        foreach ($clazz->getMethods() as $method) {
14✔
199
            $methodNameTmp = $method->getName();
14✔
200

201
            $this->methods[$methodNameTmp] = (new PHPMethod($this->parserContainer))->readObjectFromReflection($method);
14✔
202

203
            if (!$this->methods[$methodNameTmp]->file) {
14✔
204
                $this->methods[$methodNameTmp]->file = $this->file;
×
205
            }
206
        }
207

208
        foreach ($clazz->getReflectionConstants() as $constant) {
14✔
209
            $constantNameTmp = $constant->getName();
8✔
210

211
            $this->constants[$constantNameTmp] = (new PHPConst($this->parserContainer))->readObjectFromReflection($constant);
8✔
212

213
            if (!$this->constants[$constantNameTmp]->file) {
8✔
214
                $this->constants[$constantNameTmp]->file = $this->file;
×
215
            }
216
        }
217

218
        return $this;
14✔
219
    }
220

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

236
        foreach ($this->properties as $property) {
1✔
237
            if (!\in_array($property->access, $access, true)) {
1✔
238
                continue;
×
239
            }
240

241
            if ($skipMethodsWithLeadingUnderscore && \strpos($property->name, '_') === 0) {
1✔
242
                continue;
×
243
            }
244

245
            $types = [];
1✔
246
            $types['type'] = $property->type;
1✔
247
            $types['typeFromPhpDocMaybeWithComment'] = $property->typeFromPhpDocMaybeWithComment;
1✔
248
            $types['typeFromPhpDoc'] = $property->typeFromPhpDoc;
1✔
249
            $types['typeFromPhpDocSimple'] = $property->typeFromPhpDocSimple;
1✔
250
            $types['typeFromPhpDocExtended'] = $property->typeFromPhpDocExtended;
1✔
251
            $types['typeFromDefaultValue'] = $property->typeFromDefaultValue;
1✔
252

253
            $allInfo[$property->name] = $types;
1✔
254
        }
255

256
        return $allInfo;
1✔
257
    }
258

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

305
        foreach ($this->methods as $method) {
2✔
306
            if (!\in_array($method->access, $access, true)) {
2✔
307
                continue;
×
308
            }
309

310
            if ($skipDeprecatedMethods && $method->hasDeprecatedTag) {
2✔
311
                continue;
×
312
            }
313

314
            if ($skipMethodsWithLeadingUnderscore && \strpos($method->name, '_') === 0) {
2✔
315
                continue;
×
316
            }
317

318
            $paramsTypes = [];
2✔
319
            foreach ($method->parameters as $tagParam) {
2✔
320
                $paramsTypes[$tagParam->name]['type'] = $tagParam->type;
2✔
321
                $paramsTypes[$tagParam->name]['typeFromPhpDocMaybeWithComment'] = $tagParam->typeFromPhpDocMaybeWithComment;
2✔
322
                $paramsTypes[$tagParam->name]['typeFromPhpDoc'] = $tagParam->typeFromPhpDoc;
2✔
323
                $paramsTypes[$tagParam->name]['typeFromPhpDocSimple'] = $tagParam->typeFromPhpDocSimple;
2✔
324
                $paramsTypes[$tagParam->name]['typeFromPhpDocExtended'] = $tagParam->typeFromPhpDocExtended;
2✔
325
                $paramsTypes[$tagParam->name]['typeFromDefaultValue'] = $tagParam->typeFromDefaultValue;
2✔
326
            }
327

328
            $returnTypes = [];
2✔
329
            $returnTypes['type'] = $method->returnType;
2✔
330
            $returnTypes['typeFromPhpDocMaybeWithComment'] = $method->returnTypeFromPhpDocMaybeWithComment;
2✔
331
            $returnTypes['typeFromPhpDoc'] = $method->returnTypeFromPhpDoc;
2✔
332
            $returnTypes['typeFromPhpDocSimple'] = $method->returnTypeFromPhpDocSimple;
2✔
333
            $returnTypes['typeFromPhpDocExtended'] = $method->returnTypeFromPhpDocExtended;
2✔
334

335
            $paramsPhpDocRaw = [];
2✔
336
            foreach ($method->parameters as $tagParam) {
2✔
337
                $paramsPhpDocRaw[$tagParam->name] = $tagParam->phpDocRaw;
2✔
338
            }
339

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

358
            $allInfo[$method->name] = $infoTmp;
2✔
359
        }
360

361
        \asort($allInfo);
2✔
362

363
        return $allInfo;
2✔
364
    }
365

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

380
        try {
381
            $phpDoc = Utils::createDocBlockInstance()->create($docComment);
15✔
382

383
            $parsedPropertyTags = $phpDoc->getTagsByName('property')
15✔
384
                               + $phpDoc->getTagsByName('property-read')
15✔
385
                               + $phpDoc->getTagsByName('property-write');
15✔
386

387
            if (!empty($parsedPropertyTags)) {
15✔
388
                foreach ($parsedPropertyTags as $parsedPropertyTag) {
15✔
389
                    if (
390
                        $parsedPropertyTag instanceof \phpDocumentor\Reflection\DocBlock\Tags\PropertyRead
6✔
391
                        ||
392
                        $parsedPropertyTag instanceof \phpDocumentor\Reflection\DocBlock\Tags\PropertyWrite
6✔
393
                        ||
394
                        $parsedPropertyTag instanceof \phpDocumentor\Reflection\DocBlock\Tags\Property
6✔
395
                    ) {
396
                        $propertyPhp = new PHPProperty($this->parserContainer);
6✔
397

398
                        $nameTmp = $parsedPropertyTag->getVariableName();
6✔
399
                        if (!$nameTmp) {
6✔
400
                            continue;
×
401
                        }
402

403
                        $propertyPhp->name = $nameTmp;
6✔
404

405
                        $propertyPhp->access = 'public';
6✔
406

407
                        $type = $parsedPropertyTag->getType();
6✔
408

409
                        $propertyPhp->typeFromPhpDoc = Utils::normalizePhpType($type . '');
6✔
410

411
                        $typeFromPhpDocMaybeWithCommentTmp = \trim((string) $parsedPropertyTag);
6✔
412
                        if (
413
                            $typeFromPhpDocMaybeWithCommentTmp
6✔
414
                            &&
415
                            \strpos($typeFromPhpDocMaybeWithCommentTmp, '$') !== 0
6✔
416
                        ) {
417
                            $propertyPhp->typeFromPhpDocMaybeWithComment = $typeFromPhpDocMaybeWithCommentTmp;
6✔
418
                        }
419

420
                        $typeTmp = Utils::parseDocTypeObject($type);
6✔
421
                        if ($typeTmp !== '') {
6✔
422
                            $propertyPhp->typeFromPhpDocSimple = $typeTmp;
6✔
423
                        }
424

425
                        if ($propertyPhp->typeFromPhpDoc) {
6✔
426
                            $propertyPhp->typeFromPhpDocExtended = Utils::modernPhpdoc($propertyPhp->typeFromPhpDoc);
6✔
427
                        }
428

429
                        $this->properties[$propertyPhp->name] = $propertyPhp;
6✔
430
                    }
431
                }
432
            }
433
        } catch (\Exception $e) {
×
434
            $tmpErrorMessage = ($this->name ?: '?') . ':' . ($this->line ?? '?') . ' | ' . \print_r($e->getMessage(), true);
×
435
            $this->parseError[\md5($tmpErrorMessage)] = $tmpErrorMessage;
×
436
        }
437
    }
438
}
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