StaticPropertyFetchAnalyzer   F
last analyzed

Complexity

Total Complexity 68

Size/Duplication

Total Lines 411
Duplicated Lines 24.33 %

Coupling/Cohesion

Components 1
Dependencies 32

Importance

Changes 0
Metric Value
dl 100
loc 411
rs 2.96
c 0
b 0
f 0
wmc 68
lcom 1
cbo 32

2 Methods

Rating   Name   Duplication   Size   Complexity  
C analyzeVariableStaticPropertyFetch() 0 81 10
F analyze() 100 321 58

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like StaticPropertyFetchAnalyzer often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use StaticPropertyFetchAnalyzer, and based on these observations, apply Extract Interface, too.

1
<?php
2
namespace Psalm\Internal\Analyzer\Statements\Expression\Fetch;
3
4
use PhpParser;
5
use Psalm\Internal\Analyzer\ClassLikeAnalyzer;
6
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
7
use Psalm\Internal\Analyzer\Statements\Expression\ExpressionIdentifier;
8
use Psalm\Internal\Analyzer\StatementsAnalyzer;
9
use Psalm\Internal\FileManipulation\FileManipulationBuffer;
10
use Psalm\CodeLocation;
11
use Psalm\Context;
12
use Psalm\Issue\ParentNotFound;
13
use Psalm\Issue\UndefinedPropertyFetch;
14
use Psalm\IssueBuffer;
15
use Psalm\Type;
16
use Psalm\Type\Atomic\TNamedObject;
17
use function strtolower;
18
use function in_array;
19
use function count;
20
use function explode;
21
22
/**
23
 * @internal
24
 */
25
class StaticPropertyFetchAnalyzer
26
{
27
    /**
28
     * @param   StatementsAnalyzer                       $statements_analyzer
29
     * @param   PhpParser\Node\Expr\StaticPropertyFetch $stmt
30
     * @param   Context                                 $context
31
     */
32
    public static function analyze(
33
        StatementsAnalyzer $statements_analyzer,
34
        PhpParser\Node\Expr\StaticPropertyFetch $stmt,
35
        Context $context
36
    ) : bool {
37
        if (!$stmt->class instanceof PhpParser\Node\Name) {
38
            self::analyzeVariableStaticPropertyFetch($statements_analyzer, $stmt->class, $stmt, $context);
39
            return true;
40
        }
41
42
        $fq_class_name = null;
0 ignored issues
show
Unused Code introduced by
$fq_class_name is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
43
44
        $codebase = $statements_analyzer->getCodebase();
45
46
        if (count($stmt->class->parts) === 1
47
            && in_array(strtolower($stmt->class->parts[0]), ['self', 'static', 'parent'], true)
48
        ) {
49
            if ($stmt->class->parts[0] === 'parent') {
50
                $fq_class_name = $statements_analyzer->getParentFQCLN();
51
52
                if ($fq_class_name === null) {
53
                    if (IssueBuffer::accepts(
54
                        new ParentNotFound(
55
                            'Cannot check property fetch on parent as this class does not extend another',
56
                            new CodeLocation($statements_analyzer->getSource(), $stmt)
57
                        ),
58
                        $statements_analyzer->getSuppressedIssues()
59
                    )) {
60
                        return false;
61
                    }
62
63
                    return true;
64
                }
65
            } else {
66
                $fq_class_name = (string)$context->self;
67
            }
68
69
            if ($context->isPhantomClass($fq_class_name)) {
70
                return true;
71
            }
72
        } else {
73
            $aliases = $statements_analyzer->getAliases();
74
75
            if ($context->calling_method_id
76
                && !$stmt->class instanceof PhpParser\Node\Name\FullyQualified
77
            ) {
78
                $codebase->file_reference_provider->addMethodReferenceToClassMember(
79
                    $context->calling_method_id,
80
                    'use:' . $stmt->class->parts[0] . ':' . \md5($statements_analyzer->getFilePath())
81
                );
82
            }
83
84
            $fq_class_name = ClassLikeAnalyzer::getFQCLNFromNameObject(
85
                $stmt->class,
86
                $aliases
87
            );
88
89
            if ($context->isPhantomClass($fq_class_name)) {
90
                return true;
91
            }
92
93
            if ($context->check_classes) {
94
                if (ClassLikeAnalyzer::checkFullyQualifiedClassLikeName(
95
                    $statements_analyzer,
96
                    $fq_class_name,
97
                    new CodeLocation($statements_analyzer->getSource(), $stmt->class),
98
                    $context->self,
99
                    $context->calling_method_id,
100
                    $statements_analyzer->getSuppressedIssues(),
101
                    false
102
                ) !== true) {
103
                    return false;
104
                }
105
            }
106
        }
107
108
        if ($fq_class_name
109
            && $codebase->methods_to_move
110
            && $context->calling_method_id
111
            && isset($codebase->methods_to_move[$context->calling_method_id])
112
        ) {
113
            $destination_method_id = $codebase->methods_to_move[$context->calling_method_id];
114
115
            $codebase->classlikes->airliftClassLikeReference(
116
                $fq_class_name,
117
                explode('::', $destination_method_id)[0],
118
                $statements_analyzer->getFilePath(),
119
                (int) $stmt->class->getAttribute('startFilePos'),
120
                (int) $stmt->class->getAttribute('endFilePos') + 1
121
            );
122
        }
123
124
        if ($fq_class_name) {
125
            $statements_analyzer->node_data->setType(
126
                $stmt->class,
127
                new Type\Union([new TNamedObject($fq_class_name)])
128
            );
129
        }
130
131 View Code Duplication
        if ($stmt->name instanceof PhpParser\Node\VarLikeIdentifier) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
132
            $prop_name = $stmt->name->name;
133
        } elseif (($stmt_name_type = $statements_analyzer->node_data->getType($stmt->name))
134
            && $stmt_name_type->isSingleStringLiteral()
135
        ) {
136
            $prop_name = $stmt_name_type->getSingleStringLiteral()->value;
137
        } else {
138
            $prop_name = null;
139
        }
140
141 View Code Duplication
        if (!$prop_name) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
142
            if ($fq_class_name) {
143
                $codebase->analyzer->addMixedMemberName(
144
                    strtolower($fq_class_name) . '::$',
145
                    $context->calling_method_id ?: $statements_analyzer->getFileName()
146
                );
147
            }
148
149
            return true;
150
        }
151
152
        if (!$fq_class_name
153
            || !$context->check_classes
154
            || !$context->check_variables
155
            || ExpressionAnalyzer::isMock($fq_class_name)
156
        ) {
157
            return true;
158
        }
159
160
        $var_id = ExpressionIdentifier::getVarId(
161
            $stmt,
162
            $context->self ?: $statements_analyzer->getFQCLN(),
163
            $statements_analyzer
164
        );
165
166
        $property_id = $fq_class_name . '::$' . $prop_name;
167
168 View Code Duplication
        if ($codebase->store_node_types
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
169
            && !$context->collect_initializations
170
            && !$context->collect_mutations
171
        ) {
172
            $codebase->analyzer->addNodeReference(
173
                $statements_analyzer->getFilePath(),
174
                $stmt->name,
175
                $property_id
176
            );
177
        }
178
179
        if ($context->mutation_free) {
180
            if (IssueBuffer::accepts(
181
                new \Psalm\Issue\ImpureStaticProperty(
182
                    'Cannot use a static property in a mutation-free context',
183
                    new CodeLocation($statements_analyzer, $stmt)
184
                ),
185
                $statements_analyzer->getSuppressedIssues()
186
            )) {
187
                // fall through
188
            }
189
        }
190
191
        if ($var_id && $context->hasVariable($var_id, $statements_analyzer)) {
192
            $stmt_type = $context->vars_in_scope[$var_id];
193
194
            // we don't need to check anything
195
            $statements_analyzer->node_data->setType($stmt, $stmt_type);
196
197
            if ($codebase->collect_references) {
198
                // log the appearance
199
                $codebase->properties->propertyExists(
200
                    $property_id,
201
                    true,
202
                    $statements_analyzer,
203
                    $context,
204
                    $codebase->collect_locations
205
                        ? new CodeLocation($statements_analyzer->getSource(), $stmt)
206
                        : null
207
                );
208
            }
209
210 View Code Duplication
            if ($codebase->store_node_types
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
211
                && !$context->collect_initializations
212
                && !$context->collect_mutations
213
                && ($stmt_type = $statements_analyzer->node_data->getType($stmt))
214
            ) {
215
                $codebase->analyzer->addNodeType(
216
                    $statements_analyzer->getFilePath(),
217
                    $stmt->name,
218
                    $stmt_type->getId()
219
                );
220
            }
221
222
            return true;
223
        }
224
225
        if (!$codebase->properties->propertyExists(
226
            $property_id,
227
            true,
228
            $statements_analyzer,
229
            $context,
230
            $codebase->collect_locations
231
                ? new CodeLocation($statements_analyzer->getSource(), $stmt)
232
                : null
233
        )
234
        ) {
235
            if ($context->inside_isset) {
236
                return true;
237
            }
238
239
            if (IssueBuffer::accepts(
240
                new UndefinedPropertyFetch(
241
                    'Static property ' . $property_id . ' is not defined',
242
                    new CodeLocation($statements_analyzer->getSource(), $stmt),
243
                    $property_id
244
                ),
245
                $statements_analyzer->getSuppressedIssues()
246
            )) {
247
                // fall through
248
            }
249
250
            return true;
251
        }
252
253 View Code Duplication
        if (ClassLikeAnalyzer::checkPropertyVisibility(
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
254
            $property_id,
255
            $context,
256
            $statements_analyzer,
257
            new CodeLocation($statements_analyzer->getSource(), $stmt),
258
            $statements_analyzer->getSuppressedIssues()
259
        ) === false) {
260
            return false;
261
        }
262
263
        $declaring_property_class = $codebase->properties->getDeclaringClassForProperty(
264
            $fq_class_name . '::$' . $prop_name,
265
            true,
266
            $statements_analyzer
267
        );
268
269
        if ($declaring_property_class === null) {
270
            return false;
271
        }
272
273
        $declaring_property_id = strtolower($declaring_property_class) . '::$' . $prop_name;
274
275 View Code Duplication
        if ($codebase->alter_code) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
276
            $moved_class = $codebase->classlikes->handleClassLikeReferenceInMigration(
277
                $codebase,
278
                $statements_analyzer,
279
                $stmt->class,
280
                $fq_class_name,
281
                $context->calling_method_id
282
            );
283
284
            if (!$moved_class) {
285
                foreach ($codebase->property_transforms as $original_pattern => $transformation) {
286
                    if ($declaring_property_id === $original_pattern) {
287
                        list($old_declaring_fq_class_name) = explode('::$', $declaring_property_id);
288
                        list($new_fq_class_name, $new_property_name) = explode('::$', $transformation);
289
290
                        $file_manipulations = [];
291
292
                        if (strtolower($new_fq_class_name) !== strtolower($old_declaring_fq_class_name)) {
293
                            $file_manipulations[] = new \Psalm\FileManipulation(
294
                                (int) $stmt->class->getAttribute('startFilePos'),
295
                                (int) $stmt->class->getAttribute('endFilePos') + 1,
296
                                Type::getStringFromFQCLN(
297
                                    $new_fq_class_name,
298
                                    $statements_analyzer->getNamespace(),
299
                                    $statements_analyzer->getAliasedClassesFlipped(),
300
                                    null
301
                                )
302
                            );
303
                        }
304
305
                        $file_manipulations[] = new \Psalm\FileManipulation(
306
                            (int) $stmt->name->getAttribute('startFilePos'),
307
                            (int) $stmt->name->getAttribute('endFilePos') + 1,
308
                            '$' . $new_property_name
309
                        );
310
311
                        FileManipulationBuffer::add($statements_analyzer->getFilePath(), $file_manipulations);
312
                    }
313
                }
314
            }
315
        }
316
317
        $class_storage = $codebase->classlike_storage_provider->get($declaring_property_class);
318
        $property = $class_storage->properties[$prop_name];
319
320
        if ($var_id) {
321
            if ($property->type) {
322
                $context->vars_in_scope[$var_id] = \Psalm\Internal\Type\TypeExpander::expandUnion(
323
                    $codebase,
324
                    clone $property->type,
325
                    $class_storage->name,
326
                    $class_storage->name,
327
                    $class_storage->parent_class
328
                );
329
            } else {
330
                $context->vars_in_scope[$var_id] = Type::getMixed();
331
            }
332
333
            $stmt_type = clone $context->vars_in_scope[$var_id];
334
335
            $statements_analyzer->node_data->setType($stmt, $stmt_type);
336
337 View Code Duplication
            if ($codebase->store_node_types
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
338
                && !$context->collect_initializations
339
                && !$context->collect_mutations
340
            ) {
341
                $codebase->analyzer->addNodeType(
342
                    $statements_analyzer->getFilePath(),
343
                    $stmt->name,
344
                    $stmt_type->getId()
345
                );
346
            }
347
        } else {
348
            $statements_analyzer->node_data->setType($stmt, Type::getMixed());
349
        }
350
351
        return true;
352
    }
353
354
    private static function analyzeVariableStaticPropertyFetch(
355
        StatementsAnalyzer $statements_analyzer,
356
        PhpParser\Node\Expr $stmt_class,
357
        PhpParser\Node\Expr\StaticPropertyFetch $stmt,
358
        Context $context
359
    ) : void {
360
        ExpressionAnalyzer::analyze(
361
            $statements_analyzer,
362
            $stmt_class,
363
            $context
364
        );
365
366
        $stmt_class_type = $statements_analyzer->node_data->getType($stmt_class) ?: Type::getMixed();
367
368
        $old_data_provider = $statements_analyzer->node_data;
369
370
        $stmt_type = null;
371
372
        $codebase = $statements_analyzer->getCodebase();
373
374
        foreach ($stmt_class_type->getAtomicTypes() as $class_atomic_type) {
375
            $statements_analyzer->node_data = clone $statements_analyzer->node_data;
376
377
            $string_type = ($class_atomic_type instanceof Type\Atomic\TClassString
378
                    && $class_atomic_type->as_type !== null)
379
                ? $class_atomic_type->as_type->value
380
                : ($class_atomic_type instanceof Type\Atomic\TLiteralString
381
                    ? $class_atomic_type->value
382
                    : null);
383
384
            if ($string_type) {
385
                $new_stmt_name = new PhpParser\Node\Name\FullyQualified(
386
                    $string_type,
387
                    $stmt_class->getAttributes()
388
                );
389
390
                $fake_static_property = new PhpParser\Node\Expr\StaticPropertyFetch(
391
                    $new_stmt_name,
392
                    $stmt->name,
393
                    $stmt->getAttributes()
394
                );
395
396
                self::analyze($statements_analyzer, $fake_static_property, $context);
397
398
                $fake_stmt_type = $statements_analyzer->node_data->getType($fake_static_property)
399
                    ?: Type::getMixed();
400
            } else {
401
                $fake_var_name = '__fake_var_' . (string) $stmt->getAttribute('startFilePos');
402
403
                $fake_var = new PhpParser\Node\Expr\Variable(
404
                    $fake_var_name,
405
                    $stmt_class->getAttributes()
406
                );
407
408
                $context->vars_in_scope['$' . $fake_var_name] = new Type\Union([$class_atomic_type]);
409
410
                $fake_instance_property = new PhpParser\Node\Expr\PropertyFetch(
411
                    $fake_var,
412
                    $stmt->name,
413
                    $stmt->getAttributes()
414
                );
415
416
                InstancePropertyFetchAnalyzer::analyze(
417
                    $statements_analyzer,
418
                    $fake_instance_property,
419
                    $context
420
                );
421
422
                $fake_stmt_type = $statements_analyzer->node_data->getType($fake_instance_property)
423
                    ?: Type::getMixed();
424
            }
425
426
            $stmt_type = $stmt_type
427
                ? Type::combineUnionTypes($stmt_type, $fake_stmt_type, $codebase)
428
                : $fake_stmt_type;
429
430
            $statements_analyzer->node_data = $old_data_provider;
431
        }
432
433
        $statements_analyzer->node_data->setType($stmt, $stmt_type);
434
    }
435
}
436