Berkenalan dengan Rector — PHP Reconstructor Tool

Berkenalan dengan Rector — PHP Reconstructor Tool

Basa-basi dulu ya. Pernah nggak kamu develop suatu aplikasi, udah lama banget, lalu beberapa tahun kemudian kamu ditugaskan/ingin kembangkan source code aplikasi tersebut sekaligus ingin mengubah codebasenya menggunakan bahasa pemrograman atau framework yang terbaru (saat itu). Apa yang kamu lakukan? periksa filenya satu-persatu, ubah manual? ya, cara amannya memang begitu. Untuk beberapa task sederhana lain, kamu mungkin akan memanfaatkan fitur dari IDE/text-editor seperti refactor, find-and-replace, dsb.

Nah, sekarang di PHP ada tool untuk membantu kamu melakukan hal tersebut, namanya Rector — PHP Reconstructor. Rector adalah open source command line tool untuk membantu developer melakukan upgrade dan refactoring codebase PHP mereka secara instan. Sebelum kamu berharap dan membaca terlalu jauh, ini kata dokumentasi Rector: "Why refactor manually if Rector can handle 80% for you?".

Jadi, ya, Rector ini bukan tool ajaib yang bisa memenuhi 100% keinginanmu. Bahkan mungkin nggak sampai 80% kebutuhanmu seperti yang dokumentasinya bilang.

Ok, lanjut ya?

Apa Saja yang Bisa Dilakukan Rector?

Secara garis besar ya itu tadi, upgrading dan refactoring. Misal kamu mau upgrade project PHP 5.3 kamu ke PHP 7.4, Rector memiliki beberapa set rector untuk membantu kamu melakukan perubahan yang diperlukan. Atau kamu mau upgrade Laravel 5.5 ke Laravel 6, Rector juga punya beberapa set rector untuk melakukan hal tersebut.

Ok, sebelum kebingungan ini "berkembang biak", jadi gini:

  • Package Rector memiliki beberapa "set".
  • Setiap "set" terdiri dari beberapa "Rector".

Black Guy question mark

Intinya gini, package Rector ini berisi sekumpulan Rector (class) yang dikelompokkan kedalam set-nya masing-masing, dimana setiap Rector-nya bertanggung jawab untuk menangani sebuah refactoring. Seperti: mengubah nama fungsi dari fn_a() ke fn_b(), atau mengubah argumen dari fn($x, $y) menjadi fn($y, $x), dan lain sebagainya.

Kembali ke subtopik "Apa Saja yang Bisa Dilakukan Rector?". Sebetulnya banyak, karena set-nya itu banyak. Jadi disini saya sebutkan yang sekiranya "wah" aja.

  • Rector dapat mendeteksi dan memberikan return type pada function yang belum didefinisikan return type-nya.
  • Rector dapat memberikan anotasi @var pada property yang belum kamu berikan @var.
  • Mengubah penggunaan facade menjadi dependency injection pada kode Laravel kamu.
  • Mengubah kode Nette menjadi kode Symfony (saya sendiri belum coba).
  • Mengubah $value = $a ? $a : null menjadi $value = $a ?: null.
  • Kalau kamu pernah mengalami error count(): Parameter must be an array or an object that implements Countable, ada Rector CountOnNullRector untuk mencegah hal tersebut dengan melakukan safety check terlebih dahulu.

Masih banyak lagi sebetulnya, berikut daftar sets dan Rector lainnya yang dapat kamu gunakan (kayaknya belum lengkap).

Cara Kerja Rector

Untuk detil cara kerjanya kamu bisa lihat di halaman ini. Disini saya hanya menuliskan garis besarnya supaya kamu setidaknya mendapat gambaran cara kerjanya.

  1. Pertama, Rector membaca file yang ingin diterapkan refactoring menggunakan file_get_contents. Dapatlah si Rector string yang berisi source code kamu.
  2. Kemudian string yang berisi source code tersebut di-parsing menggunakan nikic/php-parser menjadi AST (Abstract Syntax Tree). Kalau kamu tidak tahu apa itu AST, anggap aja ini JSON yang berisi struktur tree dari source code kamu. AST tersebut berisi sekumpulan node yang saling terhubung, dimana setiap node-nya memiliki definisi type-nya, apakah dia function, string, int, float, constant, dsb.
  3. Setelah diparsing ke AST, AST tersebut dibaca node-per-node-nya, jika ada bagian yang perlu dilakukan refactoring, maka node input tersebut direkonstruksi menjadi node output.
  4. Kalau sudah selesai (semua node sudah dicek dan Rector yang digunakan sudah selesai melakukan rekonstruksi), AST hasil rekonstruksi tersebut akan diubah kembali menjadi string untuk disimpan ke file tersebut, atau sekedar ditampilkan perubahannya ke layar.

Menggunakan Rector

Ada 2 cara instalasi Rector, yang pertama dengan Docker, yang ke-2 dengan Composer. Disini saya menggunakan cara yang lebih gampang jelasinnya, yaitu dengan Composer.

Cek Requirements

Oke, sebelum menggunakan Rector, pastikan kamu memenuhi kebutuhan dibawah ini:

  1. PHP >= 7.2 (cek dengan php -v).
  2. Ekstensi PHP JSON dan Tokenizer (bisa dicek dengan php -m).
  3. Composer (cek dengan composer --version).

Versi Rector yang saya gunakan disini adalah versi 0.6.2, jika kamu adalah manusia dari masa depan yang sedang membaca artikel ini di versi Rector yang lebih tinggi, mungkin requirement diatas bisa berubah atau bertambah.

Menginstall Rector

Pada saat saya menulis artikel ini, Rector masih bermasalah jika diinstall secara global menggunakan composer global require, jadi kita akan menginstallnya ke direktori khusus dengan perintah composer require biasa. Berikut step-by-step-nya:

  1. Buka terminal/cmd.
  2. Buat dan masuk ke direktori manapun dimana kamu ingin menginstall Rector. Contoh saya akan menginstall Rector di ~/dev/tools/rector, maka perintah (linux) yang saya gunakan adalah:

    • mkdir ~/Dev/rector -P (buat direktorinya).
    • cd ~/Dev/rector (masuk ke direktorinya).
  3. Install Rector: composer require rector/rector --dev.
  4. Cek instalasi: ./vendor/bin/rector --version. Kalu muncul output seperti ini: Rector v0.6.2 artinya instalasinya berhasil.

Sampai sini kita sudah dapat menggunakan Rector, tapi saya mau jelaskan dulu strategi seperti apa yang akan kita gunakan. Jadi dengan cara instalasi seperti ini (menggunakan composer require), saya memikirkan setidaknya ada 3 strategi yang dapat kamu lakukan:

  1. Menginstall Rector di setiap project kamu.
  2. Menaruh source code project yang ingin dilakukan refactoring kedalam direktori rector.
  3. Mendaftarkan ~/Dev/rector/vendor/bin kedalam variable PATH untuk mengglobalkan perintah rector.

Saya tidak menyarankan cara pertama, karena buat apa juga install Rector banyak-banyak, toh fungsinya sama. Sedangkan untuk cara ke-2 dan ke-3, untuk penggunaan berkepanjangan saya lebih menyarankan cara ke-3. Tapi pada artikel ini, kita akan menggunakan cara ke-2 karena lebih mudah dan to-the-point.

Jadi kita akan menaruh source code yang akan kita lakukan refactoring kedalam direktori ~/Dev/rector/src. Misal kamu ingin refactor project Laravel kamu, kamu bisa menaruhnya ke ~/Dev/rector/src/project-laravelmu.

Melihat Set yang Tersedia

Sebelum kita melakukan refactoring, kita dapat mengecek terlebih dahulu set yang tersedia pada instalasi Rector kita. Masih di terminal, ketikkan:

./vendor/bin/rector sets

Outputnya kurang lebih akan seperti ini:

Rector v0.6.2

116 available sets:
===================

 * action-injection-to-constructor-injection
 * array-str-functions-to-static-call       
 * cakephp34
 * cakephp35
 * cakephp36
 * cakephp37
 * cakephp38
 * cakephp40
 * celebrity
 * code-quality
 * coding-style
 * constructor-injectin-to-action-injection 
 * contributte-to-symfony
 * dead-code
 * doctrine
 ... masih banyak lagi

Itu adalah daftar set yang bisa kamu gunakan untuk tahap selanjutnya. Seperti yang saya bilang sebelumnya, setiap set terdiri dari sekumpulan Rector untuk melakukan refactoring yang berbeda-beda.

Kalau kamu ingin mengecek ketersediaan set tertentu, contoh kamu ingin melihat set yang berhubungan sama Laravel, kamu bisa gunakan perintah dibawah ini:

./vendor/bin/rector sets laravel

Maka outputnya akan difiltrasi seperti ini:

Rector v0.6.2

11 available sets:
==================

 * laravel-static-to-injection
 * laravel50
 * laravel51
 * laravel52
 * laravel53
 * laravel54
 * laravel55
 * laravel56
 * laravel57
 * laravel58
 * laravel60

Sayangnya pada versi 0.6.2 yang saya install ini, kita belum bisa melihat Rector apa saja pada masing-masing set tersebut. Jadi jika kamu ingin melihat Rector pada setiap set, untuk sementara kamu dapat melihatnya pada halaman ini.

Mengaplikasikan Set dengan 1 Perintah

Ini adalah cara penggunaan Rector yang paling sederhana. Misalkan disini saya akan mencoba set php70 dimana salah satu Rector-nya adalah TernaryToNullCoalescingRector, yakni mengubah Ternary Operator menjadi Null Coalescing Operator.

Pertama saya membuat file src/sample.php. Lalu saya isikan kode sebagai berikut:

<?php

$keyword = isset($_GET['q']) ? $_GET['q'] : '';

Untuk menerapkan set php70 pada (seluruh file .php di) direktori src, kita dapat gunakan perintah dibawah ini:

./vendor/bin/rector process --set php70 src

Maka Rector akan menampilkan perubahan apa saja yang dia lakukan, dan menyimpan perubahannya ke file tersebut. Kalau kamu lihat kembali isi file src/sample.php, kode yang sebelumnya kita buat akan berubah menjadi seperti ini:

<?php

$keyword = $_GET['q'] ?? '';

Jika kamu menginginkan Rector hanya menampilkan perubahannya tanpa menyimpan perubahan ke masing-masing file, kamu dapat gunakan opsi --dry-run atau -n seperti dibawah ini:

./vendor/bin/rector process --set php70 --dry-run src

Maka Rector hanya akan menampilkan perubahannya saja, tanpa menyimpan perubahan tersebut ke setiap filenya.

Menerapkan Beberapa Set Sekaligus dengan rector.yaml

Sebelumnya kita menerapkan set dengan sebuah perintah. Bagaimana jika kita ingin menerapkan beberapa set sekaligus? jawabannya adalah file rector.yaml.

File rector.yaml adalah file konfigurasi Rector yang kita siapkan untuk project tertentu. Jadi setiap project bisa beda-beda file rector.yaml-nya. Untuk namanya sebetulnya tidak harus rector.yaml, jadi misalkan kamu punya konfigurasi berbeda antara project A dan project B, kamu bisa bedakan namanya, misal rector-a.yaml, dan rector-b.yaml. Hanya saja secara default Rector akan menggunakan file rector.yaml yang pada direktori dimana kamu menjalankan perintah rector.

Pada file rector.yaml ini kita bisa spesifikasikan beberapa hal, seperti sets yang ingin digunakan, file autoloading tambahan, path pengecualian, dsb. Tapi bakal panjang kalau saya contohin satu-persatu, jadi disini saya contohin cara menerapkan beberapa sets sekaligus aja.

Oke, jadi pertama-tama saya siapkan dulu contoh file yang mau di-refactoring-nya. Saya ubah src/sample.php menjadi seperti ini:

<?php

try {
    $filename = isset($argv[1]) ? $argv[1] : null;
    if (!$filename) {
        throw new InvalidArgumentException("Missing filename");
    }
    $json = json_decode(file_get_contents(dirname(__FILE__).'/'.$filename));
    print_r($json);
} catch (JsonException $e) {
    echo $e->getMessage();
} catch (InvalidArgumentException $e) {
    echo $e->getMessage();
}

Misalkan saya ingin menerapkan 3 buah set, yakni set php53, php70 dan php71. Jadi mari kita buat file rector.yaml seperti dibawah ini:

parameters:
  sets:
    - php53
    - php70
    - php71

Selanjutnya mari kita cek dulu apa saja yang akan dirubah dengan perintah:

./vendor/bin/rector process --dry-run src

Outputnya akan seperti ini:

Rector v0.6.2
Config file: rector.yaml

3/3 [============================] 100%

1 file with changes
===================

1) src/sample.php

  ---------- begin diff ----------
--- Original
+++ New
@@ -1,13 +1,13 @@
<?php

+use InvalidArgumentException;
+use JsonException;
try {
-    $filename = isset($argv[1]) ? $argv[1] : null;
+    $filename = $argv[1] ?? null;
     if (!$filename) {
         throw new InvalidArgumentException("missing filename");
     }
-    $json = json_decode(file_get_contents(dirname(__FILE__).'/'.$filename));
- } catch (JsonException $e) {
+    $json = json_decode(file_get_contents(__DIR__.'/'.$filename));
+ } catch (JsonException|InvalidArgumentException $e) {
     echo $e->getMessage();
- } catch (InvalidArgumentException $e) {
-    echo $e->getMessage();
- }
+ }
\ No newline at end of file
  ----------- end diff -----------

Applied rules:

 * Rector\Php71\Rector\TryCatch\MultiExceptionCatchRector
 * Rector\Php70\Rector\Ternary\TernaryToNullCoalescingRector
 * Rector\Php53\Rector\FuncCall\DirNameFileConstantToDirConstantRector


[OK] Rector is done! 1 changed files                                                                         

Disitu bisa kita lihat bagian-bagian yang ditambah dan dihapus. Juga pada bagian bawah kita dapat lihat ada 3 Rector yang digunakan, yaitu:

  1. Rector\Php71\Rector\TryCatch\MultiExceptionCatchRector dari set php71.
  2. Rector\Php70\Rector\Ternary\TernaryToNullCoalescingRector dari set php70.
  3. Rector\Php53\Rector\FuncCall\DirNameFileConstantToDirConstantRector dari set php53.

Kalau sekiranya oke, mari kita aplikasikan ke filenya dengan menghapuskan --dry-run seperti dibawah ini:

./vendor/bin/rector process src

Sekarang, kalau kita lihat filenya, maka akan jadi seperti ini:

<?php

use InvalidArgumentException;
use JsonException;
try {
    $filename = $argv[1] ?? null;
    if (!$filename) {
        throw new InvalidArgumentException("missing filename");
    }
    $json = json_decode(file_get_contents(__DIR__.'/'.$filename));
} catch (JsonException|InvalidArgumentException $e) {
    echo $e->getMessage();
}

Penutup

Oke mungkin segitu saja untuk artikel pengenalan Rector kali ini. Karena tool ini umurnya masih terbilang baru, jadi memang mungkin belum banyak yang bisa kita lakukan. Tapi kedepan mungkin akan banyak tambahan-tambahan Rector lain yang dapat membantu task refactoring kita. Jadi tidak ada salahnya kita pelajari sejak awal.

Yaudah, sekian untuk artikel kali ini. Semoga bermanfaat. Dadah ~

Suka artikel ini?

Saya biasanya share artikel-artikel terbaru via facebook atau fanpage foobarology saya. Kalau mau dapat updatenya, di add friend/like/follow aja link-link dibawah ini 😃

Facebook Foobarology