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

voku / Simple-PHP-Code-Parser / 12549509370

30 Dec 2024 04:59PM UTC coverage: 79.926% (-2.3%) from 82.259%
12549509370

Pull #47

github

web-flow
Merge c5d39b6ad into 898f2c327
Pull Request #47: Update shivammathur/setup-php action to v2.32.0

1302 of 1629 relevant lines covered (79.93%)

6.18 hits per line

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

89.62
/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);
12✔
40

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

119
        return $this;
12✔
120
    }
121

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

218
        return $this;
10✔
219
    }
220

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

243
        foreach ($this->properties as $property) {
1✔
244
            if (!\in_array($property->access, $access, true)) {
1✔
245
                continue;
×
246
            }
247

248
            if ($skipMethodsWithLeadingUnderscore && \strpos($property->name, '_') === 0) {
1✔
249
                continue;
×
250
            }
251

252
            $types = [];
1✔
253
            $types['type'] = $property->type;
1✔
254
            $types['typeFromPhpDocMaybeWithComment'] = $property->typeFromPhpDocMaybeWithComment;
1✔
255
            $types['typeFromPhpDoc'] = $property->typeFromPhpDoc;
1✔
256
            $types['typeFromPhpDocSimple'] = $property->typeFromPhpDocSimple;
1✔
257
            $types['typeFromPhpDocExtended'] = $property->typeFromPhpDocExtended;
1✔
258
            $types['typeFromDefaultValue'] = $property->typeFromDefaultValue;
1✔
259

260
            $allInfo[$property->name] = $types;
1✔
261
        }
262

263
        return $allInfo;
1✔
264
    }
265

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

312
        foreach ($this->methods as $method) {
2✔
313
            if (!\in_array($method->access, $access, true)) {
2✔
314
                continue;
×
315
            }
316

317
            if ($skipDeprecatedMethods && $method->hasDeprecatedTag) {
2✔
318
                continue;
×
319
            }
320

321
            if ($skipMethodsWithLeadingUnderscore && \strpos($method->name, '_') === 0) {
2✔
322
                continue;
×
323
            }
324

325
            $paramsTypes = [];
2✔
326
            foreach ($method->parameters as $tagParam) {
2✔
327
                $paramsTypes[$tagParam->name]['type'] = $tagParam->type;
2✔
328
                $paramsTypes[$tagParam->name]['typeFromPhpDocMaybeWithComment'] = $tagParam->typeFromPhpDocMaybeWithComment;
2✔
329
                $paramsTypes[$tagParam->name]['typeFromPhpDoc'] = $tagParam->typeFromPhpDoc;
2✔
330
                $paramsTypes[$tagParam->name]['typeFromPhpDocSimple'] = $tagParam->typeFromPhpDocSimple;
2✔
331
                $paramsTypes[$tagParam->name]['typeFromPhpDocExtended'] = $tagParam->typeFromPhpDocExtended;
2✔
332
                $paramsTypes[$tagParam->name]['typeFromDefaultValue'] = $tagParam->typeFromDefaultValue;
2✔
333
            }
334

335
            $returnTypes = [];
2✔
336
            $returnTypes['type'] = $method->returnType;
2✔
337
            $returnTypes['typeFromPhpDocMaybeWithComment'] = $method->returnTypeFromPhpDocMaybeWithComment;
2✔
338
            $returnTypes['typeFromPhpDoc'] = $method->returnTypeFromPhpDoc;
2✔
339
            $returnTypes['typeFromPhpDocSimple'] = $method->returnTypeFromPhpDocSimple;
2✔
340
            $returnTypes['typeFromPhpDocExtended'] = $method->returnTypeFromPhpDocExtended;
2✔
341

342
            $paramsPhpDocRaw = [];
2✔
343
            foreach ($method->parameters as $tagParam) {
2✔
344
                $paramsPhpDocRaw[$tagParam->name] = $tagParam->phpDocRaw;
2✔
345
            }
346

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

365
            $allInfo[$method->name] = $infoTmp;
2✔
366
        }
367

368
        \asort($allInfo);
2✔
369

370
        return $allInfo;
2✔
371
    }
372

373
    /**
374
     * @param Doc|string $doc
375
     */
376
    private function readPhpDocProperties($doc): void
377
    {
378
        if ($doc instanceof Doc) {
11✔
379
            $docComment = $doc->getText();
×
380
        } else {
381
            $docComment = $doc;
11✔
382
        }
383
        if ($docComment === '') {
11✔
384
            return;
×
385
        }
386

387
        try {
388
            $phpDoc = Utils::createDocBlockInstance()->create($docComment);
11✔
389

390
            $parsedPropertyTags = $phpDoc->getTagsByName('property')
11✔
391
                               + $phpDoc->getTagsByName('property-read')
11✔
392
                               + $phpDoc->getTagsByName('property-write');
11✔
393

394
            if (!empty($parsedPropertyTags)) {
11✔
395
                foreach ($parsedPropertyTags as $parsedPropertyTag) {
11✔
396
                    if (
397
                        $parsedPropertyTag instanceof \phpDocumentor\Reflection\DocBlock\Tags\PropertyRead
6✔
398
                        ||
399
                        $parsedPropertyTag instanceof \phpDocumentor\Reflection\DocBlock\Tags\PropertyWrite
6✔
400
                        ||
401
                        $parsedPropertyTag instanceof \phpDocumentor\Reflection\DocBlock\Tags\Property
6✔
402
                    ) {
403
                        $propertyPhp = new PHPProperty($this->parserContainer);
6✔
404

405
                        $nameTmp = $parsedPropertyTag->getVariableName();
6✔
406
                        if (!$nameTmp) {
6✔
407
                            continue;
×
408
                        }
409

410
                        $propertyPhp->name = $nameTmp;
6✔
411

412
                        $propertyPhp->access = 'public';
6✔
413

414
                        $type = $parsedPropertyTag->getType();
6✔
415

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

418
                        $typeFromPhpDocMaybeWithCommentTmp = \trim((string) $parsedPropertyTag);
6✔
419
                        if (
420
                            $typeFromPhpDocMaybeWithCommentTmp
6✔
421
                            &&
422
                            \strpos($typeFromPhpDocMaybeWithCommentTmp, '$') !== 0
6✔
423
                        ) {
424
                            $propertyPhp->typeFromPhpDocMaybeWithComment = $typeFromPhpDocMaybeWithCommentTmp;
6✔
425
                        }
426

427
                        $typeTmp = Utils::parseDocTypeObject($type);
6✔
428
                        if ($typeTmp !== '') {
6✔
429
                            $propertyPhp->typeFromPhpDocSimple = $typeTmp;
6✔
430
                        }
431

432
                        if ($propertyPhp->typeFromPhpDoc) {
6✔
433
                            $propertyPhp->typeFromPhpDocExtended = Utils::modernPhpdoc($propertyPhp->typeFromPhpDoc);
6✔
434
                        }
435

436
                        $this->properties[$propertyPhp->name] = $propertyPhp;
6✔
437
                    }
438
                }
439
            }
440
        } catch (\Exception $e) {
×
441
            $tmpErrorMessage = ($this->name ?: '?') . ':' . ($this->line ?? '?') . ' | ' . \print_r($e->getMessage(), true);
×
442
            $this->parseError[\md5($tmpErrorMessage)] = $tmpErrorMessage;
×
443
        }
444
    }
445
}
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