最近在迁徙一个上古我的项目到 laravel 中。我这边的做法是先用 rector 做一个整体初步的语法降级与 laravel 写法的替换,而后次要就是手动重写数据操作的局部。到目前为止除了利用到 rector 自带的规定外,还写了一些自定义的规定,其中有一个规范化命名格调的规定(RenameToPsrNameRector)实用于所有的 PHP 我的项目,所以在此分享进去。该规定次要是针对常量、变量、函数、类、属性、办法等命名进行对立的标准。其中,常量名遵循大写蛇形命名格调,函数名遵循小写蛇形命名格调,类名遵循大驼峰命名格调,变量名、属性名、办法名遵循小驼峰命名格调。

成果

 <?php // lower snake-function functionName(){}-functionName();-call_user_func('functionName');-call_user_func_array('functionName');-function_exists('functionName');+function function_name(){}+\function_name();+call_user_func('function_name');+call_user_func_array('function_name');+function_exists('function_name'); // ucfirst camel-class class_name{}-enum enum_name{}-enum Enum{case case_name;}-interface interface_name{}-trait trait_name{}-class Foo extends class_name implements interface_name{}-class_name::$property;-class_name::CONST;-class_name::method();-enum Enum implements interface_name{}-use class_name;-use trait_name;-class_alias('class_name', 'alias_class_name');-class_exists('class_name');-class_implements('class_name');-class_parents('class_name');-class_uses('class_name');-enum_exists('enum_name');-get_class_methods('class_name');-get_class_vars('class_name');-get_parent_class('class_name');-interface_exists('interface_name');-is_subclass_of('class_name', 'parent_class_name');-trait_exists('trait_name', true);+class ClassName{}+enum EnumName{}+enum Enum{case CaseName;}+interface InterfaceName{}+trait TraitName{}+class Foo extends \ClassName implements \InterfaceName{}+\ClassName::$property;+\ClassName::CONST;+\ClassName::method();+enum Enum implements \InterfaceName{}+use ClassName;+use TraitName;+class_alias('ClassName', 'AliasClassName');+class_exists('ClassName');+class_implements('ClassName');+class_parents('ClassName');+class_uses('ClassName');+enum_exists('EnumName');+get_class_methods('ClassName');+get_class_vars('ClassName');+get_parent_class('ClassName');+interface_exists('InterfaceName');+is_subclass_of('ClassName', 'ParentClassName');+trait_exists('TraitName', true); // upper snake-class Foo{public const constName = 'const';}-Foo::constName;-define('constName', 'const');-defined('constName');-constant('constName');+class Foo{public const CONST_NAME = 'const';}+Foo::CONST_NAME;+define('CONST_NAME', 'const');+defined('CONST_NAME');+constant('CONST_NAME'); constant('Foo::constName');-constName;+\CONST_NAME; // lcfirst camel-$var_name;-$object->method_name();-$object->property_name;-call_user_method('method_name', $object);-call_user_method_array('method_name', $object);-class Foo{public $property_name;}-class Foo{public function method_name(){}}-class Foo{public int $property_name;}-Foo::$property_name;-Foo::method_name();-method_exists($object, 'method_name');-property_exists($object, 'property_name');+$varName;+$object->methodName();+$object->propertyName;+call_user_method('methodName', $object);+call_user_method_array('methodName', $object);+class Foo{public $propertyName;}+class Foo{public function methodName(){}}+class Foo{public int $propertyName;}+Foo::$propertyName;+Foo::methodName();+method_exists($object, 'methodName');+property_exists($object, 'propertyName');

规定(RenameToPsrNameRector)

<?phpnamespace App\Support\Rectors;use Illuminate\Support\Str;use PhpParser\Node;use PhpParser\Node\Expr\FuncCall;use PhpParser\Node\Name;use Rector\Core\Contract\Rector\ConfigurableRectorInterface;use Rector\Core\Rector\AbstractRector;use RectorPrefix202305\Webmozart\Assert\Assert;use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;class RenameToPsrNameRector extends AbstractRector implements ConfigurableRectorInterface{    /**     * @var array<string>     */    protected $except = [        '*::*',        'class',        'false',        'null',        'stdClass',        'true',    ];    public function getRuleDefinition(): RuleDefinition    {        return new RuleDefinition(            'Rename to psr name',            [                new CodeSample(                    <<<'CODE_SAMPLE'// lower snakefunction functionName(){}functionName();call_user_func('functionName');call_user_func_array('functionName');function_exists('functionName');// ucfirst camelclass class_name{}enum enum_name{}enum Enum{case case_name;}interface interface_name{}trait trait_name{}class Foo extends class_name implements interface_name{}class_name::$property;class_name::CONST;class_name::method();enum Enum implements interface_name{}use class_name;use trait_name;class_alias('class_name', 'alias_class_name');class_exists('class_name');class_implements('class_name');class_parents('class_name');class_uses('class_name');enum_exists('enum_name');get_class_methods('class_name');get_class_vars('class_name');get_parent_class('class_name');interface_exists('interface_name');is_subclass_of('class_name', 'parent_class_name');trait_exists('trait_name', true);// upper snakeclass Foo{public const constName = 'const';}Foo::constName;define('constName', 'const');defined('constName');constant('constName');constant('Foo::constName');constName;// lcfirst camel$var_name;$object->method_name();$object->property_name;call_user_method('method_name', $object);call_user_method_array('method_name', $object);class Foo{public $property_name;}class Foo{public function method_name(){}}class Foo{public int $property_name;}Foo::$property_name;Foo::method_name();method_exists($object, 'method_name');property_exists($object, 'property_name');CODE_SAMPLE                    ,                    <<<'CODE_SAMPLE'// lower snakefunction function_name(){}function_name();call_user_func('function_name');call_user_func_array('function_name');function_exists('function_name');// ucfirst camelclass ClassName{}enum EnumName{}enum Enum{case CaseName;}interface InterfaceName{}trait TraitName{}class Foo extends ClassName implements InterfaceName{}ClassName::$property;ClassName::CONST;ClassName::method();enum Enum implements InterfaceName{}use ClassName;use TraitName;class_alias('ClassName', 'AliasClassName');class_exists('ClassName');class_implements('ClassName');class_parents('ClassName');class_uses('ClassName');enum_exists('EnumName');get_class_methods('ClassName');get_class_vars('ClassName');get_parent_class('ClassName');interface_exists('InterfaceName');is_subclass_of('ClassName', 'ParentClassName');trait_exists('TraitName', true);// upper snakeclass Foo{public const CONST_NAME = 'const';}Foo::CONST_NAME;define('CONST_NAME', 'const');defined('CONST_NAME');constant('CONST_NAME');constant('Foo::CONST_NAME');CONST_NAME;// lcfirst camel$varName$object->methodName();$object->propertyName;class Foo{public $propertyName;}class Foo{public function methodName(){}}class Foo{public int $propertyName;}Foo::$propertyName;Foo::methodName();call_user_method('methodName', $object);call_user_method_array('methodName', $object);method_exists($object, 'methodName');property_exists($object, 'propertyName');CODE_SAMPLE                ),            ]);    }    /**     * {@inheritDoc}     */    public function getNodeTypes(): array    {        return [            \PhpParser\Node\Name::class,            \PhpParser\Node\Expr\FuncCall::class,            \PhpParser\Node\Expr\Variable::class,            \PhpParser\Node\Identifier::class,        ];    }    /**     * @param  \PhpParser\Node\Name|\PhpParser\Node\Expr\FuncCall|\PhpParser\Node\Expr\Variable|\PhpParser\Node\Identifier  $node     */    public function refactor(Node $node)    {        try {            if ($this->shouldLowerSnakeName($node)) {                return $this->rename($node, static fn (string $name): string => Str::lower(Str::snake($name)));            }            if ($this->shouldUcfirstCamelName($node)) {                return $this->rename($node, static fn (string $name): string => Str::ucfirst(Str::camel($name)));            }            if ($this->shouldUpperSnakeName($node)) {                return $this->rename($node, static fn (string $name): string => Str::upper(Str::snake($name)));            }            if ($this->shouldLcfirstCamelName($node)) {                return $this->rename($node, static fn (string $name): string => Str::lcfirst(Str::camel($name)));            }        } catch (\RuntimeException $e) {            // skip        }        return null;    }    /**     * @param  \PhpParser\Node\Name|\PhpParser\Node\Expr\FuncCall|\PhpParser\Node\Expr\Variable|\PhpParser\Node\Identifier  $node     */    protected function rename(Node $node, callable $renamer): Node    {        $preprocessor = function (string $value): string {            if ($this->is($this->except, $value)) {                throw new \RuntimeException("The name[$value] is skipped.");            }            if (ctype_upper(preg_replace('/[^a-zA-Z]/', '', $value))) {                return mb_strtolower($value, 'UTF-8');            }            return $value;        };        if ($node instanceof Name) {            $node->parts[count($node->parts) - 1] = $renamer($preprocessor($node->parts[count($node->parts) - 1]));            return $node;        }        if (            $this->isSubclasses($node, [                Node\Expr\Variable::class,                Node\Identifier::class,            ])        ) {            $node->name = $renamer($preprocessor($node->name));            return $node;        }        if ($node instanceof FuncCall) {            if (                $this->isNames($node, [                    'call_user_func',                    'call_user_func_array',                    'call_user_method',                    'call_user_method_array',                    'class_alias',                    'class_exists',                    'class_implements',                    'class_parents',                    'class_uses',                    'constant',                    'define',                    'defined',                    'enum_exists',                    'function_exists',                    'get_class_methods',                    'get_class_vars',                    'get_parent_class',                    'interface_exists',                    'is_subclass_of',                    'trait_exists',                ])                && $this->hasFuncCallIndexStringArg($node, 0)            ) {                $node->args[0]->value->value = $renamer($preprocessor($node->args[0]->value->value));            }            if (                $this->isNames($node, [                    'class_alias',                    'is_subclass_of',                    'method_exists',                    'property_exists',                ])                && $this->hasFuncCallIndexStringArg($node, 1)            ) {                $node->args[1]->value->value = $renamer($preprocessor($node->args[1]->value->value));            }        }        return $node;    }    /**     * @param  \PhpParser\Node\Name|\PhpParser\Node\Expr\FuncCall|\PhpParser\Node\Expr\Variable|\PhpParser\Node\Identifier  $node     */    protected function shouldLowerSnakeName(Node $node): bool    {        $parent = $node->getAttribute('parent');        // function function_name(){}        if ($node instanceof Node\Identifier && $parent instanceof Node\Stmt\Function_) {            return true;        }        // function_name();        if ($node instanceof Node\Name && $parent instanceof FuncCall) {            return true;        }        if (            $node instanceof FuncCall            && $this->isNames($node, [                // function_exists('function_name');                'function_exists',                // call_user_func('function_name');                'call_user_func',                // call_user_func_array('function_name');                'call_user_func_array',            ])            && $this->hasFuncCallIndexStringArg($node, 0)        ) {            return true;        }        return false;    }    /**     * @param  \PhpParser\Node\Name|\PhpParser\Node\Expr\FuncCall|\PhpParser\Node\Expr\Variable|\PhpParser\Node\Identifier  $node     */    protected function shouldUcfirstCamelName(Node $node): bool    {        $parent = $node->getAttribute('parent');        if (            $node instanceof Node\Identifier            && $this->isSubclasses($parent, [                // interface InterfaceName{}                Node\Stmt\Interface_::class,                // class ClassName{}                Node\Stmt\Class_::class,                // trait TraitName{}                Node\Stmt\Trait_::class,                // enum EnumName{}                Node\Stmt\Enum_::class,                // enum Enum{case CaseName;}                Node\Stmt\EnumCase::class,            ])        ) {            return true;        }        if (            $node instanceof Node\Name            && ! $this->isName($node, 'stdClass')            && $this->isSubclasses($parent, [                // ClassName::CONST;                Node\Expr\ClassConstFetch::class,                // ClassName::$property;                Node\Expr\StaticPropertyFetch::class,                // ClassName::method();                Node\Expr\StaticCall::class,                // class Foo extends ClassName implements InterfaceName{}                Node\Stmt\Class_::class,                // enum Enum implements InterfaceName{}                Node\Stmt\Enum_::class,                // use ClassName;                Node\Stmt\UseUse::class,                // use TraitName;                Node\Stmt\TraitUse::class,            ])        ) {            return true;        }        if ($node instanceof FuncCall) {            if (                $this->isNames($node, [                    // class_alias('ClassName', 'AliasClassName');                    'class_alias',                    // class_exists('ClassName');                    'class_exists',                    // class_implements('ClassName');                    'class_implements',                    // class_parents('ClassName');                    'class_parents',                    // class_uses('ClassName');                    'class_uses',                    // enum_exists('EnumName');                    'enum_exists',                    // get_class_methods('ClassName');                    'get_class_methods',                    // get_class_vars('ClassName');                    'get_class_vars',                    // get_parent_class('ClassName');                    'get_parent_class',                    // interface_exists('InterfaceName');                    'interface_exists',                    // is_subclass_of('ClassName', 'ParentClassName');                    'is_subclass_of',                    // trait_exists('TraitName', true);                    'trait_exists',                ])                && $this->hasFuncCallIndexStringArg($node, 0)            ) {                return true;            }            if (                $this->isNames($node, [                    // class_alias('ClassName', 'AliasClassName');                    'class_alias',                    // is_subclass_of('ClassName', 'ParentClassName');                    'is_subclass_of',                ])                && $this->hasFuncCallIndexStringArg($node, 1)            ) {                return true;            }        }        return false;    }    /**     * @param  \PhpParser\Node\Name|\PhpParser\Node\Expr\FuncCall|\PhpParser\Node\Expr\Variable|\PhpParser\Node\Identifier  $node     */    protected function shouldUpperSnakeName(Node $node): bool    {        $parent = $node->getAttribute('parent');        if (            $node instanceof Node\Identifier            && ! $this->isName($node, 'class')            && $this->isSubclasses($parent, [                // class Foo{public const CONST_NAME = 'const';}                Node\Const_::class,                // Foo::CONST_NAME;                Node\Expr\ClassConstFetch::class,            ])        ) {            return true;        }        if (            $node instanceof FuncCall            && $this->isNames($node, [                // define('CONST_NAME', 'const');                'define',                // defined('CONST_NAME');                'defined',                // constant('Foo::CONST_NAME');                'constant',            ])            && $this->hasFuncCallIndexStringArg($node, 0)        ) {            return true;        }        // CONST_NAME;        if (            $node instanceof Name            && ! $this->isNames($node, ['null', 'true', 'false'])            && $parent instanceof Node\Expr\ConstFetch        ) {            return true;        }        return false;    }    /**     * @param  \PhpParser\Node\Name|\PhpParser\Node\Expr\FuncCall|\PhpParser\Node\Expr\Variable|\PhpParser\Node\Identifier  $node     */    protected function shouldLcfirstCamelName(Node $node): bool    {        // $varName;        if ($node instanceof Node\Expr\Variable) {            return true;        }        if (            $node instanceof Node\Identifier            && $this->isSubclasses($node->getAttribute('parent'), [                // class Foo{public $propertyName;}                Node\Stmt\Property::class,                // class Foo{public int $propertyName;}                Node\Stmt\PropertyProperty::class,                // class Foo{public function methodName(){}}                Node\Stmt\ClassMethod::class,                // $object->propertyName;                Node\Expr\PropertyFetch::class,                // Foo::$propertyName;                Node\Expr\StaticPropertyFetch::class,                // $object->methodName();                Node\Expr\MethodCall::class,                // Foo::methodName();                Node\Expr\StaticCall::class,            ])        ) {            return true;        }        if ($node instanceof FuncCall) {            if (                $this->isNames($node, [                    // call_user_method('methodName', $object);                    'call_user_method',                    // call_user_method_array('methodName', $object);                    'call_user_method_array',                ])                && $this->hasFuncCallIndexStringArg($node, 0)            ) {                return true;            }            if (                $this->isNames($node, [                    // method_exists($object, 'methodName');                    'method_exists',                    // property_exists($object, 'propertyName');                    'property_exists',                ])                && $this->hasFuncCallIndexStringArg($node, 1)            ) {                return true;            }        }        return false;    }    protected function isSubclasses($object, array $classes): bool    {        if (! is_object($object)) {            return false;        }        foreach ($classes as $class) {            if ($object instanceof $class) {                return true;            }        }        return false;    }    /**     * @param  string|iterable<string>  $patterns     * @param  string  $value     */    public function is($patterns, $value): bool    {        $value = (string) $value;        if (! is_iterable($patterns)) {            $patterns = [$patterns];        }        foreach ($patterns as $pattern) {            $pattern = (string) $pattern;            if ($pattern === $value) {                return true;            }            $pattern = preg_quote($pattern, '#');            $pattern = str_replace('\*', '.*', $pattern);            if (preg_match('#^'.$pattern.'\z#u', $value) === 1) {                return true;            }        }        return false;    }    protected function hasFuncCallIndexStringArg(FuncCall $funcCall, int $index): bool    {        return isset($funcCall->args[$index])            && $funcCall->args[$index]->name === null            && $funcCall->args[$index]->value instanceof Node\Scalar\String_;    }    protected function hasFuncCallNameStringArg(FuncCall $funcCall, string $name): bool    {        foreach ($funcCall->args as $arg) {            if (                $arg->name instanceof Node\Identifier                && $arg->name->name === $name                && $arg->value instanceof Node\Scalar\String_) {                return true;            }        }        return false;    }    public function configure(array $configuration): void    {        Assert::allStringNotEmpty($configuration);        $this->except = [...$this->except, ...$configuration];    }}

应用

rector 配置文件中配置该规定即可

<?phpdeclare(strict_types=1);use Rector\Config\RectorConfig;return static function (RectorConfig $rectorConfig): void {    ...    $rectorConfig->ruleWithConfiguration(\App\Support\Rectors\RenameToPsrNameRector::class, [        'exceptName',    ]);    ...};

参考链接

  • https://github.com/rectorphp/rector
  • https://getrector.com/documentation

原文链接

  • https://github.com/guanguans/guanguans.github.io/issues/53