phpcpd-next сканує PHP-код і виявляє блоки, які були скопійовані з одного місця в інше - той тип дублювання, який легко пропустити під час ревʼю коду, але складно підтримувати синхронізованим згодом.
Інструмент підтримується Luciano Federico Pereira як наступник архівованого phpcpd від Sebastian Bergmann і залишається повністю сумісною заміною з тією ж командою phpcpd.
Що нового: більше ніж точне копіювання
На відміну від попередньої версії, phpcpd-next виявляє не лише дослівні копії, а й дублікати, де рядки були переупорядковані або де між двома ідентичними блоками додали чи видалили один вираз:
- Три алгоритми детекції - Rabin-Karp (точні збіги), TokenBag (виявлення переупорядкованих фрагментів) та опціональне суфіксне дерево (для клонів Type-3 з пропусками), плюс режим
--fuzzy для нечутливості до перейменувань
- Чотири формати виводу - консольний текст, PMD-CPD XML, JSON та SARIF 2.1.0 для GitHub Code Scanning
- Headless API для виклику детекції всередині процесу, плюс PHPUnit-трейт, що перетворює перевірку дублювання на тестове твердження
- Можливості для CI - значущі exit-коди, повне кешування результатів та інкрементальна індексація по файлах
- Пресети для фреймворків, включно з Laravel, із CLI-прапорцями для перевизначення налаштувань пресетів
- PHP 8.5+ без runtime-залежностей Composer та детерміністичні результати
Три алгоритми, що працюють разом
Більшість детекторів copy/paste знаходять лише точне дублювання. phpcpd-next за замовчуванням запускає Rabin-Karp (точні послідовні збіги) та TokenBag (overlap незалежно від порядку, тому переставлені вирази також реєструються) одночасно при кожному запуску. Алгоритм суфіксного дерева для клонів з пропусками - коли між ідентичними блоками вставили або видалили вираз - активується опціонально:
# За замовчуванням: точна + детекція переупорядкованих фрагментів
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.
Вивід SARIF для GitHub Code Scanning
Поряд із PMD-CPD XML та JSON, phpcpd-next генерує SARIF 2.1.0, тому клони відображаються у вкладці Security на GitHub. Неузгоджені (розбіжні) клони маркуються рівнем 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
Headless API та PHPUnit-твердження
Окрім CLI, детекція виконується всередині процесу через статичний виклик detect() - без запуску shell-команд, без файлів звітів:
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, і встановлюється як dev-залежність:
composer require --dev phpcpd-next/phpcpd
vendor/bin/phpcpd src/
Пресет Laravel сканує app, routes, database та config, виключаючи vendor-код, Blade-шаблони, міграції та файли IDE-helper:
vendor/bin/phpcpd --preset=laravel app/Services --min-tokens=60
Вихідний код та повну документацію можна знайти на GitHub.