最近在迁移一个上古项目到 laravel 中。我这边的做法是先用 rector 做一个整体初步的语法升级与 laravel 写法的替换,然后主要就是手动重写数据操作的部分。到目前为止除了应用到 rector 自带的规则外,还写了一些自定义的规则,其中有一个规范化命名风格的规则(RenameToPsrNameRector)适用于所有的 PHP 项目,所以在此分享出来。该规则主要是针对常量、变量、函数、类、属性、方法等命名进行统一的规范。其中,常量名遵循大写蛇形命名风格,函数名遵循小写蛇形命名风格,类名遵循大驼峰命名风格,变量名、属性名、方法名遵循小驼峰命名风格。
效果
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 AppSupportRectors;
use IlluminateSupportStr;
use PhpParserNode;
use PhpParserNodeExprFuncCall;
use PhpParserNodeName;
use RectorCoreContractRectorConfigurableRectorInterface;
use RectorCoreRectorAbstractRector;
use RectorPrefix202305WebmozartAssertAssert;
use SymplifyRuleDocGeneratorValueObjectCodeSampleCodeSample;
use SymplifyRuleDocGeneratorValueObjectRuleDefinition;
class RenameToPsrNameRector extends AbstractRector implements ConfigurableRectorInterface
{
/**
* @var array
*/
protected $except = [
'*::*',
'class',
'false',
'null',
'stdClass',
'true',
];
public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition(
'Rename to psr name',
[
new CodeSample(
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
,
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 [
PhpParserNodeName::class,
PhpParserNodeExprFuncCall::class,
PhpParserNodeExprVariable::class,
PhpParserNodeIdentifier::class,
];
}
/**
* @param PhpParserNodeName|PhpParserNodeExprFuncCall|PhpParserNodeExprVariable|PhpParserNodeIdentifier $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 PhpParserNodeName|PhpParserNodeExprFuncCall|PhpParserNodeExprVariable|PhpParserNodeIdentifier $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, [
NodeExprVariable::class,
NodeIdentifier::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 PhpParserNodeName|PhpParserNodeExprFuncCall|PhpParserNodeExprVariable|PhpParserNodeIdentifier $node
*/
protected function shouldLowerSnakeName(Node $node): bool
{
$parent = $node->getAttribute('parent');
// function function_name(){}
if ($node instanceof NodeIdentifier && $parent instanceof NodeStmtFunction_) {
return true;
}
// function_name();
if ($node instanceof NodeName && $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 PhpParserNodeName|PhpParserNodeExprFuncCall|PhpParserNodeExprVariable|PhpParserNodeIdentifier $node
*/
protected function shouldUcfirstCamelName(Node $node): bool
{
$parent = $node->getAttribute('parent');
if (
$node instanceof NodeIdentifier
&& $this->isSubclasses($parent, [
// interface InterfaceName{}
NodeStmtInterface_::class,
// class ClassName{}
NodeStmtClass_::class,
// trait TraitName{}
NodeStmtTrait_::class,
// enum EnumName{}
NodeStmtEnum_::class,
// enum Enum{case CaseName;}
NodeStmtEnumCase::class,
])
) {
return true;
}
if (
$node instanceof NodeName
&& ! $this->isName($node, 'stdClass')
&& $this->isSubclasses($parent, [
// ClassName::CONST;
NodeExprClassConstFetch::class,
// ClassName::$property;
NodeExprStaticPropertyFetch::class,
// ClassName::method();
NodeExprStaticCall::class,
// class Foo extends ClassName implements InterfaceName{}
NodeStmtClass_::class,
// enum Enum implements InterfaceName{}
NodeStmtEnum_::class,
// use ClassName;
NodeStmtUseUse::class,
// use TraitName;
NodeStmtTraitUse::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 PhpParserNodeName|PhpParserNodeExprFuncCall|PhpParserNodeExprVariable|PhpParserNodeIdentifier $node
*/
protected function shouldUpperSnakeName(Node $node): bool
{
$parent = $node->getAttribute('parent');
if (
$node instanceof NodeIdentifier
&& ! $this->isName($node, 'class')
&& $this->isSubclasses($parent, [
// class Foo{public const CONST_NAME = 'const';}
NodeConst_::class,
// Foo::CONST_NAME;
NodeExprClassConstFetch::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 NodeExprConstFetch
) {
return true;
}
return false;
}
/**
* @param PhpParserNodeName|PhpParserNodeExprFuncCall|PhpParserNodeExprVariable|PhpParserNodeIdentifier $node
*/
protected function shouldLcfirstCamelName(Node $node): bool
{
// $varName;
if ($node instanceof NodeExprVariable) {
return true;
}
if (
$node instanceof NodeIdentifier
&& $this->isSubclasses($node->getAttribute('parent'), [
// class Foo{public $propertyName;}
NodeStmtProperty::class,
// class Foo{public int $propertyName;}
NodeStmtPropertyProperty::class,
// class Foo{public function methodName(){}}
NodeStmtClassMethod::class,
// $object->propertyName;
NodeExprPropertyFetch::class,
// Foo::$propertyName;
NodeExprStaticPropertyFetch::class,
// $object->methodName();
NodeExprMethodCall::class,
// Foo::methodName();
NodeExprStaticCall::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 $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 NodeScalarString_;
}
protected function hasFuncCallNameStringArg(FuncCall $funcCall, string $name): bool
{
foreach ($funcCall->args as $arg) {
if (
$arg->name instanceof NodeIdentifier
&& $arg->name->name === $name
&& $arg->value instanceof NodeScalarString_) {
return true;
}
}
return false;
}
public function configure(array $configuration): void
{
Assert::allStringNotEmpty($configuration);
$this->except = [...$this->except, ...$configuration];
}
}
使用
rector 配置文件中配置该规则即可
ruleWithConfiguration(AppSupportRectorsRenameToPsrNameRector::class, [
'exceptName',
]);
...
};
参考链接
- https://github.com/rectorphp/rector
- https://getrector.com/documentation
原文链接
- https://github.com/guanguans/guanguans.github.io/issues/53