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

voku / Simple-PHP-Code-Parser / 24277438854

11 Apr 2026 07:15AM UTC coverage: 82.757%. Remained the same
24277438854

push

github

web-flow
Merge pull request #85 from voku/renovate/migrate-config

chore(config): migrate Renovate config

1531 of 1850 relevant lines covered (82.76%)

90.77 hits per line

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

70.55
/src/voku/SimplePhpParser/Model/PHPTrait.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\Trait_;
9
use ReflectionClass;
10
use voku\SimplePhpParser\Parsers\Helper\DocFactoryProvider;
11
use voku\SimplePhpParser\Parsers\Helper\Utils;
12

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

20
    /**
21
     * @param Trait_ $node
22
     * @param null   $dummy
23
     *
24
     * @return $this
25
     */
26
    public function readObjectFromPhpNode($node, $dummy = null): self
27
    {
28
        $this->prepareNode($node);
30✔
29

30
        $this->name = static::getFQN($node);
30✔
31

32
        // Extract PHP 8.0+ attributes
33
        if (!empty($node->attrGroups)) {
30✔
34
            $this->attributes = Utils::extractAttributesFromAstNode($node->attrGroups);
×
35
        }
36

37
        // PHP < 8.2 raises an uncatchable E_COMPILE_ERROR for traits with constants.
38
        // Skip autoloading in that case; constants are still read from the AST below.
39
        $canAutoload = \PHP_VERSION_ID >= 80200 || empty($node->getConstants());
30✔
40
        $traitExists = false;
30✔
41
        if ($canAutoload) {
30✔
42
            try {
43
                if (\trait_exists($this->name, true)) {
30✔
44
                    $traitExists = true;
30✔
45
                }
46
            } catch (\Throwable $e) {
×
47
                // nothing
48
            }
49
        }
50
        if ($traitExists) {
30✔
51
            $reflectionClass = Utils::createClassReflectionInstance($this->name);
30✔
52
            $this->readObjectFromReflection($reflectionClass);
30✔
53
        }
54

55
        $this->collectTags($node);
30✔
56

57
        $docComment = $node->getDocComment();
30✔
58
        if ($docComment) {
30✔
59
            $this->readPhpDocProperties($docComment);
30✔
60
        }
61

62
        foreach ($node->getProperties() as $property) {
30✔
63
            $propertyNameTmp = $this->getConstantFQN($property, $property->props[0]->name->name);
24✔
64

65
            if (isset($this->properties[$propertyNameTmp])) {
24✔
66
                $this->properties[$propertyNameTmp] = $this->properties[$propertyNameTmp]->readObjectFromPhpNode($property, $this->name);
24✔
67
            } else {
68
                $this->properties[$propertyNameTmp] = (new PHPProperty($this->parserContainer))->readObjectFromPhpNode($property, $this->name);
×
69
            }
70
        }
71

72
        foreach ($node->getMethods() as $method) {
30✔
73
            $methodNameTmp = $method->name->name;
30✔
74

75
            if (isset($this->methods[$methodNameTmp])) {
30✔
76
                $this->methods[$methodNameTmp] = $this->methods[$methodNameTmp]->readObjectFromPhpNode($method, $this->name);
30✔
77
            } else {
78
                $this->methods[$methodNameTmp] = (new PHPMethod($this->parserContainer))->readObjectFromPhpNode($method, $this->name);
4✔
79
            }
80

81
            if (!$this->methods[$methodNameTmp]->file) {
30✔
82
                $this->methods[$methodNameTmp]->file = $this->file;
4✔
83
            }
84
        }
85

86
        // Constants in traits (PHP 8.2+)
87
        foreach ($node->getConstants() as $constNode) {
30✔
88
            foreach ($constNode->consts as $const) {
22✔
89
                $constNameTmp = $const->name->name;
22✔
90

91
                if (isset($this->constants[$constNameTmp])) {
22✔
92
                    $this->constants[$constNameTmp] = $this->constants[$constNameTmp]->readObjectFromPhpNode($const);
18✔
93
                } else {
94
                    $this->constants[$constNameTmp] = (new PHPConst($this->parserContainer))->readObjectFromPhpNode($const);
4✔
95
                }
96

97
                if (!$this->constants[$constNameTmp]->file) {
22✔
98
                    $this->constants[$constNameTmp]->file = $this->file;
4✔
99
                }
100
            }
101
        }
102

103
        return $this;
30✔
104
    }
105

106
    /**
107
     * @param ReflectionClass<object> $clazz
108
     *
109
     * @return $this
110
     */
111
    public function readObjectFromReflection($clazz): self
112
    {
113
        if (!$clazz->isTrait()) {
30✔
114
            return $this;
×
115
        }
116

117
        $this->name = $clazz->getName();
30✔
118

119
        if (!$this->line) {
30✔
120
            $lineTmp = $clazz->getStartLine();
×
121
            if ($lineTmp !== false) {
×
122
                $this->line = $lineTmp;
×
123
            }
124
        }
125

126
        $file = $clazz->getFileName();
30✔
127
        if ($file) {
30✔
128
            $this->file = $file;
30✔
129
        }
130

131
        $this->is_final = $clazz->isFinal();
30✔
132

133
        $this->is_abstract = $clazz->isAbstract();
30✔
134

135
        $this->is_anonymous = $clazz->isAnonymous();
30✔
136

137
        $this->is_cloneable = $clazz->isCloneable();
30✔
138

139
        $this->is_instantiable = $clazz->isInstantiable();
30✔
140

141
        $this->is_iterable = $clazz->isIterable();
30✔
142

143
        // Extract PHP 8.0+ attributes
144
        $this->attributes = Utils::extractAttributesFromReflection($clazz);
30✔
145

146
        foreach ($clazz->getProperties() as $property) {
30✔
147
            $propertyPhp = (new PHPProperty($this->parserContainer))->readObjectFromReflection($property);
24✔
148
            $this->properties[$propertyPhp->name] = $propertyPhp;
24✔
149
        }
150

151
        foreach ($clazz->getMethods() as $method) {
30✔
152
            $methodNameTmp = $method->getName();
30✔
153

154
            $this->methods[$methodNameTmp] = (new PHPMethod($this->parserContainer))->readObjectFromReflection($method);
30✔
155

156
            if (!$this->methods[$methodNameTmp]->file) {
30✔
157
                $this->methods[$methodNameTmp]->file = $this->file;
×
158
            }
159
        }
160

161
        foreach ($clazz->getReflectionConstants() as $constant) {
30✔
162
            $constantNameTmp = $constant->getName();
18✔
163

164
            $this->constants[$constantNameTmp] = (new PHPConst($this->parserContainer))->readObjectFromReflection($constant);
18✔
165

166
            if (!$this->constants[$constantNameTmp]->file) {
18✔
167
                $this->constants[$constantNameTmp]->file = $this->file;
×
168
            }
169
        }
170

171
        return $this;
30✔
172
    }
173

174
    /**
175
     * @param string[] $access
176
     * @param bool     $skipMethodsWithLeadingUnderscore
177
     *
178
     * @return array
179
     *
180
     * @psalm-return array<string, array{type: null|string, typeFromPhpDocMaybeWithComment: null|string, typeFromPhpDoc: null|string, typeFromPhpDocSimple: null|string, typeFromPhpDocExtended: null|string, typeFromDefaultValue: null|string}>
181
     */
182
    public function getPropertiesInfo(
183
        array $access = ['public', 'protected', 'private'],
184
        bool $skipMethodsWithLeadingUnderscore = false
185
    ): array {
186
        // init
187
        $allInfo = [];
8✔
188

189
        foreach ($this->properties as $property) {
8✔
190
            if (!\in_array($property->access, $access, true)) {
8✔
191
                continue;
×
192
            }
193

194
            if ($skipMethodsWithLeadingUnderscore && \strpos($property->name, '_') === 0) {
8✔
195
                continue;
×
196
            }
197

198
            $types = [];
8✔
199
            $types['type'] = $property->type;
8✔
200
            $types['typeFromPhpDocMaybeWithComment'] = $property->typeFromPhpDocMaybeWithComment;
8✔
201
            $types['typeFromPhpDoc'] = $property->typeFromPhpDoc;
8✔
202
            $types['typeFromPhpDocSimple'] = $property->typeFromPhpDocSimple;
8✔
203
            $types['typeFromPhpDocExtended'] = $property->typeFromPhpDocExtended;
8✔
204
            $types['typeFromDefaultValue'] = $property->typeFromDefaultValue;
8✔
205

206
            $allInfo[$property->name] = $types;
8✔
207
        }
208

209
        return $allInfo;
8✔
210
    }
211

212
    /**
213
     * @param string[] $access
214
     * @param bool     $skipDeprecatedMethods
215
     * @param bool     $skipMethodsWithLeadingUnderscore
216
     *
217
     * @return array<mixed>
218
     *
219
     * @psalm-return array<string, array{
220
     *     fullDescription: string,
221
     *     line: null|int,
222
     *     file: null|string,
223
     *     error: string,
224
     *     is_deprecated: bool,
225
     *     is_static: null|bool,
226
     *     is_meta: bool,
227
     *     is_internal: bool,
228
     *     is_removed: bool,
229
     *     paramsTypes: array<string,
230
     *         array{
231
     *              type?: null|string,
232
     *              typeFromPhpDoc?: null|string,
233
     *              typeFromPhpDocExtended?: null|string,
234
     *              typeFromPhpDocSimple?: null|string,
235
     *              typeFromPhpDocMaybeWithComment?: null|string,
236
     *              typeFromDefaultValue?: null|string
237
     *         }
238
     *     >,
239
     *     returnTypes: array{
240
     *         type: null|string,
241
     *         typeFromPhpDoc: null|string,
242
     *         typeFromPhpDocExtended: null|string,
243
     *         typeFromPhpDocSimple: null|string,
244
     *         typeFromPhpDocMaybeWithComment: null|string
245
     *     },
246
     *     paramsPhpDocRaw: array<string, null|string>,
247
     *     returnPhpDocRaw: null|string
248
     *  }>
249
     */
250
    public function getMethodsInfo(
251
        array $access = ['public', 'protected', 'private'],
252
        bool $skipDeprecatedMethods = false,
253
        bool $skipMethodsWithLeadingUnderscore = false
254
    ): array {
255
        // init
256
        $allInfo = [];
8✔
257

258
        foreach ($this->methods as $method) {
8✔
259
            if (!\in_array($method->access, $access, true)) {
8✔
260
                continue;
×
261
            }
262

263
            if ($skipDeprecatedMethods && $method->hasDeprecatedTag) {
8✔
264
                continue;
×
265
            }
266

267
            if ($skipMethodsWithLeadingUnderscore && \strpos($method->name, '_') === 0) {
8✔
268
                continue;
×
269
            }
270

271
            $paramsTypes = [];
8✔
272
            foreach ($method->parameters as $tagParam) {
8✔
273
                $paramsTypes[$tagParam->name]['type'] = $tagParam->type;
×
274
                $paramsTypes[$tagParam->name]['typeFromPhpDocMaybeWithComment'] = $tagParam->typeFromPhpDocMaybeWithComment;
×
275
                $paramsTypes[$tagParam->name]['typeFromPhpDoc'] = $tagParam->typeFromPhpDoc;
×
276
                $paramsTypes[$tagParam->name]['typeFromPhpDocSimple'] = $tagParam->typeFromPhpDocSimple;
×
277
                $paramsTypes[$tagParam->name]['typeFromPhpDocExtended'] = $tagParam->typeFromPhpDocExtended;
×
278
                $paramsTypes[$tagParam->name]['typeFromDefaultValue'] = $tagParam->typeFromDefaultValue;
×
279
            }
280

281
            $returnTypes = [];
8✔
282
            $returnTypes['type'] = $method->returnType;
8✔
283
            $returnTypes['typeFromPhpDocMaybeWithComment'] = $method->returnTypeFromPhpDocMaybeWithComment;
8✔
284
            $returnTypes['typeFromPhpDoc'] = $method->returnTypeFromPhpDoc;
8✔
285
            $returnTypes['typeFromPhpDocSimple'] = $method->returnTypeFromPhpDocSimple;
8✔
286
            $returnTypes['typeFromPhpDocExtended'] = $method->returnTypeFromPhpDocExtended;
8✔
287

288
            $paramsPhpDocRaw = [];
8✔
289
            foreach ($method->parameters as $tagParam) {
8✔
290
                $paramsPhpDocRaw[$tagParam->name] = $tagParam->phpDocRaw;
×
291
            }
292

293
            $infoTmp = [];
8✔
294
            $infoTmp['fullDescription'] = \trim($method->summary . "\n\n" . $method->description);
8✔
295
            $infoTmp['paramsTypes'] = $paramsTypes;
8✔
296
            $infoTmp['returnTypes'] = $returnTypes;
8✔
297
            $infoTmp['paramsPhpDocRaw'] = $paramsPhpDocRaw;
8✔
298
            $infoTmp['returnPhpDocRaw'] = $method->returnPhpDocRaw;
8✔
299
            $infoTmp['line'] = $method->line ?? $this->line;
8✔
300
            $infoTmp['file'] = $method->file ?? $this->file;
8✔
301
            $infoTmp['error'] = \implode("\n", $method->parseError);
8✔
302
            foreach ($method->parameters as $parameter) {
8✔
303
                $infoTmp['error'] .= ($infoTmp['error'] ? "\n" : '') . \implode("\n", $parameter->parseError);
×
304
            }
305
            $infoTmp['is_deprecated'] = $method->hasDeprecatedTag;
8✔
306
            $infoTmp['is_static'] = $method->is_static;
8✔
307
            $infoTmp['is_meta'] = $method->hasMetaTag;
8✔
308
            $infoTmp['is_internal'] = $method->hasInternalTag;
8✔
309
            $infoTmp['is_removed'] = $method->hasRemovedTag;
8✔
310

311
            $allInfo[$method->name] = $infoTmp;
8✔
312
        }
313

314
        \asort($allInfo);
8✔
315

316
        return $allInfo;
8✔
317
    }
318

319
    /**
320
     * @param Doc|string $doc
321
     */
322
    private function readPhpDocProperties($doc): void
323
    {
324
        if ($doc instanceof Doc) {
30✔
325
            $docComment = $doc->getText();
30✔
326
        } else {
327
            $docComment = $doc;
×
328
        }
329
        if ($docComment === '') {
30✔
330
            return;
×
331
        }
332

333
        try {
334
            $phpDoc = DocFactoryProvider::getDocFactory()->create($docComment);
30✔
335

336
            $parsedPropertyTags = $phpDoc->getTagsByName('property')
30✔
337
                               + $phpDoc->getTagsByName('property-read')
30✔
338
                               + $phpDoc->getTagsByName('property-write');
30✔
339

340
            if (!empty($parsedPropertyTags)) {
30✔
341
                foreach ($parsedPropertyTags as $parsedPropertyTag) {
30✔
342
                    if (
343
                        $parsedPropertyTag instanceof \phpDocumentor\Reflection\DocBlock\Tags\PropertyRead
×
344
                        ||
345
                        $parsedPropertyTag instanceof \phpDocumentor\Reflection\DocBlock\Tags\PropertyWrite
×
346
                        ||
347
                        $parsedPropertyTag instanceof \phpDocumentor\Reflection\DocBlock\Tags\Property
×
348
                    ) {
349
                        $propertyPhp = new PHPProperty($this->parserContainer);
×
350

351
                        $nameTmp = $parsedPropertyTag->getVariableName();
×
352
                        if (!$nameTmp) {
×
353
                            continue;
×
354
                        }
355

356
                        $propertyPhp->name = $nameTmp;
×
357

358
                        $propertyPhp->access = 'public';
×
359

360
                        $type = $parsedPropertyTag->getType();
×
361

362
                        $propertyPhp->typeFromPhpDoc = Utils::normalizePhpType($type . '');
×
363

364
                        $typeFromPhpDocMaybeWithCommentTmp = \trim((string) $parsedPropertyTag);
×
365
                        if (
366
                            $typeFromPhpDocMaybeWithCommentTmp
×
367
                            &&
368
                            \strpos($typeFromPhpDocMaybeWithCommentTmp, '$') !== 0
×
369
                        ) {
370
                            $propertyPhp->typeFromPhpDocMaybeWithComment = $typeFromPhpDocMaybeWithCommentTmp;
×
371
                        }
372

373
                        $typeTmp = Utils::parseDocTypeObject($type);
×
374
                        if ($typeTmp !== '') {
×
375
                            $propertyPhp->typeFromPhpDocSimple = $typeTmp;
×
376
                        }
377

378
                        if ($propertyPhp->typeFromPhpDoc) {
×
379
                            $propertyPhp->typeFromPhpDocExtended = Utils::modernPhpdoc($propertyPhp->typeFromPhpDoc);
×
380
                        }
381

382
                        $this->properties[$propertyPhp->name] = $propertyPhp;
×
383
                    }
384
                }
385
            }
386
        } catch (\Exception $e) {
×
387
            $tmpErrorMessage = ($this->name ?: '?') . ':' . ($this->line ?? '?') . ' | ' . \print_r($e->getMessage(), true);
×
388
            $this->parseError[\md5($tmpErrorMessage)] = $tmpErrorMessage;
×
389
        }
390
    }
391
}
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