Rector独自ルールのパッケージ運用

4 min read読了の目安(約3600字

あひるさんがRectorで始める自動リファクタリング入門という力作を書いてくれたので、せっかくなので僕も会社でやってるRectorの運用について簡単に書きます。

どちらが正解という話ではなく、飽くまでひとつの運用例だということで読んでください。今回はRectorを例に取っていますが、PHPStanも同様の構成で運用しています。

パッケージ構成

あひるさん記事の構成ではアプリケーションと同じリポジトリ内に rector/{src,tests} ディレクトリを用意していますが、私はアプリケーション本体とは別にRector専用のリポジトリで独自のRectorルールを開発しています。

Rectorに限った話ではありませんが、プロジェクトのランタイムとは関係しない開発時に必要なツールをどこに置くか、依存関係をどこに持たせるかというのは少し悩ましい問題があります。

基本的には独立したものはパッケージを分けておいた方がよいと思うのですが、Rectorの場合はリファクタリング対象がプロジェクト本体で定義された)インターフェイスや抽象クラスに依存しており完全に分かちがたいということもあるのです。RectorはPHPStanをベースにしており変更対象のクラスはエラーなく定義できなくてはいけません。これはテストのフィクスチャであっても同様です。

ただ、その場合も独立したプロジェクト側に依存するクラスやインターフェイスをコピーして一部の制約を意図的に削除することで仕様変更に備えることもできます。

会社ではSatisで社内ローカルのパッケージをホスティングしているので、pixiv/rector-pixivというパッケージとして登録しています。バージョンは2020.11.2.0のように日付をベースにしています。 (Composerで日付バージョンを使うときはくれぐれも2020.11.02のように 0でパディングしない よう気をつけてください)

先述した通りプロジェクト本体で定義される抽象クラスやインターフェイスはコピーして一部改変した上で、composer.json"autoload-dev"で自動ロードできるようにします。

インストール

あひるさんに説明した通り、会社のプロダクトではプロジェクトそのものではなく、RectorやPHPStanはサブディレクトリにインストールしています。

プロジェクトのディレクトリ配置では開発用のツールやスクリプト群はdev-scriptというディレクトリに置くようにしています。 dev-script/rectorはラッパーのシェルスクリプトで、以下の通りです。

#!/bin/bash

# rector 実行時に php-timecop が読み込まれないようにする
php --ini | grep -Eo '/.+\.ini' | xargs cat | grep -v timecop > "$(dirname "$0")/exclude-timecop.ini"
php -nc "$(dirname "$0")"/exclude-timecop.ini -dmemory_limit=${PHP_MEMORY_LIMIT:-128M} "$(dirname "$0")"/_rector/vendor/bin/rector "$@"

これはhnw/php-timecopPHPStanと相性が悪いための措置です。今のPHPStanはもう影響を受けないような気もしますが、timecopは解析時に必要ないので現在も外したままになっています。

いちおうシェルスクリプトの説明をしておくと "$@" はそれぞれの引数をすべてクオートして渡すという意味で、Rectorが受け付ける引数はすべてそのまま渡すことができます。

dev-script/_phpstan/composer.jsonは以下の通りです。

{
    "name": "pixiv/rector",
    "license": "proprietary",
    "description": "rector",
    "repositories": [
        {
            "packagist": false
        },
        {
            "type": "composer",
            "url": "http://satis.example.private/"
        }
    ],
    "require": {
        "pixiv/rector-pixiv": "^2020.10",
        "rector/rector-prefixed": "^0.8.45"
    },
    "config": {
        "optimize-autoloader": true,
        "classmap-authoritative": true,
        "discard-changes": true,
        "secure-http": false,
        "preferred-install": "dist"
    }
}

rector/rector-prefixedはrectorをPharアーカイブとしてコンパイルして提供するパッケージです。 composer require pixiv/rector-pixiv rector/rector-prefixed のようにパッケージを同時に指定して導入することでrector/rectorrector/rector-prefixedの代替となるので、複雑な依存関係の解決を避けられます。

このように独自ルールのパッケージ側ではrector/rectorに依存、プロジェクト本体に導入する際にはrector/rector-prefixedで置き換えて依存させることで、開発時に必要があればRectorの実装を読みながら作業を進めることができます。


これは気休め程度ですが、最適化のためにoptimize-autoloaderclassmap-authoritativeを有効化しています。secure-httpは社内のSatisがHTTPなので設定しています(当然、そのような状況下以外では設定しないでください)。

このPHPがテンプレートエンジンのくせに慎重すぎる (前篇) - Qiitaで説明した通り、Composer bin pluginでも同じようなことを実現できます。

最近作ったBag2\DoppelではRectorは使っていませんが、PHPStanをはじめとするQAツール群をtoolsディレクトリ以下にmakeだけで導入できるようにしています。

まとめ

簡単な内容なのでまとめも何もあったものではないですが、Rectorは確度が高い型情報をとりながらめっちゃ簡単に構文木を弄れるので楽しいですよ。

汎用的なルールは気軽にRector本体に送るといいですよ

https://github.com/rectorphp/rector/pull/2711/files