RectorによるPHPバージョンアップ
この記事は?
PHP7.4(7.3) → 8.1へのバージョンアップ対応時にRectorを使用しての自動リファクタリングを行った際の知見を保存しておくための記事になります。
このドキュメントではRectorを初めて触る方への導入部分のみを記載しています。
より詳しく知りたいと思った方は公式のドキュメントを参照してください。
Rectorとは?
Rectorとは、アプリケーションの PHP コードを即座にアップグレードおよびリファクタリングするためのツールになります。
Rectorを使用することで、次期バージョンで非推奨となる機能を排除したり、新規に追加された機能を使用したり、置き換えたりすることを自動でおこなってくれます。
また、Rectorにはリファクタリングの機能も有しており、コード品質を高く保ち続けることができます。
これにより常に最新のPHPバージョンの情報を追い続ける必要がなくなり、よりビジネスロジックの実装に時間をかけることができるようになります。
仕組みとしては、内部的にPHP-Parserによるコード抽出構文ツリー(AST)によるコード解析を行っています。
セットアップ
Rectorのインストール
composerによるパッケージをインストールします。
(開発環境でしか使用しないため、—-dev
オプションをつけてください)
$composer require rector/rector --dev
設定ファイルを配置
基本はルートディレクトリにrector.php
を設置し、内容を以下のように変更します。
(別ディレクトリに別ファイル名で作成することも可能です)
※各行のn:
の番号は各行の行数を表しています
use Rector\Config\RectorConfig;
use Rector\Set\ValueObject\SetList;
use Rector\TypeDeclaration\Rector\Property\TypedPropertyFromStrictConstructorRector;
return static function (RectorConfig $rectorConfig): void {
// register single rule
$rectorConfig->rule(TypedPropertyFromStrictConstructorRector::class);
// here we can define, what sets of rules will be applied
// tip: use "SetList" class to autocomplete sets with your IDE
$rectorConfig->sets([
SetList::CODE_QUALITY
]);
};
コードの解説
- L5:
return static function (RectorConfig $rectorConfig): void {}
{}
内にRectorの設定値を記述していきます。
RectorConfig
クラスに設定に関するメソッドが実装されています。 - L7:
$rectorConfig->rule(TypedPropertyFromStrictConstructorRector::class);
自動リファクタにTypedPropertyFromStrictConstructorRector
ルールを適用する - L11~L13:
$rectorConfig->sets([SetList::CODE_QUALITY]);
複数のルールがまとめてセットされているCODE_QUALITY
のルールを適用する
自動リファクタの実行
$vendor/bin/rector process src # ディレクトリ
$vendor/bin/rector process index.php # 個別ファイル
$vendor/bin/rector process index.php index2.php # 個別ファイル(複数)
$vendor/bin/rector process index.php index2.php --dry-run # 実行前確認
PHP7.4 → 8.1への自動リファクタリング
設定ファイルの作成
ルートディレクトリに/rector
ディレクトリを作成し、/rector
ディレクトリ配下にrector.php
ファイルを作成
rector.php
の中を下記の内容に置き換えます。
use Rector\CodeQuality\Rector\ClassMethod\OptionalParametersAfterRequiredRector;
use Rector\CodeQuality\Rector\Ternary\ArrayKeyExistsTernaryThenValueToCoalescingRector;
use Rector\Config\RectorConfig;
use Rector\Core\ValueObject\PhpVersion;
use Rector\Php71\Rector\FuncCall\RemoveExtraParametersRector;
use Rector\Php73\Rector\FuncCall\JsonThrowOnErrorRector;
use Rector\Php54\Rector\Array_\LongArrayToShortArrayRector;
use Rector\Php74\Rector\Ternary\ParenthesizeNestedTernaryRector;
use Rector\Set\ValueObject\LevelSetList;
return static function (RectorConfig $rectorConfig): void {
$rectorConfig->paths([__DIR__]);
// define sets of rules
$rectorConfig->sets([
LevelSetList::UP_TO_PHP_81,
]);
$rectorConfig->rules([
ParenthesizeNestedTernaryRector::class, // 三項演算子のネスト
ArrayKeyExistsTernaryThenValueToCoalescingRector::class, // array_key_exists()対応
]);
$rectorConfig->phpVersion(PhpVersion::PHP_81);
$rectorConfig->skip([
'**/tmp/*',
'**/css/*',
'**/js/*',
'**/data/*',
'**/doc/*',
'**/guide/*',
'**/images/*',
'**/files/*',
'**/sp/*',
'**/tpl/*',
'**/tests/*',
'**/test/*',
'**/smarty*',
'**/vendor/*',
'**/lp/**',
'**/rector/**',
'**/View/**',
'lib/classes/MPO/PDF/tcpdf/*',
'/lib/emojiCfgData/*',
'/lib/Google/*',
'/lib/PHPWord/*',
'/lib/royalcanin/*',
LongArrayToShortArrayRector::class, // array() -> []への変換(php-cs-fixer側でリファクタを行う)
RemoveExtraParametersRector::class, // function method parameter remove
JsonThrowOnErrorRector::class, // json_encode()またはjson_decode()にJSON_THROW_ON_ERRORオプションを渡さない
OptionalParametersAfterRequiredRector::class, // デフォルト引数の順番を変更するルール
]);
};
コードの解説
- L12:
$rectorConfig->paths([__DIR__]);
リファクタを適用するパスを設定する - L14~L16:
$rectorConfig->sets([LevelSetList::UP_TO_PHP_81]);
PHP8.1へのバージョンアップをルールをまとめて適用する - L17~L20:
$rectorConfig->rules([…]);
個別のルールを適用する - L21:
$rectorConfig->phpVersion(PhpVersion::PHP_81);
実行するPHPバージョンを指定する(リファクタリング後のPHP実行バージョン) - L23~L50:
$rectorConfig->skip([...]);
特定のファイルやディレクトリ、ルールを除去する
ルール一覧
rector/rector_rules_overview.md at main · rectorphp/rector
独自のリファクタリングルールを作成したい
Rectorには事前に用意されているルールだけでなく、独自のルールを作成し自動リファクタリングを行うことができます。
例として、set
から始まるメソッド名(setPassword()
)をchange
から始まるメソッド名(changePassword()
)に変更したい場合のリファクタリングを想定します。
- 独自ルールの作成
AbstractRactor
を継承した独自クラスを作成します。最低限実装しなければいけないメソッドはgetNodeTypes()
とrector()
になります。
2つのメソッドで何を実装すべきかはコード解説の方で解説しています。(MyRuleRector.php
はプロジェクトルートのrector/Rules/
配下に作成します。)
namespace Rector\Rules;
use PhpParser\Node;
use PhpParser\Node\Identifier;
use PhpParser\Node\Expr\MethodCall;
use Rector\Core\Rector\AbstractRector;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
final class MyRuleRector extends AbstractRector
{
public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition(
'Change method calls from set* to change*.', [
new CodeSample(
// code before
'$user->setPassword("123456");',
// code after
'$user->changePassword("123456");'
),
]
);
}
public function getNodeTypes(): array
{
return [MethodCall::class];
}
public function refactor(Node $node): ?Node
{
if (! $this->isName($node->name, 'set*')) {
return null;
}
$methodCallName = $this->getName($node->name);
$newMethodCallName = preg_replace('#^set#', 'change', $methodCallName);
$node->name = new Identifier($newMethodCallName);
return $node;
}
}
コード解説
- L12:
getRuleDefinition()
リファクタリングの大まかな概要を記述します。before→afterを書くことで、実装部分を見なくても大まかな変更ルールを理解できるようになります - L26:
getNodeTypes()
リファクタリングを適用したNodeタイプを返します。今回の場合は、メソッドの呼び出しの場合のみを変更したいため、MethodCall::class
のみを返します。Nodeタイプに関しては、↓こちらのドキュメントに記載があります。
https://github.com/rectorphp/php-parser-nodes-docs - L31:
refactor()
実際のリファクタリング実装部分になります。NULL
を返した場合は、そのノードに関してリファクタリングは行わず、受け取った引数のNodeに関して様々な変更を行い、変更したNodeを返すことでリファクタリングが行われます。 - L33:
$this->isName($node->name, 'set*')
Nodeの名前と期待する文字列と比較を行います。$node->name
で取得できるものはNode
であることに注意してください。 - L37:
$methodCallName = $this->getName($node->name);
メソッド名をstring
で取得します - L38:
$newMethodCallName = preg_replace('#^set#', 'change', $methodCallName);
set
→change
に置き換えます - L40:
$node->name = new Identifier($newMethodCallName);
メソッド名を新しく置き換えたものに変更します。
MethodCall
の場合、var
name
args
の3つのプロパティを持ち、それぞれのプロパティは以下のような関係を表します$this->setPassword('P@ssW0rd'); ----- ----------- ---------- var name args
-
composerのautoloadの設定を追加
composer.json
ファイルに以下のようにautoloadの設定を追加composer.json{ ... "autoload-dev": { "psr-4": { "Rector\\Rules": "rector/rules", } } }
上記変更を行った後は、下記コマンドを実行します。
$composer dump-autoload
-
rector.php
で独自ルールの指定rector.phpuse Rector/Rules/MyRuleRector; return static function (RectorConfig $rectorConfig): void { ... $rectorConfig->rules([ ..., MyRuleRector::class ]); }
トラブルシューティング
-
リファクタリングが失敗してエラーになった
→ コードが複雑のため、ASTによる解析が行えなくっている状態です。該当ファイルのみ複雑になっている箇所のコードを変更することにより成功する可能性があります -
サーバーのPHPのバージョンは古いが次回バージョンアップに向けて非推奨のものを無くしたい
→ 設定ファイルのrector.php
で実行するPHPバージョンを指定する
下記の指定により、PHP8.1で削除される・非推奨になる機能をリファクタリングし、PHP8.1から追加される機能は使用しないようになります。$rectorConfig->sets([LevelSetList::UP_TO_PHP_81]); $rectorConfig->phpVersion(PhpVersion::PHP_74);
Discussion