
Badoo telah ada selama lebih dari 12 tahun. Kami memiliki banyak kode PHP (jutaan baris) dan bahkan mungkin baris yang ditulis 12 tahun yang lalu dipertahankan. Kami memiliki kode yang ditulis kembali pada hari-hari PHP 4 dan PHP 5. Kami memposting kode dua kali sehari, dan setiap tata letak berisi sekitar 10-20 tugas. Selain itu, programmer dapat memposting tambalan mendesak - perubahan kecil. Dan pada hari tambalan seperti itu, kita mendapatkan beberapa lusin. Secara umum, kode kami berubah sangat aktif.
Kami terus mencari peluang untuk mempercepat pengembangan, dan meningkatkan kualitas kode. Jadi suatu hari kami memutuskan untuk menerapkan analisis kode statis. Apa yang terjadi, baca di bawah potongan.
Jenis yang ketat: mengapa kita belum menggunakannya
Suatu kali, diskusi dimulai di obrolan PHP perusahaan kami. Salah satu karyawan baru memberi tahu bagaimana di tempat kerja sebelumnya mereka memperkenalkan
petunjuk jenis skalar strict_types + wajib untuk seluruh kode - dan ini secara signifikan mengurangi jumlah bug pada produksi.
Kebanyakan obrolan orang tua menentang inovasi semacam itu. Alasan utama adalah bahwa PHP tidak memiliki kompiler yang memeriksa semua jenis dalam kode pada waktu kompilasi, dan jika Anda tidak memiliki cakupan 100% dari kode dengan tes, maka selalu ada risiko bahwa kesalahan akan muncul pada produksi, yang kami tidak lakukan ingin mengizinkan.
Tentu saja, strict_types akan menemukan persentase bug tertentu yang disebabkan oleh ketidakcocokan jenis dan bagaimana PHP "diam-diam" mengonversi jenis. Tetapi banyak programmer PHP yang berpengalaman sudah tahu bagaimana sistem tipe dalam PHP bekerja, dengan apa jenis konversi aturan terjadi, dan dalam kebanyakan kasus mereka menulis kode kerja yang benar.
Tetapi gagasan memiliki sistem tertentu yang menunjukkan di mana dalam kode ada jenis ketidakcocokan, kami suka. Kami memikirkan alternatif untuk strict_types.
Awalnya kami bahkan ingin menambal PHP. Kami ingin bahwa jika fungsi mengambil beberapa jenis skalar (katakanlah int), dan tipe skalar lain (seperti float) masuk, maka TypeError (yang merupakan pengecualian dalam dirinya sendiri) tidak akan dibuang, tetapi konversi jenis akan terjadi, serta mencatat acara ini di error.log. Ini akan memungkinkan kami untuk menemukan semua tempat di mana asumsi kami tentang jenis tidak benar. Tapi tambalan seperti itu tampaknya berisiko bagi kami, dan bahkan mungkin ada masalah dengan ketergantungan eksternal, tidak siap untuk perilaku seperti itu.
Kami meninggalkan ide menambal PHP, tetapi seiring waktu semuanya bertepatan dengan rilis pertama dari penganalisis statis Phan, komitmen pertama yang dibuat oleh Rasmus Lerdorf sendiri. Jadi kami datang dengan ide mencoba penganalisa kode statis.
Apa itu analisis kode statis?
Penganalisa kode statis hanya membaca kode dan mencoba untuk menemukan kesalahan di dalamnya. Mereka dapat melakukan pemeriksaan yang sangat sederhana dan jelas (misalnya, untuk keberadaan kelas, metode dan fungsi, dan yang lebih rumit (misalnya, mencari ketidakcocokan jenis, kondisi ras atau kerentanan dalam kode). Kuncinya adalah bahwa penganalisa tidak mengeksekusi kode - mereka menganalisis teks program dan memeriksa kesalahan khas (dan tidak demikian).
Contoh paling jelas dari penganalisa kode PHP statis adalah inspeksi di PHPStorm: ketika Anda menulis kode, ini menyoroti panggilan yang salah ke fungsi, metode, ketidakcocokan jenis parameter, dll. Namun, PHPStorm tidak menjalankan kode PHP Anda - hanya menganalisisnya.
Saya perhatikan bahwa dalam artikel ini kita berbicara tentang penganalisa yang mencari kesalahan dalam kode. Ada kelas analisis lain - mereka memeriksa gaya penulisan kode, kompleksitas siklomatik, ukuran metode, panjang garis, dll. Kami tidak mempertimbangkan analisis tersebut di sini.
Meskipun tidak semua yang ditemukan oleh analis yang kami temukan ternyata adalah kesalahan. Secara tidak sengaja, maksud saya kode yang akan dibuat Fatal pada produksi. Sangat sering, apa yang ditemukan oleh para analis lebih cenderung tidak akurat. Misalnya, tipe parameter yang salah dapat ditentukan dalam PHPDoc. Ketidaktepatan ini tidak mempengaruhi operasi kode, tetapi kemudian kode akan berkembang - programmer lain dapat membuat kesalahan.
Analisis Kode PHP yang ada
Ada tiga penganalisa kode PHP populer:
- PHPStan .
- Mazmur
- Phan .
Dan ada
Exakat , yang belum kita coba.
Di sisi pengguna, ketiga analisis itu sama: Anda menginstalnya (kemungkinan besar melalui Komposer), mengonfigurasinya, setelah itu Anda dapat memulai analisis seluruh proyek atau grup file. Sebagai aturan, alat analisa dapat dengan indah menampilkan hasil di konsol. Anda juga dapat menampilkan hasilnya dalam format JSON dan menggunakannya dalam CI.
Ketiga proyek tersebut sekarang sedang aktif dikembangkan. Pemelihara mereka sangat aktif dalam menanggapi masalah di GitHub. Seringkali pada hari pertama setelah membuat tiket mereka bereaksi setidaknya (komentar atau beri tag seperti bug / peningkatan). Banyak bug yang kami temukan diperbaiki dalam beberapa hari. Tapi saya terutama menyukai kenyataan bahwa pengelola proyek secara aktif berkomunikasi satu sama lain, melaporkan bug satu sama lain, dan mengirim permintaan tarikan.
Kami telah menerapkan dan menggunakan ketiga analisanya. Masing-masing memiliki nuansa sendiri, bug sendiri. Tetapi menggunakan tiga analisis pada saat yang sama memfasilitasi pemahaman tentang di mana masalah sebenarnya dan di mana positif palsu.
Apa yang bisa dilakukan analis
Analisis memiliki banyak fitur umum, jadi pertama-tama kita akan melihat apa yang dapat mereka semua lakukan, dan kemudian beralih ke fitur masing-masing.
Pemeriksaan Standar
Tentu saja, analis melakukan semua pemeriksaan kode standar untuk fakta bahwa:
- Kode tidak mengandung kesalahan sintaksis;
- semua kelas, metode, fungsi, konstanta ada;
- variabel ada;
- di PHPDoc, petunjuknya benar.
Selain itu, parser memeriksa kode untuk argumen dan variabel yang tidak digunakan. Banyak dari kesalahan-kesalahan ini mengarah ke fatamorgana nyata dalam kode.
Pada pandangan pertama, sepertinya programmer yang baik tidak membuat kesalahan seperti itu, tetapi kadang-kadang kita sedang terburu-buru, kadang-kadang copy-paste, kadang-kadang kita hanya lalai. Dan dalam kasus seperti itu, cek ini menghemat banyak.
Pemeriksaan Tipe Data
Tentu saja, analisa statis juga melakukan pemeriksaan standar mengenai tipe data. Jika ditulis dalam kode yang menerima fungsi, katakanlah, int, maka penganalisa akan memeriksa apakah ada tempat di mana objek dilewatkan ke fungsi ini. Untuk sebagian besar analisis, Anda dapat mengonfigurasi tingkat keparahan tes dan mensimulasikan strict_types: verifikasi bahwa tidak ada string atau Boolean yang diteruskan ke fungsi ini.
Selain pemeriksaan standar, analis masih harus melakukan banyak hal.
Jenis serikatSemua analis mendukung konsep tipe Union. Misalkan Anda memiliki fungsi seperti:
function isYes($yes_or_no) :bool { if (\is_bool($yes_or_no)) { return $yes_or_no; } elseif (is_numeric($yes_or_no)) { return $yes_or_no > 0; } else { return strtoupper($yes_or_no) == 'YES'; } }
Isinya tidak terlalu penting - jenis
string|int|bool
parameter input
string|int|bool
penting. Yaitu, variabel
$yes_or_no
adalah string, atau integer, atau
Boolean
.
Menggunakan PHP, jenis parameter fungsi ini tidak dapat dijelaskan. Tetapi dalam PHPDoc, ini mungkin, dan banyak editor (seperti PHPStorm) memahaminya.
Dalam analisis statis, tipe ini disebut
tipe union , dan mereka sangat baik dalam memeriksa tipe data tersebut. Misalnya, jika kita menulis fungsi di atas seperti ini (tanpa memeriksa
Boolean
):
function isYes($yes_or_no) :bool { if (is_numeric($yes_or_no)) { return $yes_or_no > 0; } else { return strtoupper($yes_or_no) == 'YES'; } }
analisanya akan melihat bahwa string atau Boolean bisa datang ke strtoupper, dan mengembalikan kesalahan - Anda tidak bisa meneruskan Boolean ke strtoupper.
Jenis pemeriksaan ini membantu programmer menangani kesalahan atau situasi dengan benar ketika suatu fungsi tidak dapat mengembalikan data. Kami sering menulis fungsi yang dapat mengembalikan beberapa data atau
null
:
Dalam kasus kode semacam itu, penganalisa akan memberi tahu Anda bahwa variabel
$User
sini bisa
null
dan kode ini bisa berakibat fatal.
Ketik salahDalam bahasa PHP itu sendiri, ada beberapa fungsi yang dapat mengembalikan nilai atau salah. Jika kita menulis fungsi seperti itu, bagaimana kita mendokumentasikan jenisnya?
function fopen(...) { … }
Secara formal, semuanya tampaknya benar di sini: fopen mengembalikan sumber daya atau
false
(yang merupakan tipe
Boolean
). Tetapi ketika kita mengatakan bahwa suatu fungsi mengembalikan beberapa jenis tipe data, itu berarti ia dapat mengembalikan nilai
apa pun dari set yang termasuk dalam tipe data ini. Dalam contoh kami, untuk penganalisa, ini berarti bahwa
fopen()
dapat mengembalikan
true
. Dan, misalnya, dalam hal kode seperti itu:
$fp = fopen('some.file','r'); if($fp === false) { return false; } fwrite($fp, "some string");
analisa akan mengeluh bahwa
fwrite
menerima sumber daya parameter pertama, dan kami memberikan
bool
(karena analis melihat bahwa opsi yang benar adalah mungkin). Untuk alasan ini, semua analis memahami tipe data "buatan" seperti
false
, dan dalam contoh kita, kita dapat menulis
@return false|resource
. PHPStorm juga memahami deskripsi tipe ini.
Bentuk arraySeringkali, array dalam PHP digunakan sebagai tipe
record
- suatu struktur dengan daftar bidang yang jelas, di mana setiap bidang memiliki jenisnya sendiri. Tentu saja, banyak programmer sudah menggunakan kelas untuk ini. Tetapi kami memiliki banyak kode lawas di Badoo, dan array secara aktif digunakan di sana. Dan itu juga terjadi bahwa programmer terlalu malas untuk membuat kelas yang terpisah untuk beberapa struktur satu kali, dan di tempat seperti itu array juga sering digunakan.
Masalah dengan array tersebut adalah bahwa tidak ada deskripsi yang jelas tentang struktur ini (daftar bidang dan tipenya) dalam kode. Pemrogram dapat membuat kesalahan ketika bekerja dengan struktur seperti itu: lupakan bidang yang diperlukan atau tambahkan kunci "kiri", lebih lanjut membingungkan kode.
Analisis memungkinkan Anda untuk memasukkan deskripsi struktur seperti itu:
function showUrl(array $parsed_url) { … }
Dalam contoh ini, kami menggambarkan array dengan tiga bidang string:
scheme, host
dan
path
. Jika di dalam fungsi kita beralih ke bidang lain, alat analisa akan menunjukkan kesalahan.
Jika Anda tidak mendeskripsikan jenisnya, maka alat analisis akan mencoba untuk "menebak" struktur array, tetapi, seperti yang ditunjukkan oleh praktik, mereka tidak benar-benar berhasil dengan kode kami. :)
Pendekatan ini memiliki satu kelemahan. Misalkan Anda memiliki struktur yang aktif digunakan dalam kode. Anda tidak dapat mendeklarasikan pseudotype di satu tempat dan kemudian menggunakannya di mana-mana. Anda harus mendaftarkan PHPDoc dengan deskripsi array di mana-mana dalam kode, yang sangat merepotkan, terutama jika ada banyak bidang dalam array. Juga akan bermasalah untuk mengedit tipe ini nanti (tambah dan hapus bidang).
Deskripsi tipe kunci arrayDi PHP, kunci array bisa berupa bilangan bulat dan string. Jenis terkadang penting untuk analisis statis (dan juga untuk programmer). Analisis statis memungkinkan Anda untuk menggambarkan kunci array di PHPDoc:
$users = UserLoaders::loadUsers($user_ids);
Dalam contoh ini, menggunakan PHPDoc, kami menambahkan petunjuk bahwa dalam array
$users
kunci integer ints, dan nilainya adalah objek dari kelas
\User
. Kita bisa menggambarkan tipe sebagai \ Pengguna []. Ini akan memberi tahu penganalisa bahwa ada objek di kelas
\User
dalam array, tetapi tidak akan memberi tahu kami apa pun tentang jenis kunci.
PHPStorm mendukung format ini untuk menjelaskan array yang dimulai dengan versi 2018.3.
Namespace Anda di PHPDocPHPStorm (dan editor lainnya) dan analisis statis dapat memahami PHPDoc secara berbeda. Misalnya, penganalisis mendukung format ini:
function showUrl($parsed_url) { … }
Tapi PHPStorm tidak memahaminya. Tapi kita bisa menulis seperti ini:
function showUrl($parsed_url) { … }
Dalam hal ini, analis dan PHPStorm akan puas. PHPStorm akan menggunakan
@param
, dan penganalisa akan menggunakan tag PHPDoc mereka sendiri.
Fitur PHP Memeriksa
Jenis tes ini paling baik digambarkan dengan contoh.
Apakah kita semua tahu apa fungsi
explode () dapat kembali? Jika Anda melirik ke dokumentasi, sepertinya mengembalikan array. Tetapi jika Anda memeriksanya lebih hati-hati, kita akan melihat bahwa itu juga dapat mengembalikan false. Bahkan, itu bisa mengembalikan nol dan kesalahan jika Anda memberikannya tipe yang salah, tetapi memberikan nilai yang salah dengan tipe data yang salah sudah merupakan kesalahan, jadi opsi ini tidak menarik bagi kami sekarang.
Secara formal, dari sudut pandang penganalisa, jika suatu fungsi dapat mengembalikan false atau array, maka kemungkinan besar, maka kode harus memeriksa false. Tetapi fungsi explode () mengembalikan false hanya jika pembatas (parameter pertama) sama dengan string kosong. Seringkali, secara eksplisit ditulis dalam kode, dan analis dapat memverifikasi bahwa itu tidak kosong, yang berarti bahwa di tempat ini fungsi explode () secara akurat mengembalikan array dan pemeriksaan palsu tidak diperlukan.
PHP memiliki beberapa fitur. Analisis secara bertahap menambahkan pemeriksaan yang sesuai atau memperbaikinya, dan kami, programmer, tidak perlu lagi mengingat semua fitur ini.
Kami beralih ke deskripsi alat analisis tertentu.
PHPStan
Pengembangan Ondřej Mirtes tertentu dari Republik Ceko. Aktif dikembangkan sejak akhir 2016.
Untuk mulai menggunakan PHPStan, Anda perlu:
- Instal (cara termudah untuk melakukannya adalah melalui Komposer).
- (opsional) Konfigurasikan.
- Dalam kasus paling sederhana, jalankan saja:
vendor/bin/phpstan analyse ./src
(bukan
src
mungkin ada daftar file tertentu yang ingin Anda periksa).
PHPStan akan membaca kode PHP dari file yang ditransfer. Jika dia menemukan kelas yang tidak dikenal, dia akan mencoba memuatnya dengan autoload dan melalui refleksi untuk memahami antarmuka mereka. Anda juga dapat mentransfer jalur ke file
Bootstrap
tempat Anda mengkonfigurasi autoload, dan melampirkan beberapa file tambahan untuk menyederhanakan analisis PHPStan.
Fitur Utama:
- Dimungkinkan untuk menganalisis bukan seluruh basis kode, tetapi hanya bagian - kelas yang tidak diketahui PHPStan akan mencoba memuat autoload.
- Jika karena alasan tertentu beberapa kelas Anda tidak ada dalam autoload, PHPStan tidak akan dapat menemukannya dan akan memberikan kesalahan.
- Jika Anda aktif menggunakan metode sulap melalui
__call / __get / __set
, maka Anda dapat menulis plugin untuk PHPStan. Plugin untuk Symfony, Doctrine, Laravel, Mockery, dll sudah ada.
- Faktanya, PHPStan melakukan autoload tidak hanya untuk kelas yang tidak diketahui, tetapi secara umum untuk semua orang. Kami memiliki banyak kode lama yang ditulis sebelum munculnya kelas anonim, ketika kami membuat kelas dalam satu file, dan kemudian langsung instantiate dan, mungkin, bahkan memanggil beberapa metode. Muat otomatis (
include
) file tersebut menyebabkan kesalahan, karena kode ini tidak dijalankan di lingkungan normal.
- Konfigurasi dalam format neon (saya tidak pernah mendengar bahwa format seperti itu digunakan di tempat lain).
- Tidak ada dukungan untuk tag PHPDoc mereka seperti
@phpstan-var, @phpstan-return
, dll.
Fitur lainnya adalah kesalahan memiliki teks, tetapi tidak ada jenis. Yaitu, teks kesalahan dikembalikan kepada Anda, misalnya:
Method \SomeClass::getAge() should return int but returns int|null
Method \SomeOtherClass::getName() should return string but returns string|null
Dalam contoh ini, kedua kesalahan pada dasarnya tentang hal yang sama: metode harus mengembalikan satu jenis, tetapi pada kenyataannya ia mengembalikan yang lain. Namun teks kesalahannya berbeda, meski serupa. Karena itu, jika Anda ingin memfilter kesalahan apa pun di PHPStan, lakukan hanya melalui ekspresi reguler.
Sebagai perbandingan, di penganalisa lain, kesalahan memiliki tipe. Misalnya, di Phan, kesalahan seperti itu adalah tipe
PhanPossiblyNullTypeReturn
, dan Anda bisa menentukan dalam konfigurasi bahwa Anda tidak perlu memeriksa kesalahan tersebut. Juga, memiliki jenis kesalahan, dimungkinkan, misalnya, untuk dengan mudah mengumpulkan statistik tentang kesalahan.
Karena kita tidak menggunakan Laravel, Symfony, Doctrine dan solusi serupa, dan kita jarang menggunakan metode ajaib dalam kode kita, fitur utama PHPStan ternyata tidak diklaim oleh kita. ; (Selain itu, karena fakta bahwa PHPStan memasukkan-itu
semua kelas yang diperiksa, terkadang analisisnya tidak bekerja pada basis kode kami.
Namun, PHPStan tetap bermanfaat bagi kami:
- Jika Anda perlu memeriksa beberapa file, PHPStan terasa lebih cepat dari Phan dan sedikit (20-50%) lebih cepat dari Mazmur.
- Laporan PHPStan memudahkan untuk menemukan
false-positive
di analisis lain. Biasanya, jika ada beberapa fatal
eksplisit dalam kode, ini ditunjukkan oleh semua analis (atau setidaknya dua dari tiga).
Perbarui:Penulis PHPStan Ondřej Mirtes juga membaca artikel kami dan
memberi tahu kami bahwa PhpStan, seperti Psalm, memiliki situs web dengan "kotak pasir":
https://phpstan.org/ . Ini sangat nyaman untuk laporan bug: Anda mereproduksi kesalahan dan memberikan tautan di GitHub.
Phan
Dikembangkan oleh Etsy. Komitmen pertama dari Rasmus Lerdorf.
Dari ketiga pertanyaan tersebut, Phan adalah satu-satunya penganalisa statis
nyata (dalam arti bahwa itu tidak mengeksekusi file Anda - itu mem-parsing
seluruh basis kode Anda, dan kemudian menganalisis apa yang Anda katakan). Bahkan untuk menganalisis beberapa file dalam basis kode kami, diperlukan sekitar 6 GB RAM, dan proses ini memakan waktu empat hingga lima menit. Tetapi kemudian analisis lengkap dari seluruh basis kode membutuhkan waktu sekitar enam hingga tujuh menit. Sebagai perbandingan, Mazmur menganalisisnya dalam beberapa puluh menit. Dan dari PHPStan kami tidak dapat mencapai analisis lengkap dari seluruh basis kode sama sekali karena termasuk kelas yang disertakan.
Pengalaman Phan ada dua. Di satu sisi, ini adalah penganalisis paling berkualitas dan stabil, ia menemukan banyak dan ada sedikit masalah dengan itu ketika perlu untuk menganalisis seluruh basis kode. Di sisi lain, ia memiliki dua fitur yang tidak menyenangkan.
Di bawah tenda, Phan menggunakan ekstensi php-ast. Rupanya, ini adalah salah satu alasan mengapa analisis seluruh basis kode relatif cepat. Tetapi php-ast menunjukkan representasi internal dari pohon AST seperti yang muncul dalam PHP itu sendiri. Dan dalam PHP itu sendiri, pohon AST tidak mengandung informasi tentang komentar yang terletak di dalam fungsi. Yaitu, jika Anda menulis sesuatu seperti:
function doSomething($type) { $obj = MyFactory::createObjectByType($type); … }
kemudian di dalam pohon AST terdapat informasi tentang PHPDoc eksternal untuk fungsi
doSomething()
, tetapi tidak ada informasi bantuan PHPDoc di dalam fungsi tersebut. Dan, karenanya, Phan juga tidak tahu apa-apa tentangnya. Ini adalah penyebab paling umum dari
false-positive
di Phan. Ada
beberapa rekomendasi tentang cara memasukkan tooltips (melalui string atau assert-s), tetapi, sayangnya, mereka sangat berbeda dari yang biasa digunakan oleh programmer kami. Sebagian, kami memecahkan masalah ini dengan menulis plugin untuk Phan. Tetapi plugin akan dibahas di bawah ini.
Fitur kedua yang tidak menyenangkan adalah bahwa Phan tidak menganalisis properti objek dengan baik. Berikut ini sebuah contoh:
class A { private $a; public function __construct(string $a = null) { $this->a = $a; } public function doSomething() { if ($this->a && strpos($this->a, 'a') === 0) { var_dump("test1"); } } }
Dalam contoh ini, Phan akan memberi tahu Anda bahwa dalam strpos Anda dapat melewati nol. Anda dapat mempelajari lebih lanjut tentang masalah ini di sini:
https://github.com/phan/phan/issues/204 .
Ringkasan Meskipun ada beberapa kesulitan, Phan adalah perkembangan yang sangat keren dan bermanfaat. Selain dua jenis
false-positive
, ia hampir tidak membuat kesalahan, atau membuat kesalahan, tetapi pada beberapa kode yang benar-benar kompleks. Kami juga menyukai konfigurasi ini dalam file PHP - ini memberikan fleksibilitas. Phan juga tahu cara bekerja sebagai server bahasa, tetapi kami tidak menggunakan fitur ini, karena PHPStorm sudah cukup bagi kami.
Plugin
Phan memiliki API pengembangan plugin yang dikembangkan dengan baik. Anda dapat menambahkan cek Anda sendiri, meningkatkan inferensi jenis untuk kode Anda. API ini
memiliki dokumentasi , tetapi khususnya keren bahwa ada plugin yang berfungsi di dalamnya yang dapat digunakan sebagai contoh.
Kami berhasil menulis dua plugin. Yang pertama dimaksudkan untuk pemeriksaan satu kali. Kami ingin mengevaluasi seberapa siap kode kami untuk PHP 7.3 (khususnya, untuk mengetahui apakah ia memiliki konstanta
case-insensitive
). Kami hampir yakin bahwa tidak ada konstanta seperti itu, tetapi apa pun bisa terjadi dalam 12 tahun - itu harus diperiksa. Dan kami menulis sebuah plugin untuk Phan yang akan bersumpah jika parameter ketiga digunakan dalam
define()
.
Plugin ini sangat sederhana <?php declare(strict_types=1); use Phan\AST\ContextNode; use Phan\CodeBase; use Phan\Language\Context; use Phan\Language\Element\Func; use Phan\PluginV2; use Phan\PluginV2\AnalyzeFunctionCallCapability; use ast\Node; class DefineThirdParamTrue extends PluginV2 implements AnalyzeFunctionCallCapability { public function getAnalyzeFunctionCallClosures(CodeBase $code_base) : array { $define_callback = function ( CodeBase $code_base, Context $context, Func $function, array $args ) { if (\count($args) < 3) { return; } $this->emitIssue( $code_base, $context, 'PhanDefineCaseInsensitiv', 'Define with 3 arguments', [] ); }; return [ 'define' => $define_callback, ]; } } return new DefineThirdParamTrue();
Di Phan, berbagai plugin dapat digantung pada acara yang berbeda. Khususnya, plugin dengan antarmuka
AnalyzeFunctionCallCapability
dipicu ketika panggilan fungsi diuraikan. Di plugin ini, kami membuatnya sehingga ketika kami memanggil fungsi
define()
, fungsi anonim kami dipanggil, yang memeriksa yang
define()
tidak
define()
lebih dari dua argumen. Kemudian kami baru saja memulai Phan, menemukan semua tempat di mana
define()
dipanggil dengan tiga argumen, dan memastikan bahwa kami tidak memiliki
case-insensitive-
.
Menggunakan plugin, kami juga sebagian menyelesaikan masalah
false-positive
ketika Phan tidak melihat petunjuk PHPDoc di dalam kode.
Kita sering menggunakan metode pabrik yang mengambil konstanta sebagai input dan membuat objek darinya. Seringkali kode terlihat seperti ini:
$Object = \Objects\Factory::create(\Objects\Config::MY_CONTROLLER);
Phan tidak mengerti petunjuk PHPDoc tersebut, tetapi dalam kode ini kelas objek dapat diperoleh dari nama konstanta yang diteruskan ke metode
create()
. Phan memungkinkan Anda untuk menulis sebuah plugin yang menyala ketika menganalisis nilai kembali suatu fungsi. Dan dengan plugin ini, Anda dapat memberi tahu penganalisa jenis fungsi yang dikembalikan dalam panggilan ini.
Contoh dari plugin ini lebih kompleks. Tetapi ada contoh yang baik dalam kode Phan di
vendor/phan/phan/src/Phan/Plugin/Internal/DependentReturnTypeOverridePlugin.php.
Secara keseluruhan, kami sangat senang dengan alat analisa Phan.
false-positive
tercantum di atas kami pelajari sebagian (dalam kasus sederhana, dengan kode sederhana) untuk difilter. Setelah itu, Phan menjadi penganalisis referensi. Namun, kebutuhan untuk segera menguraikan seluruh basis kode (waktu dan banyak memori) masih mempersulit proses implementasinya.
Mazmur
Mazmur adalah pengembangan oleh Vimeo. Jujur, saya bahkan tidak tahu bahwa Vimeo menggunakan PHP sampai saya melihat Mazmur.
Penganalisa ini adalah yang termuda dari tiga kami. Ketika saya membaca berita bahwa Vimeo merilis Mazmur, saya bingung: "Mengapa berinvestasi dalam Mazmur jika Anda sudah memiliki Phan dan PHPStan?" Tetapi ternyata Mazmur memiliki fitur-fiturnya sendiri yang bermanfaat.
Mazmur mengikuti jejak PHPStan: Anda juga dapat memberikan daftar file untuk dianalisis, dan itu akan menganalisisnya, dan menghubungkan kelas-kelas yang tidak ditemukan dengan memuat-otomatis. Pada saat yang sama, itu
hanya menghubungkan kelas yang tidak ditemukan, dan file yang kami minta analisis tidak akan dimasukkan (ini berbeda dari PHPStan). Konfigurasi disimpan dalam file XML (bagi kami, ini lebih cenderung minus, tetapi tidak terlalu kritis).
Mazmur memiliki
situs berpasir di mana Anda dapat menulis kode PHP dan menganalisisnya. Ini sangat nyaman untuk laporan bug: Anda mereproduksi kesalahan di situs dan memberikan tautan di GitHub. Dan omong-omong, situs ini menjelaskan semua jenis kesalahan yang mungkin terjadi. Sebagai perbandingan: di PHPStan kesalahan tidak memiliki tipe, dan di Phan ada kesalahan, tetapi tidak ada daftar tunggal yang dapat ditemukan.
Kami juga menyukai itu ketika menghasilkan kesalahan, Mazmur segera menunjukkan baris kode di mana mereka ditemukan. Ini
sangat menyederhanakan membaca laporan.
Tetapi mungkin fitur yang paling menarik dari Mazmur adalah tag PHPDoc khusus, yang memungkinkan Anda untuk meningkatkan analisis (terutama definisi jenis). Kami daftar yang paling menarik dari mereka.
@ mazmur-abaikan-nullable-return
Itu terjadi bahwa secara formal suatu metode dapat mengembalikan
null
, tetapi kode sudah diatur sedemikian rupa sehingga ini tidak pernah terjadi. Dalam hal ini sangat nyaman bahwa Anda dapat menambahkan petunjuk PHPDoc seperti itu ke metode / fungsi - dan Mazmur akan menganggap bahwa
null
tidak dikembalikan.
Petunjuk serupa ada untuk false:
@psalm-ignore-falsable-return
.
Jenis untuk penutupan
Jika Anda pernah tertarik pada pemrograman fungsional, Anda mungkin telah memperhatikan bahwa seringkali suatu fungsi dapat mengembalikan fungsi lain atau mengambil beberapa fungsi sebagai parameter. Dalam PHP, gaya ini bisa sangat membingungkan bagi kolega Anda, dan salah satu alasannya adalah PHP tidak memiliki standar untuk mendokumentasikan fungsi tersebut. Sebagai contoh:
function my_filter(array $ar, \Closure $func) { … }
Bagaimana seorang programmer dapat memahami antarmuka yang memiliki fungsi dalam parameter kedua? Parameter apa yang harus diambil? Apa yang harus dia kembalikan?
Mazmur mendukung sintaks untuk menggambarkan fungsi dalam PHPDoc:
function my_filter(array $ar, \Closure $func) { … }
Dengan deskripsi seperti itu, sudah jelas bahwa Anda harus meneruskan fungsi anonim ke
my_filter
, yang akan menerima int dan mengembalikan bool. Dan, tentu saja, Mazmur akan memverifikasi bahwa Anda memiliki fungsi seperti itu yang diteruskan dalam kode Anda.
Enum
Misalkan Anda memiliki fungsi yang mengambil parameter string, dan Anda hanya dapat melewatkan string tertentu di sana:
function isYes(string $yes_or_no) : bool { $yes_or_no = strtolower($yes_or_no) switch($yes_or_no) { case 'yes': return true; case 'no': return false; default: throw new \InvalidArgumentException(…); } }
Mazmur memungkinkan Anda untuk menggambarkan parameter fungsi ini seperti ini:
function isYes(string $yes_or_no) : bool { … }
Dalam hal ini, Mazmur akan mencoba memahami nilai spesifik apa yang diteruskan ke fungsi ini, dan melempar kesalahan jika ada nilai selain
Yes
dan
No
Baca lebih lanjut tentang enum di
sini .
Ketik alias
Sebelumnya dalam deskripsi
array shapes
saya menyebutkan bahwa meskipun analisis memungkinkan Anda untuk menggambarkan struktur array, itu sangat tidak nyaman untuk menggunakannya, karena deskripsi array harus disalin di tempat yang berbeda. Solusi yang benar, tentu saja, adalah menggunakan kelas daripada array. Tetapi dalam kasus warisan bertahun-tahun, ini tidak selalu mungkin.
Faktanya, masalah muncul tidak hanya dengan array, tetapi dengan tipe apa pun yang bukan kelas:- sebuah array;
- penutupan;
- jenis serikat (misalnya, beberapa kelas atau kelas dan jenis lainnya);
- enum.
Setiap tipe seperti itu, jika digunakan di beberapa tempat, perlu diduplikasi dalam PHPDoc dan, jika diubah, karenanya, diperbaiki di mana-mana. Karena itu, Mazmur memiliki sedikit perbaikan dalam hal ini. Anda dapat mendeklarasikan alias untuk suatu jenis dan kemudian menggunakan yang ini di PHPDoc alias
. Sayangnya, ada batasan: ini berfungsi dalam kerangka file PHP tunggal. Tapi ini sudah menyederhanakan deskripsi tipe. Benar, hanya untuk Mazmur.Templat alias generik
Pertimbangkan kesempatan ini sebagai contoh. Katakanlah Anda memiliki fungsi seperti ini: function identity($x) { return $x; }
? ? ?
, , , —
mixed
, .
mixed
— . , . ,
identity()
/ , : , . -. , :
$i = 5;
(int)
, ,
$y
(
int
).
? Psalm PHPDoc-:
function identity($x) { $return $x; }
templates Psalm , / .
Psalm templates:
—
vendor/vimeo/psalm/src/Psalm/Stubs/CoreGenericFunctions.php ;
—
vendor/vimeo/psalm/src/Psalm/Stubs/CoreGenericClasses.php .
Phan, :
https://github.com/phan/phan/wiki/Generic-Types .
, Psalm . , «» . , Psalm , , Phan PHPStan. .
PHPStorm
: , . , , .
. Phan, language server. PHPStorm, , .
, , PHPStorm ( ), . — Php Inspections (EA Extended). — , , . , . , scopes - scopes.
,
deep-assoc-completion . .
Badoo
?
, .
, . , ,
git diff
/ , , () . , .
, : -
git diff
. . , . . , , , , .
, , :

false-positive
. , , Phan , , . , - Phan , , .
QA
:
— , , , . :
- 100% ( , );
- , code review;
- , .
strict types
. ,
strict types
, :
- ,
strict types
, ;
- , (, , );
- , PHP (,
union types
, PHP);
strict types
, .
:
, . .
-, , , - , .
-, , — , , PHPDoc. — .
-, . , - , PHPDoc. :)
, , . , .