phpcpd-next は PHP コードをスキャンし、ある場所から別の場所へコピー&ペーストされたブロックを報告します。レビューで見逃しやすく、後で同期を保つのが面倒な重複です。
Sebastian Bergmann 氏の phpcpd(アーカイブ済み)の後継として Luciano Federico Pereira 氏が保守しており、同じ phpcpd コマンドでドロップイン置き換えが可能です。
新機能として、逐語的なコピーだけでなく、行の順序が入れ替わった場合や、2 つの同一ブロック間で文が追加・削除された場合も検出します:
- 3 つの検出エンジン — Rabin-Karp(完全一致)、TokenBag(順序変更対応)、オプトインの接尾辞木(ギャップのある Type-3)、さらに rename-insensitive な
--fuzzyマッチング - 4 つの出力形式 — コンソールテキスト、PMD-CPD XML、JSON、GitHub Code Scanning 用の SARIF 2.1.0
- ヘッドレス API — プロセス内で検出を呼び出し可能。PHPUnit トレイトで重複をテストアサーションに変換
- CI 機能 — 有意義な終了コード、完全な結果キャッシュ、ファイル単位の増分インデックス
- フレームワークプリセット(Laravel など) — CLI フラグでプリセットのデフォルトを上書き可能
- PHP 8.5+、Composer ランタイム依存ゼロ、決定的な結果
3 つのエンジンをデフォルトで同時に実行
ほとんどのコピー/ペースト検出ツールは完全一致の重複しか検出しません。phpcpd-next はデフォルト実行時に Rabin-Karp(連続する完全一致)と TokenBag(順序不変の重複、ステートメントがシャッフルされていても検出)を同時に実行します。ギャップのあるクローン(同一ブロック間に文が挿入・削除されたもの)を検出する接尾辞木エンジンはオプトインです:
# デフォルト: 完全一致 + 順序変更検出
phpcpd src/
# Rabin-Karp のみ(高速、順序変更検出なし)
phpcpd --rk src/
# 接尾辞木によるギャップのある Type-3 クローン
phpcpd --algorithm=suffixtree src/
コンソール出力は重複範囲を指摘し、単に行番号を列挙するだけでなくリファクタリングを提案します:
Found 2 code clones with 21 duplicated lines in 2 files:
- app/Services/Billing.php:12-33 (21 lines)
app/Services/Invoicing.php:40-61
→ Consider extracting the shared lines into a reusable method or constant.
37.50% duplicated lines out of 56 total lines of code.
GitHub Code Scanning 用の SARIF 出力
PMD-CPD XML や JSON に加え、phpcpd-next は SARIF 2.1.0 を出力するため、クローンが GitHub の Security タブに表示されます。不整合(分岐)したクローンは warning 重大度、完全一致クローンは note としてマッピングされます:
- name: Detect duplicated code
run: vendor/bin/phpcpd --log-sarif=phpcpd.sarif src/ || true
- name: Upload results
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: phpcpd.sarif
ヘッドレス API と PHPUnit アサーション
CLI 以外にも、静的な detect() 呼び出しでプロセス内検出が可能です。シェル呼び出しやレポートファイルは不要です:
use LucianoPereira\PhpcpdNext\Phpcpd;
$clones = Phpcpd::detect(
paths: 'app',
minTokens: 60,
algorithm: null, // null = Rabin-Karp + TokenBag
preset: 'laravel',
);
foreach ($clones as $clone) {
echo $clone->numberOfLines(), " lines\n";
}
同梱のトレイトでテスト化でき、重複をクローン位置付きで失敗させるリグレッションチェックにできます:
use LucianoPereira\PhpcpdNext\PHPUnit\AssertNoDuplication;
use PHPUnit\Framework\TestCase;
final class DuplicationTest extends TestCase
{
use AssertNoDuplication;
public function test_app_is_dry(): void
{
$this->assertNoDuplication(__DIR__ . '/../app', minTokens: 70);
}
}
CI 向けの増分キャッシュ
大規模コードベース向けに --cache は設定フィンガープリントとファイルマニフェストハッシュで結果を保存し、変更がなければキャッシュ結果を再生します。--incremental はさらに進んで、変更されたファイルのみ再トークナイズし、残りはファイル単位のインデックスから再利用(Rabin-Karp のみ)し、(incremental index: 412 reused, 3 scanned) のようなサマリを表示します:
- uses: actions/cache@v4
with:
path: .phpcpd-cache
key: phpcpd-${{ hashFiles('**/*.php') }}
restore-keys: phpcpd-
- run: vendor/bin/phpcpd --incremental --cache-dir .phpcpd-cache src/
インストール
PHP 8.5+、ext-dom、ext-mbstring が必要で、開発依存としてインストールします:
composer require --dev phpcpd-next/phpcpd
vendor/bin/phpcpd src/
Laravel プリセットは app、routes、database、config をスキャンし、vendor コード、Blade ビュー、マイグレーション、IDE ヘルパーファイルを除外します:
vendor/bin/phpcpd --preset=laravel app/Services --min-tokens=60
ソースと完全なドキュメントは GitHub で確認できます。
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.