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

PHP-CS-Fixer / PHP-CS-Fixer / 3721300657

pending completion
3721300657

push

github

GitHub
minor: Follow PSR12 ordered imports in Symfony ruleset (#6712)

9 of 9 new or added lines in 2 files covered. (100.0%)

22674 of 24281 relevant lines covered (93.38%)

39.08 hits per line

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

85.6
/src/Fixer/LanguageConstruct/FunctionToConstantFixer.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\LanguageConstruct;
16

17
use PhpCsFixer\AbstractFixer;
18
use PhpCsFixer\Fixer\ConfigurableFixerInterface;
19
use PhpCsFixer\FixerConfiguration\AllowedValueSubset;
20
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
21
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface;
22
use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
23
use PhpCsFixer\FixerDefinition\CodeSample;
24
use PhpCsFixer\FixerDefinition\FixerDefinition;
25
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
26
use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer;
27
use PhpCsFixer\Tokenizer\CT;
28
use PhpCsFixer\Tokenizer\Token;
29
use PhpCsFixer\Tokenizer\Tokens;
30

31
final class FunctionToConstantFixer extends AbstractFixer implements ConfigurableFixerInterface
32
{
33
    /**
34
     * @var array<string, Token[]>
35
     */
36
    private static $availableFunctions;
37

38
    /**
39
     * @var array<string, Token[]>
40
     */
41
    private array $functionsFixMap;
42

43
    public function __construct()
44
    {
45
        if (null === self::$availableFunctions) {
41✔
46
            self::$availableFunctions = [
×
47
                'get_called_class' => [
×
48
                    new Token([T_STATIC, 'static']),
×
49
                    new Token([T_DOUBLE_COLON, '::']),
×
50
                    new Token([CT::T_CLASS_CONSTANT, 'class']),
×
51
                ],
×
52
                'get_class' => [new Token([T_CLASS_C, '__CLASS__'])],
×
53
                'get_class_this' => [
×
54
                    new Token([T_STATIC, 'static']),
×
55
                    new Token([T_DOUBLE_COLON, '::']),
×
56
                    new Token([CT::T_CLASS_CONSTANT, 'class']),
×
57
                ],
×
58
                'php_sapi_name' => [new Token([T_STRING, 'PHP_SAPI'])],
×
59
                'phpversion' => [new Token([T_STRING, 'PHP_VERSION'])],
×
60
                'pi' => [new Token([T_STRING, 'M_PI'])],
×
61
            ];
×
62
        }
63

64
        parent::__construct();
41✔
65
    }
66

67
    /**
68
     * {@inheritdoc}
69
     */
70
    public function configure(array $configuration): void
71
    {
72
        parent::configure($configuration);
41✔
73

74
        $this->functionsFixMap = [];
41✔
75

76
        foreach ($this->configuration['functions'] as $key) {
41✔
77
            $this->functionsFixMap[$key] = self::$availableFunctions[$key];
41✔
78
        }
79
    }
80

81
    /**
82
     * {@inheritdoc}
83
     */
84
    public function getDefinition(): FixerDefinitionInterface
85
    {
86
        return new FixerDefinition(
3✔
87
            'Replace core functions calls returning constants with the constants.',
3✔
88
            [
3✔
89
                new CodeSample(
3✔
90
                    "<?php\necho phpversion();\necho pi();\necho php_sapi_name();\nclass Foo\n{\n    public function Bar()\n    {\n        echo get_class();\n        echo get_called_class();\n    }\n}\n"
3✔
91
                ),
3✔
92
                new CodeSample(
3✔
93
                    "<?php\necho phpversion();\necho pi();\nclass Foo\n{\n    public function Bar()\n    {\n        echo get_class();\n        get_class(\$this);\n        echo get_called_class();\n    }\n}\n",
3✔
94
                    ['functions' => ['get_called_class', 'get_class_this', 'phpversion']]
3✔
95
                ),
3✔
96
            ],
3✔
97
            null,
3✔
98
            'Risky when any of the configured functions to replace are overridden.'
3✔
99
        );
3✔
100
    }
101

102
    /**
103
     * {@inheritdoc}
104
     *
105
     * Must run before NativeFunctionCasingFixer, NoExtraBlankLinesFixer, NoSinglelineWhitespaceBeforeSemicolonsFixer, NoTrailingWhitespaceFixer, NoWhitespaceInBlankLineFixer, SelfStaticAccessorFixer.
106
     * Must run after NoSpacesAfterFunctionNameFixer, NoSpacesInsideParenthesisFixer.
107
     */
108
    public function getPriority(): int
109
    {
110
        return 1;
1✔
111
    }
112

113
    /**
114
     * {@inheritdoc}
115
     */
116
    public function isCandidate(Tokens $tokens): bool
117
    {
118
        return $tokens->isTokenKindFound(T_STRING);
32✔
119
    }
120

121
    /**
122
     * {@inheritdoc}
123
     */
124
    public function isRisky(): bool
125
    {
126
        return true;
1✔
127
    }
128

129
    /**
130
     * {@inheritdoc}
131
     */
132
    protected function applyFix(\SplFileInfo $file, Tokens $tokens): void
133
    {
134
        $functionAnalyzer = new FunctionsAnalyzer();
32✔
135

136
        for ($index = $tokens->count() - 4; $index > 0; --$index) {
32✔
137
            $candidate = $this->getReplaceCandidate($tokens, $functionAnalyzer, $index);
32✔
138
            if (null === $candidate) {
32✔
139
                continue;
32✔
140
            }
141

142
            $this->fixFunctionCallToConstant(
21✔
143
                $tokens,
21✔
144
                $index,
21✔
145
                $candidate[0], // brace open
21✔
146
                $candidate[1], // brace close
21✔
147
                $candidate[2]  // replacement
21✔
148
            );
21✔
149
        }
150
    }
151

152
    /**
153
     * {@inheritdoc}
154
     */
155
    protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
156
    {
157
        $functionNames = array_keys(self::$availableFunctions);
41✔
158

159
        return new FixerConfigurationResolver([
41✔
160
            (new FixerOptionBuilder('functions', 'List of function names to fix.'))
41✔
161
                ->setAllowedTypes(['array'])
41✔
162
                ->setAllowedValues([new AllowedValueSubset($functionNames)])
41✔
163
                ->setDefault([
41✔
164
                    'get_called_class',
41✔
165
                    'get_class',
41✔
166
                    'get_class_this',
41✔
167
                    'php_sapi_name',
41✔
168
                    'phpversion',
41✔
169
                    'pi',
41✔
170
                ])
41✔
171
                ->getOption(),
41✔
172
        ]);
41✔
173
    }
174

175
    /**
176
     * @param Token[] $replacements
177
     */
178
    private function fixFunctionCallToConstant(Tokens $tokens, int $index, int $braceOpenIndex, int $braceCloseIndex, array $replacements): void
179
    {
180
        for ($i = $braceCloseIndex; $i >= $braceOpenIndex; --$i) {
21✔
181
            if ($tokens[$i]->isGivenKind([T_WHITESPACE, T_COMMENT, T_DOC_COMMENT])) {
21✔
182
                continue;
7✔
183
            }
184

185
            $tokens->clearTokenAndMergeSurroundingWhitespace($i);
21✔
186
        }
187

188
        if ($replacements[0]->isGivenKind([T_CLASS_C, T_STATIC])) {
21✔
189
            $prevIndex = $tokens->getPrevMeaningfulToken($index);
10✔
190
            $prevToken = $tokens[$prevIndex];
10✔
191
            if ($prevToken->isGivenKind(T_NS_SEPARATOR)) {
10✔
192
                $tokens->clearAt($prevIndex);
4✔
193
            }
194
        }
195

196
        $tokens->clearAt($index);
21✔
197
        $tokens->insertAt($index, $replacements);
21✔
198
    }
199

200
    private function getReplaceCandidate(
201
        Tokens $tokens,
202
        FunctionsAnalyzer $functionAnalyzer,
203
        int $index
204
    ): ?array {
205
        if (!$tokens[$index]->isGivenKind(T_STRING)) {
32✔
206
            return null;
32✔
207
        }
208

209
        $lowerContent = strtolower($tokens[$index]->getContent());
32✔
210

211
        if ('get_class' === $lowerContent) {
32✔
212
            return $this->fixGetClassCall($tokens, $functionAnalyzer, $index);
9✔
213
        }
214

215
        if (!isset($this->functionsFixMap[$lowerContent])) {
30✔
216
            return null;
19✔
217
        }
218

219
        if (!$functionAnalyzer->isGlobalFunctionCall($tokens, $index)) {
24✔
220
            return null;
6✔
221
        }
222

223
        // test if function call without parameters
224
        $braceOpenIndex = $tokens->getNextMeaningfulToken($index);
18✔
225
        if (!$tokens[$braceOpenIndex]->equals('(')) {
18✔
226
            return null;
×
227
        }
228

229
        $braceCloseIndex = $tokens->getNextMeaningfulToken($braceOpenIndex);
18✔
230
        if (!$tokens[$braceCloseIndex]->equals(')')) {
18✔
231
            return null;
3✔
232
        }
233

234
        return $this->getReplacementTokenClones($lowerContent, $braceOpenIndex, $braceCloseIndex);
15✔
235
    }
236

237
    private function fixGetClassCall(
238
        Tokens $tokens,
239
        FunctionsAnalyzer $functionAnalyzer,
240
        int $index
241
    ): ?array {
242
        if (!isset($this->functionsFixMap['get_class']) && !isset($this->functionsFixMap['get_class_this'])) {
9✔
243
            return null;
×
244
        }
245

246
        if (!$functionAnalyzer->isGlobalFunctionCall($tokens, $index)) {
9✔
247
            return null;
1✔
248
        }
249

250
        $braceOpenIndex = $tokens->getNextMeaningfulToken($index);
8✔
251
        $braceCloseIndex = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $braceOpenIndex);
8✔
252

253
        if ($braceCloseIndex === $tokens->getNextMeaningfulToken($braceOpenIndex)) { // no arguments passed
8✔
254
            if (isset($this->functionsFixMap['get_class'])) {
4✔
255
                return $this->getReplacementTokenClones('get_class', $braceOpenIndex, $braceCloseIndex);
4✔
256
            }
257
        } elseif (isset($this->functionsFixMap['get_class_this'])) {
7✔
258
            $isThis = false;
7✔
259

260
            for ($i = $braceOpenIndex + 1; $i < $braceCloseIndex; ++$i) {
7✔
261
                if ($tokens[$i]->equalsAny([[T_WHITESPACE], [T_COMMENT], [T_DOC_COMMENT], ')'])) {
7✔
262
                    continue;
3✔
263
                }
264

265
                if ($tokens[$i]->isGivenKind(T_VARIABLE) && '$this' === strtolower($tokens[$i]->getContent())) {
7✔
266
                    $isThis = true;
6✔
267

268
                    continue;
6✔
269
                }
270

271
                if (false === $isThis && $tokens[$i]->equals('(')) {
4✔
272
                    continue;
1✔
273
                }
274

275
                $isThis = false;
3✔
276

277
                break;
3✔
278
            }
279

280
            if ($isThis) {
7✔
281
                return $this->getReplacementTokenClones('get_class_this', $braceOpenIndex, $braceCloseIndex);
5✔
282
            }
283
        }
284

285
        return null;
4✔
286
    }
287

288
    private function getReplacementTokenClones(string $lowerContent, int $braceOpenIndex, int $braceCloseIndex): array
289
    {
290
        $clones = [];
21✔
291
        foreach ($this->functionsFixMap[$lowerContent] as $token) {
21✔
292
            $clones[] = clone $token;
21✔
293
        }
294

295
        return [
21✔
296
            $braceOpenIndex,
21✔
297
            $braceCloseIndex,
21✔
298
            $clones,
21✔
299
        ];
21✔
300
    }
301
}
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

© 2025 Coveralls, Inc