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

keradus / PHP-CS-Fixer / 17253322895

26 Aug 2025 11:52PM UTC coverage: 94.753% (+0.008%) from 94.745%
17253322895

push

github

keradus
add to git-blame-ignore-revs

28316 of 29884 relevant lines covered (94.75%)

45.64 hits per line

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

99.14
/src/Fixer/ClassNotation/StaticPrivateMethodFixer.php
1
<?php
2

3
declare(strict_types=1);
4

5
/*
6
 * This file is part of PHP CS Fixer.
7
 *
8
 * (c) Fabien Potencier <fabien@symfony.com>
9
 *     Dariusz Rumiński <dariusz.ruminski@gmail.com>
10
 *
11
 * This source file is subject to the MIT license that is bundled
12
 * with this source code in the file LICENSE.
13
 */
14

15
namespace PhpCsFixer\Fixer\ClassNotation;
16

17
use PhpCsFixer\AbstractFixer;
18
use PhpCsFixer\FixerDefinition\CodeSample;
19
use PhpCsFixer\FixerDefinition\FixerDefinition;
20
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
21
use PhpCsFixer\Tokenizer\Token;
22
use PhpCsFixer\Tokenizer\Tokens;
23
use PhpCsFixer\Tokenizer\TokensAnalyzer;
24

25
/**
26
 * @author Filippo Tessarotto <zoeslam@gmail.com>
27
 *
28
 * @no-named-arguments Parameter names are not covered by the backward compatibility promise.
29
 */
30
final class StaticPrivateMethodFixer extends AbstractFixer
31
{
32
    /**
33
     * @var array<string, true>
34
     */
35
    private const MAGIC_METHODS = [
36
        '__clone' => true,
37
        '__construct' => true,
38
        '__destruct' => true,
39
        '__wakeup' => true,
40
    ];
41

42
    public function getDefinition(): FixerDefinitionInterface
43
    {
44
        return new FixerDefinition(
3✔
45
            'Converts private methods to `static` where possible.',
3✔
46
            [
3✔
47
                new CodeSample(
3✔
48
                    '<?php
3✔
49
class Foo
50
{
51
    public function bar()
52
    {
53
        return $this->baz();
54
    }
55

56
    private function baz()
57
    {
58
        return 1;
59
    }
60
}
61
'
3✔
62
                ),
3✔
63
            ],
3✔
64
            null,
3✔
65
            'Risky when the method:'
3✔
66
            .' contains dynamic generated calls to the instance,'
3✔
67
            .' is dynamically referenced,'
3✔
68
            .' is referenced inside a Trait the class uses.'
3✔
69
        );
3✔
70
    }
71

72
    /**
73
     * {@inheritdoc}
74
     *
75
     * Must run before StaticLambdaFixer.
76
     * Must run after ProtectedToPrivateFixer.
77
     */
78
    public function getPriority(): int
79
    {
80
        return 1;
1✔
81
    }
82

83
    public function isCandidate(Tokens $tokens): bool
84
    {
85
        return $tokens->isAllTokenKindsFound([\T_CLASS, \T_PRIVATE, \T_FUNCTION]);
17✔
86
    }
87

88
    public function isRisky(): bool
89
    {
90
        return true;
1✔
91
    }
92

93
    protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
94
    {
95
        $tokensAnalyzer = new TokensAnalyzer($tokens);
17✔
96

97
        do {
98
            $anythingChanged = false;
17✔
99

100
            $end = \count($tokens) - 3; // min. number of tokens to form a class candidate to fix
17✔
101
            for ($index = $end; $index > 0; --$index) {
17✔
102
                if (!$tokens[$index]->isGivenKind(\T_CLASS)) {
17✔
103
                    continue;
17✔
104
                }
105

106
                $classOpen = $tokens->getNextTokenOfKind($index, ['{']);
17✔
107
                $classClose = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $classOpen);
17✔
108

109
                $anythingChanged |= $this->fixClass($tokens, $tokensAnalyzer, $classOpen, $classClose);
17✔
110
            }
111
        } while ($anythingChanged);
17✔
112
    }
113

114
    private function fixClass(Tokens $tokens, TokensAnalyzer $tokensAnalyzer, int $classOpen, int $classClose): bool
115
    {
116
        $fixedMethods = [];
17✔
117
        foreach ($this->getClassMethods($tokens, $classOpen, $classClose) as $methodData) {
17✔
118
            [$functionKeywordIndex, $methodOpen, $methodClose] = $methodData;
17✔
119

120
            if ($this->skipMethod($tokens, $tokensAnalyzer, $functionKeywordIndex, $methodOpen, $methodClose)) {
17✔
121
                continue;
17✔
122
            }
123

124
            $methodNameIndex = $tokens->getNextMeaningfulToken($functionKeywordIndex);
12✔
125
            $methodName = $tokens[$methodNameIndex]->getContent();
12✔
126
            $fixedMethods[$methodName] = true;
12✔
127

128
            $tokens->insertSlices([$functionKeywordIndex => [new Token([\T_STATIC, 'static']), new Token([\T_WHITESPACE, ' '])]]);
12✔
129
        }
130

131
        if (0 === \count($fixedMethods)) {
17✔
132
            return false;
17✔
133
        }
134

135
        $classClose = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $classOpen);
12✔
136
        foreach ($this->getClassMethods($tokens, $classOpen, $classClose) as $methodData) {
12✔
137
            [, $methodOpen, $methodClose] = $methodData;
12✔
138

139
            $this->fixReferencesInFunction($tokens, $tokensAnalyzer, $methodOpen, $methodClose, $fixedMethods);
12✔
140
        }
141

142
        return true;
12✔
143
    }
144

145
    private function skipMethod(Tokens $tokens, TokensAnalyzer $tokensAnalyzer, int $functionKeywordIndex, int $methodOpen, int $methodClose): bool
146
    {
147
        $methodNameIndex = $tokens->getNextMeaningfulToken($functionKeywordIndex);
17✔
148
        $methodName = strtolower($tokens[$methodNameIndex]->getContent());
17✔
149
        if (isset(self::MAGIC_METHODS[$methodName])) {
17✔
150
            return true;
1✔
151
        }
152

153
        $prevTokenIndex = $tokens->getPrevMeaningfulToken($functionKeywordIndex);
16✔
154
        if ($tokens[$prevTokenIndex]->isGivenKind(\T_FINAL)) {
16✔
155
            $prevTokenIndex = $tokens->getPrevMeaningfulToken($prevTokenIndex);
1✔
156
        }
157
        if (!$tokens[$prevTokenIndex]->isGivenKind(\T_PRIVATE)) {
16✔
158
            return true;
13✔
159
        }
160

161
        $prePrevTokenIndex = $tokens->getPrevMeaningfulToken($prevTokenIndex);
16✔
162
        if ($tokens[$prePrevTokenIndex]->isGivenKind(\T_STATIC)) {
16✔
163
            return true;
1✔
164
        }
165

166
        for ($index = $methodOpen + 1; $index < $methodClose - 1; ++$index) {
15✔
167
            if ($tokens[$index]->isGivenKind(\T_CLASS) && $tokensAnalyzer->isAnonymousClass($index)) {
13✔
168
                $anonymousClassOpen = $tokens->getNextTokenOfKind($index, ['{']);
1✔
169
                $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $anonymousClassOpen);
1✔
170

171
                continue;
1✔
172
            }
173

174
            if ($tokens[$index]->isGivenKind(\T_FUNCTION)) {
13✔
175
                return true;
1✔
176
            }
177

178
            if ($tokens[$index]->equals([\T_VARIABLE, '$this'])) {
13✔
179
                $operatorIndex = $tokens->getNextMeaningfulToken($index);
3✔
180
                $methodNameIndex = $tokens->getNextMeaningfulToken($operatorIndex);
3✔
181
                $argumentsBraceIndex = $tokens->getNextMeaningfulToken($methodNameIndex);
3✔
182

183
                if (
184
                    !$tokens[$operatorIndex]->isGivenKind(\T_OBJECT_OPERATOR)
3✔
185
                    || $methodName !== $tokens[$methodNameIndex]->getContent()
3✔
186
                    || !$tokens[$argumentsBraceIndex]->equals('(')
3✔
187
                ) {
188
                    return true;
2✔
189
                }
190
            }
191

192
            if ($tokens[$index]->equals([\T_STRING, 'debug_backtrace'])) {
13✔
193
                return true;
1✔
194
            }
195
        }
196

197
        return false;
12✔
198
    }
199

200
    /**
201
     * @param array<string, bool> $fixedMethods
202
     */
203
    private function fixReferencesInFunction(Tokens $tokens, TokensAnalyzer $tokensAnalyzer, int $methodOpen, int $methodClose, array $fixedMethods): void
204
    {
205
        for ($index = $methodOpen + 1; $index < $methodClose - 1; ++$index) {
12✔
206
            if ($tokens[$index]->isGivenKind(\T_FUNCTION)) {
11✔
207
                $prevIndex = $tokens->getPrevMeaningfulToken($index);
2✔
208
                $closureStart = $tokens->getNextTokenOfKind($index, ['{']);
2✔
209
                $closureEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $closureStart);
2✔
210
                if (!$tokens[$prevIndex]->isGivenKind(\T_STATIC)) {
2✔
211
                    $this->fixReferencesInFunction($tokens, $tokensAnalyzer, $closureStart, $closureEnd, $fixedMethods);
2✔
212
                }
213

214
                $index = $closureEnd;
2✔
215

216
                continue;
2✔
217
            }
218

219
            if ($tokens[$index]->isGivenKind(\T_CLASS) && $tokensAnalyzer->isAnonymousClass($index)) {
11✔
220
                $anonymousClassOpen = $tokens->getNextTokenOfKind($index, ['{']);
3✔
221
                $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $anonymousClassOpen);
3✔
222

223
                continue;
3✔
224
            }
225

226
            if (!$tokens[$index]->equals([\T_VARIABLE, '$this'])) {
11✔
227
                continue;
11✔
228
            }
229

230
            $objectOperatorIndex = $tokens->getNextMeaningfulToken($index);
8✔
231
            if (!$tokens[$objectOperatorIndex]->isGivenKind(\T_OBJECT_OPERATOR)) {
8✔
232
                continue;
×
233
            }
234

235
            $methodNameIndex = $tokens->getNextMeaningfulToken($objectOperatorIndex);
8✔
236
            $argumentsBraceIndex = $tokens->getNextMeaningfulToken($methodNameIndex);
8✔
237
            if (!$tokens[$argumentsBraceIndex]->equals('(')) {
8✔
238
                continue;
4✔
239
            }
240

241
            $currentMethodName = $tokens[$methodNameIndex]->getContent();
8✔
242
            if (!isset($fixedMethods[$currentMethodName])) {
8✔
243
                continue;
1✔
244
            }
245

246
            $tokens[$index] = new Token([\T_STRING, 'self']);
8✔
247
            $tokens[$objectOperatorIndex] = new Token([\T_DOUBLE_COLON, '::']);
8✔
248
        }
249
    }
250

251
    /**
252
     * @return list<array{int, int, int}>
253
     */
254
    private function getClassMethods(Tokens $tokens, int $classOpen, int $classClose): array
255
    {
256
        $methods = [];
17✔
257
        for ($index = $classClose - 1; $index > $classOpen + 1; --$index) {
17✔
258
            if ($tokens[$index]->equals('}')) {
17✔
259
                $index = $tokens->findBlockStart(Tokens::BLOCK_TYPE_CURLY_BRACE, $index);
17✔
260

261
                continue;
17✔
262
            }
263

264
            if (!$tokens[$index]->isGivenKind(\T_FUNCTION)) {
17✔
265
                continue;
17✔
266
            }
267

268
            $functionKeywordIndex = $index;
17✔
269
            $prevTokenIndex = $tokens->getPrevMeaningfulToken($functionKeywordIndex);
17✔
270
            $prevPrevTokenIndex = $tokens->getPrevMeaningfulToken($prevTokenIndex);
17✔
271
            if ($tokens[$prevTokenIndex]->isGivenKind(\T_ABSTRACT) || $tokens[$prevPrevTokenIndex]->isGivenKind(\T_ABSTRACT)) {
17✔
272
                continue;
2✔
273
            }
274

275
            $methodOpen = $tokens->getNextTokenOfKind($functionKeywordIndex, ['{']);
17✔
276
            $methodClose = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $methodOpen);
17✔
277

278
            $methods[] = [$functionKeywordIndex, $methodOpen, $methodClose];
17✔
279
        }
280

281
        return $methods;
17✔
282
    }
283
}
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