统一规范化代码的命名风格

最近在迁移一个上古项目到 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)

<?php

namespace App\Support\Rectors;

use Illuminate\Support\Str;
use PhpParser\Node;
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',
        'self',
        'static',
        'stdClass',
        'true',
    ];

    public function getRuleDefinition(): RuleDefinition
    {
        return new RuleDefinition(
            'Rename to psr name',
            [
                new CodeSample(
                    <<<'CODE_SAMPLE'
// lower snake
function functionName(){}
functionName();
call_user_func('functionName');
call_user_func_array('functionName');
function_exists('functionName');

// 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);

// upper snake
class 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 snake
function function_name(){}
function_name();
call_user_func('function_name');
call_user_func_array('function_name');
function_exists('function_name');

// ucfirst camel
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 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 [
            Node\Expr\FuncCall::class,
            Node\Expr\Variable::class,
            Node\Identifier::class,
            Node\Name::class,
        ];
    }

    /**
     * @param  Node\Expr\FuncCall|Node\Expr\Variable|Node\Identifier|Node\Name  $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  Node\Expr\FuncCall|Node\Expr\Variable|Node\Identifier|Node\Name  $node
     */
    protected function rename(Node $node, callable $renamer): Node
    {
        $preprocessor = function (string $name): string {
            if ($this->isMatches($name, $this->except)) {
                throw new \RuntimeException("The name[$name] is skipped.");
            }

            if (ctype_upper(preg_replace('/[^a-zA-Z]/', '', $name))) {
                return mb_strtolower($name, 'UTF-8');
            }

            return $name;
        };

        if ($node instanceof Node\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 Node\Expr\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  Node\Expr\FuncCall|Node\Expr\Variable|Node\Identifier|Node\Name  $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 Node\Expr\FuncCall) {
            return true;
        }

        if (
            $node instanceof Node\Expr\FuncCall
            && $this->isNames($node, [
                // call_user_func('function_name');
                'call_user_func',
                // call_user_func_array('function_name');
                'call_user_func_array',
                // function_exists('function_name');
                'function_exists',
            ])
            && $this->hasFuncCallIndexStringArg($node, 0)
        ) {
            return true;
        }

        return false;
    }

    /**
     * @param  Node\Expr\FuncCall|Node\Expr\Variable|Node\Identifier|Node\Name  $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, [
                // 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,
                // ClassName::CONST;
                Node\Expr\ClassConstFetch::class,
                // ClassName::$property;
                Node\Expr\StaticPropertyFetch::class,
                // ClassName::method();
                Node\Expr\StaticCall::class,
            ])
        ) {
            return true;
        }

        if ($node instanceof Node\Expr\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  Node\Expr\FuncCall|Node\Expr\Variable|Node\Identifier|Node\Name  $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 Node\Expr\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 Node\Name
            && ! $this->isNames($node, ['null', 'true', 'false'])
            && $parent instanceof Node\Expr\ConstFetch
        ) {
            return true;
        }

        return false;
    }

    /**
     * @param  Node\Expr\FuncCall|Node\Expr\Variable|Node\Identifier|Node\Name  $node
     */
    protected function shouldLcfirstCamelName(Node $node): bool
    {
        // $varName;
        if ($node instanceof Node\Expr\Variable && is_string($node->name)) {
            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 Node\Expr\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  $value
     * @param  string|iterable<string>  $patterns
     */
    public function isMatches($value, $patterns): 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(Node\Expr\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(Node\Expr\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 配置文件中配置该规则即可

<?php

declare(strict_types=1);

use Rector\Config\RectorConfig;

return static function (RectorConfig $rectorConfig): void {
    ...
    $rectorConfig->ruleWithConfiguration(\App\Support\Rectors\RenameToPsrNameRector::class, [
        'exceptName',
    ]);
    ...
};

参考链接

原文链接

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容