ClassConstFetchAnalyzer   F
last analyzed

Complexity

Total Complexity 78

Size/Duplication

Total Lines 396
Duplicated Lines 18.94 %

Coupling/Cohesion

Components 1
Dependencies 31

Importance

Changes 0
Metric Value
dl 75
loc 396
rs 2.16
c 0
b 0
f 0
wmc 78
lcom 1
cbo 31

2 Methods

Rating   Name   Duplication   Size   Complexity  
B analyzeClassConstAssignment() 0 32 6
F analyze() 75 360 72

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 ClassConstFetchAnalyzer 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 ClassConstFetchAnalyzer, 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\StatementsAnalyzer;
8
use Psalm\Internal\Analyzer\TraitAnalyzer;
9
use Psalm\Internal\FileManipulation\FileManipulationBuffer;
10
use Psalm\CodeLocation;
11
use Psalm\Context;
12
use Psalm\Issue\CircularReference;
13
use Psalm\Issue\DeprecatedClass;
14
use Psalm\Issue\DeprecatedConstant;
15
use Psalm\Issue\InaccessibleClassConstant;
16
use Psalm\Issue\NonStaticSelfCall;
17
use Psalm\Issue\ParentNotFound;
18
use Psalm\Issue\UndefinedConstant;
19
use Psalm\IssueBuffer;
20
use Psalm\Type;
21
use function strtolower;
22
use function explode;
23
24
/**
25
 * @internal
26
 */
27
class ClassConstFetchAnalyzer
28
{
29
    public static function analyze(
30
        StatementsAnalyzer $statements_analyzer,
31
        PhpParser\Node\Expr\ClassConstFetch $stmt,
32
        Context $context
33
    ) : bool {
34
        $codebase = $statements_analyzer->getCodebase();
35
36
        if ($stmt->class instanceof PhpParser\Node\Name) {
37
            $first_part_lc = strtolower($stmt->class->parts[0]);
38
39
            if ($first_part_lc === 'self' || $first_part_lc === 'static') {
40
                if (!$context->self) {
41
                    if (IssueBuffer::accepts(
42
                        new NonStaticSelfCall(
43
                            'Cannot use ' . $first_part_lc . ' outside class context',
44
                            new CodeLocation($statements_analyzer->getSource(), $stmt)
45
                        ),
46
                        $statements_analyzer->getSuppressedIssues()
47
                    )) {
48
                        return false;
49
                    }
50
51
                    return true;
52
                }
53
54
                $fq_class_name = $context->self;
55
            } elseif ($first_part_lc === 'parent') {
56
                $fq_class_name = $statements_analyzer->getParentFQCLN();
57
58
                if ($fq_class_name === null) {
59
                    if (IssueBuffer::accepts(
60
                        new ParentNotFound(
61
                            'Cannot check property fetch on parent as this class does not extend another',
62
                            new CodeLocation($statements_analyzer->getSource(), $stmt)
63
                        ),
64
                        $statements_analyzer->getSuppressedIssues()
65
                    )) {
66
                        return false;
67
                    }
68
69
                    return true;
70
                }
71
            } else {
72
                $fq_class_name = ClassLikeAnalyzer::getFQCLNFromNameObject(
73
                    $stmt->class,
74
                    $statements_analyzer->getAliases()
75
                );
76
77
                if ($stmt->name instanceof PhpParser\Node\Identifier) {
78
                    if ((!$context->inside_class_exists || $stmt->name->name !== 'class')
79
                        && !isset($context->phantom_classes[strtolower($fq_class_name)])
80
                    ) {
81
                        if (ClassLikeAnalyzer::checkFullyQualifiedClassLikeName(
82
                            $statements_analyzer,
83
                            $fq_class_name,
84
                            new CodeLocation($statements_analyzer->getSource(), $stmt->class),
85
                            $context->self,
86
                            $context->calling_method_id,
87
                            $statements_analyzer->getSuppressedIssues(),
88
                            false,
89
                            true
90
                        ) === false) {
91
                            return true;
92
                        }
93
                    }
94
                }
95
            }
96
97
            $moved_class = false;
98
99
            if ($codebase->alter_code
100
                && !\in_array($stmt->class->parts[0], ['parent', 'static'])
101
            ) {
102
                $moved_class = $codebase->classlikes->handleClassLikeReferenceInMigration(
103
                    $codebase,
104
                    $statements_analyzer,
105
                    $stmt->class,
106
                    $fq_class_name,
107
                    $context->calling_method_id,
108
                    false,
109
                    $stmt->class->parts[0] === 'self'
110
                );
111
            }
112
113
            if ($stmt->name instanceof PhpParser\Node\Identifier && $stmt->name->name === 'class') {
114
                if ($codebase->classlikes->classExists($fq_class_name)) {
115
                    $fq_class_name = $codebase->classlikes->getUnAliasedName($fq_class_name);
116
                    $class_const_storage = $codebase->classlike_storage_provider->get($fq_class_name);
117
                    $fq_class_name = $class_const_storage->name;
118
119 View Code Duplication
                    if ($class_const_storage->deprecated && $fq_class_name !== $context->self) {
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...
120
                        if (IssueBuffer::accepts(
121
                            new DeprecatedClass(
122
                                'Class ' . $fq_class_name . ' is deprecated',
123
                                new CodeLocation($statements_analyzer->getSource(), $stmt),
124
                                $fq_class_name
125
                            ),
126
                            $statements_analyzer->getSuppressedIssues()
127
                        )) {
128
                            // fall through
129
                        }
130
                    }
131
                }
132
133
                if ($first_part_lc === 'static') {
134
                    $static_named_object = new Type\Atomic\TNamedObject($fq_class_name);
135
                    $static_named_object->was_static = true;
136
137
                    $statements_analyzer->node_data->setType(
138
                        $stmt,
139
                        new Type\Union([
140
                            new Type\Atomic\TClassString($fq_class_name, $static_named_object)
141
                        ])
142
                    );
143
                } else {
144
                    $statements_analyzer->node_data->setType($stmt, Type::getLiteralClassString($fq_class_name));
145
                }
146
147 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...
148
                    && !$context->collect_initializations
149
                    && !$context->collect_mutations
150
                ) {
151
                    $codebase->analyzer->addNodeReference(
152
                        $statements_analyzer->getFilePath(),
153
                        $stmt->class,
154
                        $fq_class_name
155
                    );
156
                }
157
158
                return true;
159
            }
160
161
            // if we're ignoring that the class doesn't exist, exit anyway
162
            if (!$codebase->classlikes->classOrInterfaceExists($fq_class_name)) {
163
                $statements_analyzer->node_data->setType($stmt, Type::getMixed());
164
165
                return true;
166
            }
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->class,
175
                    $fq_class_name
176
                );
177
            }
178
179
            if (!$stmt->name instanceof PhpParser\Node\Identifier) {
180
                return true;
181
            }
182
183
            $const_id = $fq_class_name . '::' . $stmt->name;
184
185 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...
186
                && !$context->collect_initializations
187
                && !$context->collect_mutations
188
            ) {
189
                $codebase->analyzer->addNodeReference(
190
                    $statements_analyzer->getFilePath(),
191
                    $stmt->name,
192
                    $const_id
193
                );
194
            }
195
196
            if ($fq_class_name === $context->self
197
                || (
198
                    $statements_analyzer->getSource()->getSource() instanceof TraitAnalyzer &&
199
                    $fq_class_name === $statements_analyzer->getSource()->getFQCLN()
200
                )
201
            ) {
202
                $class_visibility = \ReflectionProperty::IS_PRIVATE;
203
            } elseif ($context->self &&
204
                ($codebase->classlikes->classExtends($context->self, $fq_class_name)
205
                    || $codebase->classlikes->classExtends($fq_class_name, $context->self))
206
            ) {
207
                $class_visibility = \ReflectionProperty::IS_PROTECTED;
208
            } else {
209
                $class_visibility = \ReflectionProperty::IS_PUBLIC;
210
            }
211
212
            try {
213
                $class_constant_type = $codebase->classlikes->getConstantForClass(
214
                    $fq_class_name,
215
                    $stmt->name->name,
216
                    $class_visibility,
217
                    $statements_analyzer
218
                );
219
            } catch (\InvalidArgumentException $_) {
220
                return true;
221
            } catch (\Psalm\Exception\CircularReferenceException $e) {
222
                if (IssueBuffer::accepts(
223
                    new CircularReference(
224
                        'Constant ' . $const_id . ' contains a circular reference',
225
                        new CodeLocation($statements_analyzer->getSource(), $stmt)
226
                    ),
227
                    $statements_analyzer->getSuppressedIssues()
228
                )) {
229
                    // fall through
230
                }
231
232
                return true;
233
            }
234
235
            if (!$class_constant_type) {
236
                if ($fq_class_name !== $context->self) {
237
                    $class_constant_type = $codebase->classlikes->getConstantForClass(
238
                        $fq_class_name,
239
                        $stmt->name->name,
240
                        \ReflectionProperty::IS_PRIVATE,
241
                        $statements_analyzer
242
                    );
243
                }
244
245 View Code Duplication
                if ($class_constant_type) {
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...
246
                    if (IssueBuffer::accepts(
247
                        new InaccessibleClassConstant(
248
                            'Constant ' . $const_id . ' is not visible in this context',
249
                            new CodeLocation($statements_analyzer->getSource(), $stmt)
250
                        ),
251
                        $statements_analyzer->getSuppressedIssues()
252
                    )) {
253
                        // fall through
254
                    }
255
                } elseif ($context->check_consts) {
256
                    if (IssueBuffer::accepts(
257
                        new UndefinedConstant(
258
                            'Constant ' . $const_id . ' is not defined',
259
                            new CodeLocation($statements_analyzer->getSource(), $stmt)
260
                        ),
261
                        $statements_analyzer->getSuppressedIssues()
262
                    )) {
263
                        // fall through
264
                    }
265
                }
266
267
                return true;
268
            }
269
270
            if ($context->calling_method_id) {
271
                $codebase->file_reference_provider->addMethodReferenceToClassMember(
272
                    $context->calling_method_id,
273
                    strtolower($fq_class_name) . '::' . $stmt->name->name
274
                );
275
            }
276
277
            $declaring_const_id = strtolower($fq_class_name) . '::' . $stmt->name->name;
278
279
            if ($codebase->alter_code && !$moved_class) {
280
                foreach ($codebase->class_constant_transforms as $original_pattern => $transformation) {
281
                    if ($declaring_const_id === $original_pattern) {
282
                        list($new_fq_class_name, $new_const_name) = explode('::', $transformation);
283
284
                        $file_manipulations = [];
285
286
                        if (strtolower($new_fq_class_name) !== strtolower($fq_class_name)) {
287
                            $file_manipulations[] = new \Psalm\FileManipulation(
288
                                (int) $stmt->class->getAttribute('startFilePos'),
289
                                (int) $stmt->class->getAttribute('endFilePos') + 1,
290
                                Type::getStringFromFQCLN(
291
                                    $new_fq_class_name,
292
                                    $statements_analyzer->getNamespace(),
293
                                    $statements_analyzer->getAliasedClassesFlipped(),
294
                                    null
295
                                )
296
                            );
297
                        }
298
299
                        $file_manipulations[] = new \Psalm\FileManipulation(
300
                            (int) $stmt->name->getAttribute('startFilePos'),
301
                            (int) $stmt->name->getAttribute('endFilePos') + 1,
302
                            $new_const_name
303
                        );
304
305
                        FileManipulationBuffer::add($statements_analyzer->getFilePath(), $file_manipulations);
306
                    }
307
                }
308
            }
309
310
            $class_const_storage = $codebase->classlike_storage_provider->get($fq_class_name);
311
312
            if ($class_const_storage->deprecated && $fq_class_name !== $context->self) {
313
                if (IssueBuffer::accepts(
314
                    new DeprecatedClass(
315
                        'Class ' . $fq_class_name . ' is deprecated',
316
                        new CodeLocation($statements_analyzer->getSource(), $stmt),
317
                        $fq_class_name
318
                    ),
319
                    $statements_analyzer->getSuppressedIssues()
320
                )) {
321
                    // fall through
322
                }
323
            } elseif (isset($class_const_storage->deprecated_constants[$stmt->name->name])) {
324
                if (IssueBuffer::accepts(
325
                    new DeprecatedConstant(
326
                        'Constant ' . $const_id . ' is deprecated',
327
                        new CodeLocation($statements_analyzer->getSource(), $stmt)
328
                    ),
329
                    $statements_analyzer->getSuppressedIssues()
330
                )) {
331
                    // fall through
332
                }
333
            }
334
335
            if ($first_part_lc !== 'static' || $class_const_storage->final) {
336
                $stmt_type = clone $class_constant_type;
337
338
                $statements_analyzer->node_data->setType($stmt, $stmt_type);
339
                $context->vars_in_scope[$const_id] = $stmt_type;
340
            } else {
341
                $statements_analyzer->node_data->setType($stmt, Type::getMixed());
342
            }
343
344
            return true;
345
        }
346
347
        if ($stmt->name instanceof PhpParser\Node\Identifier && $stmt->name->name === 'class') {
348
            ExpressionAnalyzer::analyze($statements_analyzer, $stmt->class, $context);
349
            $lhs_type = $statements_analyzer->node_data->getType($stmt->class);
350
351
            $class_string_types = [];
352
353
            $has_mixed_or_object = false;
354
355
            if ($lhs_type) {
356 View Code Duplication
                foreach ($lhs_type->getAtomicTypes() as $lhs_atomic_type) {
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...
357
                    if ($lhs_atomic_type instanceof Type\Atomic\TNamedObject) {
358
                        $class_string_types[] = new Type\Atomic\TClassString(
359
                            $lhs_atomic_type->value,
360
                            clone $lhs_atomic_type
361
                        );
362
                    } elseif ($lhs_atomic_type instanceof Type\Atomic\TObject
363
                        || $lhs_atomic_type instanceof Type\Atomic\TMixed
364
                    ) {
365
                        $has_mixed_or_object = true;
366
                    }
367
                }
368
            }
369
370
            if ($has_mixed_or_object) {
371
                $statements_analyzer->node_data->setType($stmt, new Type\Union([new Type\Atomic\TClassString()]));
372
            } elseif ($class_string_types) {
373
                $statements_analyzer->node_data->setType($stmt, new Type\Union($class_string_types));
374
            } else {
375
                $statements_analyzer->node_data->setType($stmt, Type::getMixed());
376
            }
377
378
            return true;
379
        }
380
381
        $statements_analyzer->node_data->setType($stmt, Type::getMixed());
382
383
        if (ExpressionAnalyzer::analyze($statements_analyzer, $stmt->class, $context) === false) {
384
            return false;
385
        }
386
387
        return true;
388
    }
389
390
    public static function analyzeClassConstAssignment(
391
        StatementsAnalyzer $statements_analyzer,
392
        PhpParser\Node\Stmt\ClassConst $stmt,
393
        Context $context
394
    ): void {
395
        $const_visibility = \ReflectionProperty::IS_PUBLIC;
396
397
        if ($stmt->isProtected()) {
398
            $const_visibility = \ReflectionProperty::IS_PROTECTED;
399
        }
400
401
        if ($stmt->isPrivate()) {
402
            $const_visibility = \ReflectionProperty::IS_PRIVATE;
403
        }
404
405
        $codebase = $statements_analyzer->getCodebase();
406
407
        foreach ($stmt->consts as $const) {
408
            ExpressionAnalyzer::analyze($statements_analyzer, $const->value, $context);
409
410
            if (($const_type = $statements_analyzer->node_data->getType($const->value))
411
                && !$const_type->hasMixed()
412
            ) {
413
                $codebase->classlikes->setConstantType(
414
                    (string)$statements_analyzer->getFQCLN(),
415
                    $const->name->name,
416
                    $const_type,
417
                    $const_visibility
418
                );
419
            }
420
        }
421
    }
422
}
423