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

voku / Simple-PHP-Code-Parser / 24272981784

11 Apr 2026 02:51AM UTC coverage: 79.752% (-3.5%) from 83.27%
24272981784

Pull #78

github

web-flow
Merge 7f69b0ffc into cc07ae5a0
Pull Request #78: Update dependencies: php-parser v5, phpdoc-parser v2, reflection-docblock v6, type-resolver v2, phpunit v11

26 of 33 new or added lines in 9 files covered. (78.79%)

27 existing lines in 3 files now uncovered.

1481 of 1857 relevant lines covered (79.75%)

33.51 hits per line

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

85.31
/src/voku/SimplePhpParser/Model/PHPParameter.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\FunctionLike;
9
use PhpParser\Node\Param;
10
use ReflectionParameter;
11
use voku\SimplePhpParser\Parsers\Helper\DocFactoryProvider;
12
use voku\SimplePhpParser\Parsers\Helper\Utils;
13

14
class PHPParameter extends BasePHPElement
15
{
16
    /**
17
     * @var mixed|null
18
     */
19
    public $defaultValue;
20

21
    public ?string $phpDocRaw = null;
22

23
    public ?string $type = null;
24

25
    public ?string $typeFromDefaultValue = null;
26

27
    public ?string $typeFromPhpDoc = null;
28

29
    public ?string $typeFromPhpDocSimple = null;
30

31
    public ?string $typeFromPhpDocExtended = null;
32

33
    public ?string $typeFromPhpDocMaybeWithComment = null;
34

35
    public ?bool $is_vararg = null;
36

37
    public ?bool $is_passed_by_ref = null;
38

39
    public ?bool $is_inheritdoc = null;
40

41
    /**
42
     * PHP 8.0+ attributes on this parameter.
43
     *
44
     * @var PHPAttribute[]
45
     */
46
    public array $attributes = [];
47

48
    /**
49
     * @param Param        $parameter
50
     * @param FunctionLike $node
51
     * @param mixed|null   $classStr
52
     *
53
     * @return $this
54
     */
55
    public function readObjectFromPhpNode($parameter, $node = null, $classStr = null): self
56
    {
57
        $parameterVar = $parameter->var;
78✔
58
        if ($parameterVar instanceof \PhpParser\Node\Expr\Error) {
78✔
59
            $this->parseError[] = ($this->line ?? '?') . ':' . ($this->pos ?? '') . ' | may be at this position an expression is required';
×
60

61
            $this->name = \md5(\uniqid('error', true));
×
62

63
            return $this;
×
64
        }
65

66
        $this->name = \is_string($parameterVar->name) ? $parameterVar->name : '';
78✔
67

68
        if ($node) {
78✔
69
            $this->prepareNode($node);
78✔
70

71
            $docComment = $node->getDocComment();
78✔
72
            if ($docComment) {
78✔
73
                $docCommentText = $docComment->getText();
42✔
74

75
                if (\stripos($docCommentText, '@inheritdoc') !== false) {
42✔
76
                    $this->is_inheritdoc = true;
24✔
77
                }
78

79
                $this->readPhpDoc($docComment, $this->name);
42✔
80
            }
81
        }
82

83
        if ($parameter->type !== null) {
78✔
84
            if (!$this->type) {
63✔
85
                $typeStr = Utils::typeNodeToString($parameter->type);
18✔
86
                if ($typeStr !== null) {
18✔
87
                    $this->type = $typeStr;
18✔
88
                }
89
            }
90

91
            if ($parameter->type instanceof \PhpParser\Node\NullableType) {
63✔
92
                if ($this->type && $this->type !== 'null' && \strpos($this->type, 'null|') !== 0) {
12✔
93
                    $this->type = 'null|' . $this->type;
9✔
94
                } elseif (!$this->type) {
12✔
95
                    $this->type = 'null|mixed';
×
96
                }
97
            }
98
        }
99

100
        if ($parameter->default) {
78✔
101
            $defaultValue = Utils::getPhpParserValueFromNode($parameter->default, $classStr, $this->parserContainer);
39✔
102
            if ($defaultValue !== Utils::GET_PHP_PARSER_VALUE_FROM_NODE_HELPER) {
39✔
103
                $this->defaultValue = $defaultValue;
39✔
104

105
                $this->typeFromDefaultValue = Utils::normalizePhpType(\gettype($this->defaultValue));
39✔
106
            }
107
        }
108

109
        $this->is_vararg = $parameter->variadic;
78✔
110

111
        $this->is_passed_by_ref = $parameter->byRef;
78✔
112

113
        // Extract PHP 8.0+ attributes (only if not already populated by reflection)
114
        if (empty($this->attributes) && !empty($parameter->attrGroups)) {
78✔
115
            $this->attributes = Utils::extractAttributesFromAstNode($parameter->attrGroups);
3✔
116
        }
117

118
        return $this;
78✔
119
    }
120

121
    /**
122
     * @param ReflectionParameter $parameter
123
     *
124
     * @return $this
125
     */
126
    public function readObjectFromReflection($parameter): self
127
    {
128
        $this->name = $parameter->getName();
78✔
129

130
        if ($parameter->isDefaultValueAvailable()) {
78✔
131
            try {
132
                $this->defaultValue = $parameter->getDefaultValue();
39✔
NEW
133
            } catch (\ReflectionException $e) {
×
134
                // nothing
135
            }
136
            if ($this->defaultValue !== null) {
39✔
137
                $this->typeFromDefaultValue = Utils::normalizePhpType(\gettype($this->defaultValue));
39✔
138
            }
139
        }
140

141
        $method = $parameter->getDeclaringFunction();
78✔
142

143
        $docComment = $method->getDocComment();
78✔
144
        if ($docComment) {
78✔
145
            if (\stripos($docComment, '@inheritdoc') !== false) {
39✔
146
                $this->is_inheritdoc = true;
24✔
147
            }
148

149
            $this->readPhpDoc($docComment, $this->name);
39✔
150
        }
151

152
        try {
153
            $type = $parameter->getType();
78✔
154
        } catch (\ReflectionException $e) {
×
155
            $type = null;
×
156
        }
157
        if ($type !== null) {
78✔
158
            if (\method_exists($type, 'getName')) {
78✔
159
                $this->type = Utils::normalizePhpType($type->getName(), true);
60✔
160
            } else {
161
                $this->type = Utils::normalizePhpType($type . '', true);
30✔
162
            }
163
            if ($this->type && \class_exists($this->type, true)) {
78✔
164
                $this->type = '\\' . \ltrim($this->type, '\\');
42✔
165
            }
166

167
            try {
168
                $constNameTmp = $parameter->getDefaultValueConstantName();
78✔
169
                if ($constNameTmp && \defined($constNameTmp)) {
39✔
170
                    $defaultTmp = \constant($constNameTmp);
9✔
171
                    if ($defaultTmp === null) {
9✔
172
                        if ($this->type && $this->type !== 'null' && \strpos($this->type, 'null|') !== 0) {
×
173
                            $this->type = 'null|' . $this->type;
×
174
                        } elseif (!$this->type) {
×
175
                            $this->type = 'null|mixed';
39✔
176
                        }
177
                    }
178
                }
179
            } catch (\ReflectionException $e) {
78✔
180
                if ($type->allowsNull()) {
78✔
181
                    if ($this->type && $this->type !== 'null' && \strpos($this->type, 'null|') !== 0) {
12✔
182
                        $this->type = 'null|' . $this->type;
12✔
183
                    } elseif (!$this->type) {
×
184
                        $this->type = 'null|mixed';
×
185
                    }
186
                }
187
            }
188
        }
189

190
        $this->is_vararg = $parameter->isVariadic();
78✔
191

192
        $this->is_passed_by_ref = $parameter->isPassedByReference();
78✔
193

194
        // Extract PHP 8.0+ attributes
195
        $this->attributes = Utils::extractAttributesFromReflection($parameter);
78✔
196

197
        return $this;
78✔
198
    }
199

200
    /**
201
     * @return string|null
202
     */
203
    public function getType(): ?string
204
    {
205
        if ($this->typeFromPhpDocExtended) {
×
206
            return $this->typeFromPhpDocExtended;
×
207
        }
208

209
        if ($this->type) {
×
210
            return $this->type;
×
211
        }
212

213
        if ($this->typeFromPhpDocSimple) {
×
214
            return $this->typeFromPhpDocSimple;
×
215
        }
216

217
        return null;
×
218
    }
219

220
    /**
221
     * @param Doc|string $doc
222
     */
223
    private function readPhpDoc($doc, string $parameterName): void
224
    {
225
        if ($doc instanceof Doc) {
45✔
226
            $docComment = $doc->getText();
42✔
227
        } else {
228
            $docComment = $doc;
39✔
229
        }
230
        if ($docComment === '') {
45✔
231
            return;
×
232
        }
233

234
        try {
235
            $phpDoc = DocFactoryProvider::getDocFactory()->create($docComment);
45✔
236

237
            $parsedParamTags = $phpDoc->getTagsByName('param');
45✔
238

239
            if (!empty($parsedParamTags)) {
45✔
240
                foreach ($parsedParamTags as $parsedParamTag) {
42✔
241
                    if ($parsedParamTag instanceof \phpDocumentor\Reflection\DocBlock\Tags\Param) {
42✔
242
                        // check only the current "param"-tag
243
                        if (\strtoupper($parameterName) !== \strtoupper((string) $parsedParamTag->getVariableName())) {
42✔
244
                            continue;
36✔
245
                        }
246

247
                        $type = $parsedParamTag->getType();
42✔
248

249
                        $this->typeFromPhpDoc = Utils::normalizePhpType($type . '');
42✔
250

251
                        $typeFromPhpDocMaybeWithCommentTmp = \trim((string) $parsedParamTag);
42✔
252
                        if (
253
                            $typeFromPhpDocMaybeWithCommentTmp
42✔
254
                            &&
255
                            \strpos($typeFromPhpDocMaybeWithCommentTmp, '$') !== 0
42✔
256
                        ) {
257
                            $this->typeFromPhpDocMaybeWithComment = $typeFromPhpDocMaybeWithCommentTmp;
42✔
258
                        }
259

260
                        $typeTmp = Utils::parseDocTypeObject($type);
42✔
261
                        if ($typeTmp !== '') {
42✔
262
                            $this->typeFromPhpDocSimple = $typeTmp;
42✔
263
                        }
264
                    }
265

266
                    $parsedParamTagParam = (string) $parsedParamTag;
42✔
267
                    $spitedData = Utils::splitTypeAndVariable($parsedParamTag);
42✔
268
                    $variableName = $spitedData['variableName'];
42✔
269

270
                    // check only the current "param"-tag
271
                    if ($variableName && \strtoupper($parameterName) === \strtoupper($variableName)) {
42✔
272
                        $this->phpDocRaw = $parsedParamTagParam;
42✔
273
                        $this->typeFromPhpDocExtended = Utils::modernPhpdoc($parsedParamTagParam);
42✔
274
                    }
275

276
                    break;
42✔
277
                }
278
            }
279

280
            $parsedParamTags = $phpDoc->getTagsByName('psalm-param')
45✔
281
                               + $phpDoc->getTagsByName('phpstan-param');
45✔
282

283
            if (!empty($parsedParamTags)) {
45✔
284
                foreach ($parsedParamTags as $parsedParamTag) {
45✔
285
                    if (!$parsedParamTag instanceof \phpDocumentor\Reflection\DocBlock\Tags\Generic) {
21✔
286
                        continue;
×
287
                    }
288

289
                    $spitedData = Utils::splitTypeAndVariable($parsedParamTag);
21✔
290
                    $parsedParamTagStr = $spitedData['parsedParamTagStr'];
21✔
291
                    $variableName = $spitedData['variableName'];
21✔
292

293
                    // check only the current "param"-tag
294
                    if (!$variableName || \strtoupper($parameterName) !== \strtoupper($variableName)) {
21✔
295
                        continue;
21✔
296
                    }
297

298
                    $this->typeFromPhpDocExtended = Utils::modernPhpdoc($parsedParamTagStr);
21✔
299
                }
300
            }
301
        } catch (\Exception $e) {
6✔
302
            $tmpErrorMessage = $this->name . ':' . ($this->line ?? '?') . ' | ' . \print_r($e->getMessage(), true);
6✔
303
            $this->parseError[\md5($tmpErrorMessage)] = $tmpErrorMessage;
6✔
304
        }
305

306
        try {
307
            $this->readPhpDocByTokens($docComment, $parameterName);
45✔
308
        } catch (\Exception $e) {
9✔
309
            $tmpErrorMessage = $this->name . ':' . ($this->line ?? '?') . ' | ' . \print_r($e->getMessage(), true);
9✔
310
            $this->parseError[\md5($tmpErrorMessage)] = $tmpErrorMessage;
9✔
311
        }
312
    }
313

314
    /**
315
     * @throws \PHPStan\PhpDocParser\Parser\ParserException
316
     */
317
    private function readPhpDocByTokens(string $docComment, string $parameterName): void
318
    {
319
        $tokens = Utils::modernPhpdocTokens($docComment);
45✔
320

321
        $paramContent = null;
45✔
322
        foreach ($tokens->getTokens() as $token) {
45✔
323
            $content = $token[0];
45✔
324

325
            if ($content === '@param' || $content === '@psalm-param' || $content === '@phpstan-param') {
45✔
326
                // reset
327
                $paramContent = '';
42✔
328

329
                continue;
42✔
330
            }
331

332
            // We can stop if we found the param variable e.g. `@param array{foo:int} $param`.
333
            if ($content === '$' . $parameterName) {
45✔
334
                break;
42✔
335
            }
336

337
            if ($paramContent !== null) {
45✔
338
                $paramContent .= $content;
42✔
339
            }
340
        }
341

342
        $paramContent = $paramContent ? \trim($paramContent) : null;
45✔
343
        if ($paramContent) {
45✔
344
            if (!$this->phpDocRaw) {
42✔
345
                $this->phpDocRaw = $paramContent . ' ' . '$' . $parameterName;
21✔
346
            }
347
            $this->typeFromPhpDocExtended = Utils::modernPhpdoc($paramContent);
42✔
348
        }
349
    }
350
}
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