Create New Item
Item Type
File
Folder
Item Name
Search file in folder and subfolders...
Are you sure want to rename?
tipuloidea
/
back
/
vendor
/
friendsofphp
/
php-cs-fixer
/
src
/
Fixer
/
Alias
:
MbStrFunctionsFixer.php
Advanced Search
Upload
New Item
Settings
Back
Back Up
Advanced Editor
Save
<?php declare(strict_types=1); /* * This file is part of PHP CS Fixer. * * (c) Fabien Potencier <fabien@symfony.com> * Dariusz RumiĆski <dariusz.ruminski@gmail.com> * * This source file is subject to the MIT license that is bundled * with this source code in the file LICENSE. */ namespace PhpCsFixer\Fixer\Alias; use PhpCsFixer\AbstractFixer; use PhpCsFixer\FixerDefinition\CodeSample; use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; use PhpCsFixer\Tokenizer\Analyzer\ArgumentsAnalyzer; use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer; use PhpCsFixer\Tokenizer\CT; use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; /** * @author Filippo Tessarotto <zoeslam@gmail.com> * * @no-named-arguments Parameter names are not covered by the backward compatibility promise. */ final class MbStrFunctionsFixer extends AbstractFixer { /** * list of the string-related function names and their mb_ equivalent. * * @var array< * string, * array{ * alternativeName: string, * argumentCount: list<int>, * }, * > */ private static array $functionsMap = [ 'str_split' => ['alternativeName' => 'mb_str_split', 'argumentCount' => [1, 2, 3]], 'stripos' => ['alternativeName' => 'mb_stripos', 'argumentCount' => [2, 3]], 'stristr' => ['alternativeName' => 'mb_stristr', 'argumentCount' => [2, 3]], 'strlen' => ['alternativeName' => 'mb_strlen', 'argumentCount' => [1]], 'strpos' => ['alternativeName' => 'mb_strpos', 'argumentCount' => [2, 3]], 'strrchr' => ['alternativeName' => 'mb_strrchr', 'argumentCount' => [2]], 'strripos' => ['alternativeName' => 'mb_strripos', 'argumentCount' => [2, 3]], 'strrpos' => ['alternativeName' => 'mb_strrpos', 'argumentCount' => [2, 3]], 'strstr' => ['alternativeName' => 'mb_strstr', 'argumentCount' => [2, 3]], 'strtolower' => ['alternativeName' => 'mb_strtolower', 'argumentCount' => [1]], 'strtoupper' => ['alternativeName' => 'mb_strtoupper', 'argumentCount' => [1]], 'substr' => ['alternativeName' => 'mb_substr', 'argumentCount' => [2, 3]], 'substr_count' => ['alternativeName' => 'mb_substr_count', 'argumentCount' => [2, 3, 4]], ]; /** * @var array< * string, * array{ * alternativeName: string, * argumentCount: list<int>, * }, * > */ private array $functions; public function __construct() { parent::__construct(); if (\PHP_VERSION_ID >= 8_03_00) { self::$functionsMap['str_pad'] = ['alternativeName' => 'mb_str_pad', 'argumentCount' => [1, 2, 3, 4]]; } if (\PHP_VERSION_ID >= 8_04_00) { self::$functionsMap['trim'] = ['alternativeName' => 'mb_trim', 'argumentCount' => [1, 2]]; self::$functionsMap['ltrim'] = ['alternativeName' => 'mb_ltrim', 'argumentCount' => [1, 2]]; self::$functionsMap['rtrim'] = ['alternativeName' => 'mb_rtrim', 'argumentCount' => [1, 2]]; } $this->functions = array_filter( self::$functionsMap, static fn (array $mapping): bool => (new \ReflectionFunction($mapping['alternativeName']))->isInternal(), ); } public function isCandidate(Tokens $tokens): bool { return $tokens->isTokenKindFound(\T_STRING); } public function isRisky(): bool { return true; } /** * {@inheritdoc} * * Must run before NativeFunctionInvocationFixer. */ public function getPriority(): int { return 2; } public function getDefinition(): FixerDefinitionInterface { return new FixerDefinition( 'Replace non multibyte-safe functions with corresponding mb function.', [ new CodeSample( <<<'PHP' <?php $a = strlen($a); $a = strpos($a, $b); $a = strrpos($a, $b); $a = substr($a, $b); $a = strtolower($a); $a = strtoupper($a); $a = stripos($a, $b); $a = strripos($a, $b); $a = strstr($a, $b); $a = stristr($a, $b); $a = strrchr($a, $b); $a = substr_count($a, $b); PHP, ), ], null, 'Risky when any of the functions are overridden, or when relying on the string byte size rather than its length in characters.', ); } protected function applyFix(\SplFileInfo $file, Tokens $tokens): void { $argumentsAnalyzer = new ArgumentsAnalyzer(); $functionsAnalyzer = new FunctionsAnalyzer(); for ($index = $tokens->count() - 1; $index > 0; --$index) { if (!$tokens[$index]->isGivenKind(\T_STRING)) { continue; } $lowercasedContent = strtolower($tokens[$index]->getContent()); if (!isset($this->functions[$lowercasedContent])) { continue; } // is it a global function call? if ($functionsAnalyzer->isGlobalFunctionCall($tokens, $index)) { $openParenthesis = $tokens->getNextMeaningfulToken($index); $closeParenthesis = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openParenthesis); $numberOfArguments = $argumentsAnalyzer->countArguments($tokens, $openParenthesis, $closeParenthesis); if (!\in_array($numberOfArguments, $this->functions[$lowercasedContent]['argumentCount'], true)) { continue; } $tokens[$index] = new Token([\T_STRING, $this->functions[$lowercasedContent]['alternativeName']]); continue; } // is it a global function import? $functionIndex = $tokens->getPrevMeaningfulToken($index); if ($tokens[$functionIndex]->isGivenKind(\T_NS_SEPARATOR)) { $functionIndex = $tokens->getPrevMeaningfulToken($functionIndex); } if (!$tokens[$functionIndex]->isGivenKind(CT::T_FUNCTION_IMPORT)) { continue; } $useIndex = $tokens->getPrevMeaningfulToken($functionIndex); if (!$tokens[$useIndex]->isGivenKind(\T_USE)) { continue; } $tokens[$index] = new Token([\T_STRING, $this->functions[$lowercasedContent]['alternativeName']]); } } }