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

voku / Simple-PHP-Code-Parser / 24356931827

13 Apr 2026 05:20PM UTC coverage: 84.815%. Remained the same
24356931827

Pull #87

github

web-flow
Merge 09c5a3e3f into 605f079b7
Pull Request #87: Update actions/cache action to v5.0.5

1765 of 2081 relevant lines covered (84.81%)

149.67 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);
58✔
29

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

32
        // Extract PHP 8.0+ attributes
33
        if (!empty($node->attrGroups)) {
58✔
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());
58✔
40
        $traitExists = false;
58✔
41
        if ($canAutoload) {
58✔
42
            try {
43
                if (\trait_exists($this->name, true)) {
58✔
44
                    $traitExists = true;
58✔
45
                }
46
            } catch (\Throwable $e) {
×
47
                // nothing
48
            }
49
        }
50
        if ($traitExists) {
58✔
51
            $reflectionClass = Utils::createClassReflectionInstance($this->name);
48✔
52
            $this->readObjectFromReflection($reflectionClass);
48✔
53
        }
54

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

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

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

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

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

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

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

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

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

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

103
        return $this;
58✔
104
    }
105

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

171
        return $this;
48✔
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 = [];
10✔
188

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

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

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

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

209
        return $allInfo;
10✔
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 = [];
10✔
257

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

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

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

271
            $paramsTypes = [];
10✔
272
            foreach ($method->parameters as $tagParam) {
10✔
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 = [];
10✔
282
            $returnTypes['type'] = $method->returnType;
10✔
283
            $returnTypes['typeFromPhpDocMaybeWithComment'] = $method->returnTypeFromPhpDocMaybeWithComment;
10✔
284
            $returnTypes['typeFromPhpDoc'] = $method->returnTypeFromPhpDoc;
10✔
285
            $returnTypes['typeFromPhpDocSimple'] = $method->returnTypeFromPhpDocSimple;
10✔
286
            $returnTypes['typeFromPhpDocExtended'] = $method->returnTypeFromPhpDocExtended;
10✔
287

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

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

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

314
        \asort($allInfo);
10✔
315

316
        return $allInfo;
10✔
317
    }
318

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

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

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

340
            if (!empty($parsedPropertyTags)) {
48✔
341
                foreach ($parsedPropertyTags as $parsedPropertyTag) {
48✔
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