Plugin WordPress smallPict mengonversi gambar yang diunggah ke WebP dan AVIF, lalu menyajikannya ke pengunjung. Saat kami memberi tag pada sebuah rilis, GitHub Actions meng-commit rilis itu ke repositori SVN WordPress.org, dan dari sana rilis tersebut sampai ke setiap website yang memasang plugin ini. Website yang mengaktifkan pembaruan otomatis langsung memasangnya tanpa ada yang meninjau perubahannya. Plugin ini mendeklarasikan Requires PHP: 7.4 dan Requires at least: 5.9, jadi satu build harus berjalan di shared hosting yang masih memakai PHP 7.4 sekaligus di container yang menjalankan PHP 8.5.
Fatal error di sebuah plugin bisa membuat wp-admin tidak bisa dibuka. Satu nilai yang tidak di-escape di halaman pengaturan berarti bug XSS di semua website sekaligus. Satu pengecekan nonce yang terlewat membuat aksi admin menjadi sasaran CSRF. Tidak satu pun dari masalah ini bisa ditambal diam-diam: perbaikannya butuh rilis baru, dan sebagian website tetap memakai versi yang rusak sampai rilis itu tiba.
Artikel ini menjelaskan pipeline CI/CD plugin WordPress kami: WordPress Coding Standards (WPCS), PHPStan, dan tes otomatis yang berjalan di tujuh versi PHP, serta gate pra-deploy di depan WordPress.org. Semua yang dibahas di bawah diambil dari repositori smallpict-wp pada rilis 1.2.9.
Poin penting
- Setiap pull request ke
mastermenjalankan WPCS, PHPStan, dan 9 skrip tes otomatis di PHP 7.4, 8.0, 8.1, 8.2, 8.3, 8.4, dan 8.5 secara paralel di GitHub Actions.- WPCS mewajibkan input yang disanitasi, output yang di-escape, nonce, dan prepared query. PHPStan di level 5 menemukan error tipe dan cabang mati berdasarkan stub WordPress, tanpa menjalankan WordPress.
- Tag rilis menjalankan WPCS sekali lagi di ketujuh versi PHP, dan job yang meng-commit ke SVN WordPress.org baru mulai setelah ketujuhnya lolos.
Gambaran singkat pipeline CI/CD plugin WordPress
Dua workflow GitHub Actions menjaga plugin ini.
| Workflow | Dipicu oleh | Job | Pemeriksaan |
|---|---|---|---|
phpcs.yml (PHP Code Quality & Standards) | push ke master atau develop, pull request ke master | satu job per versi PHP: 7.4, 8.0, 8.1, 8.2, 8.3, 8.4, 8.5 | WPCS, PHPStan, tes otomatis |
deploy-all.yml (Deploy to WordPress.org) | push tag v* | WPCS di masing-masing dari 7 versi PHP, lalu deploy | WPCS, lalu build dan commit SVN |
Catatan tentang cakupan versi PHP: Screenshot di artikel ini menampilkan run CI kami sebelumnya yang mencakup PHP 7.4 hingga 8.3. Kini matrix tes GitHub Actions kami sudah diperluas hingga mencakup PHP 7.4 sampai PHP 8.5, sehingga plugin tetap kompatibel dengan runtime PHP modern tanpa regresi sedikit pun di lingkungan lama.
Semua tooling dipasang sebagai dev dependency Composer, jadi tidak ada yang ikut terkirim ke pengguna:
"require-dev": { "wp-coding-standards/wpcs": "^3.4.1", "phpcsstandards/php_codesniffer": "^3.0", "phpstan/phpstan": "^2.1", "szepeviktor/phpstan-wordpress": "^2.0 || ^1.3.5", "phpstan/extension-installer": "^1.4"}Matrix testing multi-versi PHP di GitHub Actions
Apa yang berubah antara PHP 7.4 dan PHP 8.5
PHP 7.4 dan PHP 8.5 menjalankan source plugin yang sama dengan cara berbeda. Beberapa perubahan yang memengaruhi kode plugin WordPress:
- PHP 8.0 membuat banyak fungsi internal melempar
TypeErroruntuk argumen bertipe salah, padahal 7.4 mengembalikannulldisertai warning. Kode yang mengoper array kestrlen()tetap jalan di 7.4 dan berhenti di 8.0. - PHP 8.0 mengubah cara angka dibandingkan dengan string non-numerik.
0 == "abc"bernilaitruedi 7.4 danfalsedi 8.0, sehingga sebagian perbandingan longgar pada nilai option berbalik tanpa ada tanda apa pun. - PHP 8.0 menambahkan
str_starts_with()dan fungsi sejenisnya. Image processor kami memanggilstr_starts_with(). Di PHP 7.4 itu hanya berfungsi karena WordPress 5.9 ke atas menyertakan polyfill, dan hal itu dijamin olehRequires at least: 5.9kami. - PHP 8.1 menandai deprecated pengiriman
nullke parameter non-nullable pada fungsi internal.strtolower( wp_parse_url( $url, PHP_URL_HOST ) )aman di 7.4, tetapi di 8.1 mencatat deprecation saat$urlberupa URL relatif dan host-nyanull. Sebagian hosting mencetak deprecation langsung ke halaman. - PHP 8.2 menandai deprecated dynamic property. Mengisi property yang tidak dideklarasikan di salah satu class Anda sendiri akan mencatat deprecation di setiap request.
Masing-masing masalah ini baru muncul ketika kode dianalisis atau dijalankan di versi PHP yang perilakunya berbeda. Karena itulah pipeline menjalankan setiap pull request ke master di ketujuh versi sekaligus.
Matrix GitHub Actions

Gambar 1: Workflow PHP Code Quality & Standards untuk merge pekerjaan 1.2.0 ke master. Setiap versi PHP adalah job tersendiri, dan job PHP 8.3 menampilkan langkah yang dijalankan setiap job: PHPCS, PHPStan, lalu tes otomatis.
Catatan tentang cakupan versi PHP: Screenshot di atas menampilkan run CI kami sebelumnya yang mencakup PHP 7.4 hingga 8.3. Kini matrix tes GitHub Actions kami sudah diperluas hingga mencakup PHP 7.4 sampai PHP 8.5, sehingga plugin tetap kompatibel dengan runtime PHP modern tanpa regresi sedikit pun di lingkungan lama.
jobs: code-quality: name: WPCS & PHPStan (PHP ${{ matrix.php-version }}) runs-on: ubuntu-26.04 strategy: matrix: php-version: ['7.4', '8.0', '8.1', '8.2', '8.3', '8.4', '8.5'] steps: - uses: actions/checkout@v7 - uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php-version }} tools: composer:v2, cs2pr ini-values: memory_limit=2G coverage: none - run: composer install --prefer-dist --no-progress --no-interaction - name: Run PHPCS (WordPress Coding Standards) run: vendor/bin/phpcs -q --standard=phpcs.xml.dist --report=checkstyle | cs2pr - name: Run PHPStan (WordPress Static Analysis) run: vendor/bin/phpstan analyse --memory-limit=2G - name: Run the standalone tests run: | for t in tests/test_*.php; do echo "== $t" php "$t" || exit 1 donefail-fast tetap memakai default true, jadi saat satu versi gagal, GitHub membatalkan job yang masih berjalan. Pull request yang rusak jadi murah ongkosnya, dengan konsekuensi hasil semua versi tidak terlihat dalam satu run yang sama.
Apa yang dibuktikan matrix, dan apa yang tidak
Ketiga pemeriksaan tidak mendapat manfaat yang sama dari matrix. PHPStan menganalisis kode untuk versi PHP tempat ia berjalan, dan tes mengeksekusi kode di versi itu, jadi keduanya bisa memberi jawaban berbeda di tiap job. PHPCS bekerja pada token dan dalam praktiknya memberi jawaban yang sama di ketujuh versi; di matrix, fungsinya terutama membuktikan bahwa toolchain bisa dipasang dan berjalan di tiap versi. Memeriksa sintaks dan fungsi terhadap rentang versi PHP dalam sekali jalan adalah tugas PHPCompatibility, yang belum masuk ruleset kami.
Otomatisasi WordPress Coding Standards (WPCS)
Sniff keamanan yang penting untuk plugin
WPCS adalah kumpulan sniff PHP_CodeSniffer yang ditulis khusus untuk WordPress. Aturan yang paling penting untuk keamanan plugin ada di ruleset WordPress itu sendiri:
WordPress.Security.ValidatedSanitizedInputmenandai setiap pembacaan$_POST,$_GET,$_REQUEST, atau$_COOKIEyang tidak di-unslash dan disanitasi.WordPress.Security.EscapeOutputmenandai setiapechoatauprintdari nilai yang belum melewati fungsi escaping sepertiesc_html(),esc_attr(), atauesc_url().WordPress.Security.NonceVerificationmenandai handler yang membaca data request tanpa memverifikasi nonce.WordPress.DB.PreparedSQLdanPreparedSQLPlaceholdersmenandai query yang dibangun lewat penggabungan string alih-alih$wpdb->prepare(), serta placeholder yang tidak cocok dengan argumennya.
Ruleset WPCS kami
phpcs.xml.dist menambahkan WordPress-Extra dan WordPress-Docs, menetapkan text domain untuk sniff terjemahan, dan mewajibkan setiap fungsi, class, dan konstanta global memakai prefix kami, sehingga plugin ini tidak bisa bentrok dengan nama milik plugin lain di website yang sama:
<rule ref="WordPress"> <exclude name="Universal.Arrays.DisallowShortArraySyntax"/></rule><rule ref="WordPress-Extra"/><rule ref="WordPress-Docs"/>
<rule ref="WordPress.WP.I18n"> <properties> <property name="text_domain" type="array"> <element value="smallpict"/> </property> </properties></rule>
<rule ref="WordPress.NamingConventions.PrefixAllGlobals"> <properties> <property name="prefixes" type="array"> <element value="smallpict"/> <element value="SmallPict"/> <element value="SMALLPICT"/> </property> </properties></rule>Handler AJAX yang lolos WPCS
Handler dari plugin ini memulihkan media yang di-offload secara bertahap per batch. Setiap batas input ditangani di baris-baris pertamanya:
public function ajax_restore_offloaded() { if ( ! check_ajax_referer( 'smallpict_restore_offloaded', 'nonce', false ) || ! current_user_can( 'manage_options' ) ) { wp_send_json_error( array( 'message' => 'forbidden' ), 403 ); } global $wpdb; $after = isset( $_POST['after'] ) ? absint( wp_unslash( $_POST['after'] ) ) : 0; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- admin action, the rows change on every call. $ids = $wpdb->get_col( $wpdb->prepare( "SELECT DISTINCT post_id FROM {$wpdb->postmeta} WHERE meta_key = %s AND meta_value = %s AND post_id > %d ORDER BY post_id ASC LIMIT %d", '_smallpict_offloaded', '1', $after, self::RESTORE_BATCH ) ); // ...}Nonce dan capability diperiksa bersamaan, karena nonce yang valid hanya membuktikan bahwa request berasal dari halaman admin kami, bukan bahwa user boleh melakukan aksi tersebut. $_POST['after'] di-unslash dan dikonversi dengan absint() sebelum sampai ke query, dan query tetap melewati $wpdb->prepare() meskipun nilainya sudah berupa integer.
Suppression wajib menyebut nama sniff dan alasannya
Baris phpcs:ignore di atas sengaja ditulis. WPCS memperingatkan direct database query tanpa object caching. Saran itu tepat untuk pembacaan di front-end, tetapi salah untuk aksi admin yang baris datanya berubah di setiap pemanggilan. Aturan kami: setiap ignore harus menyebut nama sniff yang persis dan memberi alasan setelah --. // phpcs:ignore tanpa keterangan tidak akan lolos review.
Di CI, PHPCS menulis laporan checkstyle dan cs2pr mengubahnya menjadi anotasi di diff pull request, jadi reviewer melihat setiap pelanggaran tepat di baris penyebabnya, bukan di log.
Menangkap kesalahan logika dengan analisis statis PHPStan
WPCS membaca kode baris per baris. WPCS tahu sebuah echo tidak memanggil fungsi escaping, tetapi tidak tahu tipe apa yang dipegang sebuah variabel atau apakah sebuah cabang bisa dieksekusi. PHPStan membangun model tipe yang mengalir di setiap fungsi dan melaporkan kode yang bertentangan dengan model itu: memanggil method yang tidak ada, mengoper string|false ke tempat yang membutuhkan string, membandingkan nilai yang tidak mungkin sama, serta kondisi yang selalu true atau selalu false.
Memakai PHPStan dengan WordPress tanpa menjalankan WordPress
szepeviktor/phpstan-wordpress memberi PHPStan signature fungsi dan class inti WordPress melalui file stub, ditambah beberapa type extension khusus WordPress. phpstan/extension-installer mendaftarkannya secara otomatis. PHPStan membaca deklarasi tersebut dan tidak pernah memuat WordPress maupun database.
Konfigurasi PHPStan kami
parameters: level: 5 paths: - smallpict.php - includes/ - admin/ treatPhpDocTypesAsCertain: false bootstrapFiles: - phpstan-bootstrap.phpLevel 5 memeriksa tipe setiap argumen yang dioper ke fungsi atau method, di atas pemeriksaan level-level di bawahnya untuk class, method, dan property yang tidak dikenal, return type, dan dead code. treatPhpDocTypesAsCertain: false memberi tahu PHPStan agar tidak menganggap docblock @param kami sebagai bukti. WordPress meneruskan apa pun yang dikembalikan sebuah filter, jadi parameter yang didokumentasikan sebagai int tetap bisa datang berupa string dari plugin lain, dan kami ingin PHPStan mempertahankan pengecekan defensif yang menangani kasus itu. File bootstrap mendefinisikan konstanta plugin (SMALLPICT_PATH, SMALLPICT_URL, dan lainnya), yang normalnya baru diset saat runtime.
![Log analisis statis PHPStan untuk WordPress di GitHub Actions: langkah WPCS bersih, PHPStan menganalisis 11 dari 11 file plugin dengan "[OK] No errors"](/blog-assets/02_phpcs_phpstan_log.png)
Gambar 2: Langkah PHPCS dan PHPStan pada job PHP 8.3. PHPCS tidak mencetak apa pun jika tidak ada yang perlu dilaporkan. PHPStan menganalisis 11 file plugin (file utama plugin dan 10 class di includes/) dan tidak melaporkan error.
Temuan PHPStan yang nyata: baris 626 di image processor
Pada 2 Oktober 2026, pipeline gagal di PHPStan dengan satu error:

Gambar 3: Job PHP 8.2 berhenti di langkah PHPStan dengan booleanAnd.rightAlwaysTrue di baris 626 file includes/class-smallpict-image-processor.php. Saat itu plugin memiliki 9 file di path yang dianalisis.
Kode ini adalah langkah terakhir sebuah konversi. Jika pengaturan Format keluaran plugin bernilai WebP atau AVIF, file hasil konversi menjadi attachment dan file unggahan asli dihapus untuk menghemat ruang disk. Pilihan lainnya, "AVIF, dengan WebP sebagai cadangan" dan "Pertahankan format asli", tetap menyimpan file JPEG dan PNG asli. Ini cabang penghapusannya:
// C. Pure 1-to-1 Mode: Delete raw uncompressed source file to free 100% hosting space.if ( ( $is_single_webp || $is_single_avif ) && $canonical_path !== $main_file_path && file_exists( $main_file_path ) ) { wp_delete_file( $main_file_path );}Klaim PHPStan adalah bahwa file_exists( $main_file_path ) selalu bernilai true di titik itu. Alasannya ada 150 baris sebelumnya, di bagian atas method yang sama:
$main_file_path = get_attached_file( $attachment_id );if ( ! $main_file_path || ! file_exists( $main_file_path ) ) { return new WP_Error( 'file_missing', 'File not found on disk.' );}Method ini hanya berlanjut jika file-nya ada, dan PHPStan membawa fakta itu sampai ke baris 626.
Kami tidak langsung menerima temuan itu begitu saja. file_exists() bertanya ke filesystem, dan jawabannya bisa berubah di antara dua pemanggilan. Secara default PHPStan mengingat hasil pemanggilan seperti itu (opsi rememberPossiblyImpureFunctionValues), dengan asumsi tidak ada yang mengubah disk di antaranya. Sebelum menghapus pengecekan itu, kami memastikan dua hal dengan membaca kodenya:
- Tidak ada kode di antara guard dan baris 626 yang menghapus atau memindahkan file sumber. Proses konversi menulis setiap output ke file sementara lalu memindahkannya ke path baru, dan satu-satunya pemanggilan
wp_delete_file()pada path itu hanya menghapus file sementara. Langkah offload, yang memang menghapus file lokal, berjalan setelah baris 626. wp_delete_file()tetap aman jika file tidak ada. Fungsi ini meneruskan path melalui filterwp_delete_filedan memanggil@unlink(), yang tidak menimbulkan masalah apa pun jika file-nya sudah hilang.
Setelah keduanya terkonfirmasi, kondisi itu adalah dead code, dan dead code di cabang penghapusan menyesatkan pembaca berikutnya, seolah-olah file bisa hilang di tengah method. Perbaikannya menghapus kondisi tersebut:
-if ( ( $is_single_webp || $is_single_avif ) && $canonical_path !== $main_file_path && file_exists( $main_file_path ) ) {+if ( ( $is_single_webp || $is_single_avif ) && $canonical_path !== $main_file_path ) { wp_delete_file( $main_file_path ); }Temuan ini bukan crash. Nilainya ada pada apa yang diungkapkannya tentang method itu: asumsi kode sudah melenceng dari strukturnya, tepat di satu-satunya cabang yang menghapus file milik pengguna. Pelajaran yang kami ambil lebih sempit daripada "percayai saja analyzer". Laporan always-true atau always-false pada fungsi filesystem, jaringan, atau waktu layak diperiksa manual, yaitu apa yang terjadi di antara dua pemanggilan itu, sebelum kondisinya dihapus.
Tes otomatis untuk plugin WordPress tanpa PHPUnit
Langkah terakhir setiap job matrix menjalankan file-file di tests/. Ini bukan tes PHPUnit. Setiap file adalah skrip PHP biasa yang mendefinisikan stub minimal untuk fungsi WordPress yang dibutuhkannya (get_option(), update_option(), sanitize_key(), dan seterusnya), memuat class plugin yang diuji, menjalankannya, lalu keluar dengan kode non-zero pada kegagalan pertama:
for t in tests/test_.php do echo "== $t" php "$t" exit 1doneSaat ini ada 9 skrip seperti itu. Skrip-skrip ini mencakup jalur yang kesalahannya bisa membuat pelanggan kehilangan data atau uang: pipeline konversi WebP dan AVIF beserta fallback-nya, penyajian gambar, offload dan penghapusan file di cloud storage, pemulihan file yang di-offload per batch, perilaku saat sebuah akun berhenti membayar, kuota penyimpanan, dan uninstall. Tes uninstall menjalankan query uninstall yang sebenarnya terhadap database SQLite in-memory dan memeriksa apa yang tersisa:
check( array( '_smallpict_cdn_base', '_smallpict_cdn_key', '_smallpict_offloaded', '_smallpictx', '_wp_attached_file' ) === $meta, 'offload markers and foreign meta remain (got ' . implode( ',', $meta ) . ')');Assertion itu melindungi keputusan yang mudah dibatalkan tanpa sengaja. Uninstall plugin menghapus pengaturannya, tetapi mempertahankan post meta yang mencatat file mana yang hanya tersimpan di cloud storage, sehingga website yang memasang ulang plugin masih bisa menemukan gambarnya. DELETE ... LIKE '_smallpict%' yang ceroboh akan menghapus penanda tersebut, dan tes ini gagal jika itu terjadi.
Kami memilih skrip biasa ketimbang test suite PHPUnit WordPress demi kecepatan dan biaya setup: skrip ini tidak butuh server database maupun checkout WordPress, dan kesembilannya selesai dalam waktu kurang dari satu detik, jadi menjalankannya di tujuh versi PHP nyaris tidak menambah waktu sebuah pull request. Konsekuensinya, stub hanya memodelkan apa yang kami tulis di dalamnya. Perilaku yang bergantung pada core WordPress yang sebenarnya, seperti penanganan metadata milik media library, tidak tercakup oleh tes ini.
Gate pra-deploy ke WordPress.org
Rilis dikirim melalui workflow terpisah. Push tag seperti v1.2.9 memicunya, dan job yang meng-commit ke SVN WordPress.org mendeklarasikan dependensi pada matrix lint:
jobs: lint: name: WPCS Check (PHP ${{ matrix.php-version }}) runs-on: ubuntu-26.04 strategy: matrix: php-version: ['7.4', '8.0', '8.1', '8.2', '8.3', '8.4', '8.5'] steps: - uses: actions/checkout@v7 - uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php-version }} - run: composer install --prefer-dist --no-progress --no-interaction - name: PHP CodeSniffer Check run: vendor/bin/phpcs --standard=phpcs.xml.dist
deploy: name: Deploy needs: lint runs-on: ubuntu-26.04 permissions: contents: read steps: - uses: actions/checkout@v7 - uses: shivammathur/setup-php@v2 with: php-version: '7.4' - name: Build WP.org Version run: | chmod +x build-wporg.sh ./build-wporg.sh - name: Deploy to WordPress.org uses: 10up/action-wordpress-plugin-deploy@stable with: generate-zip: false env: SVN_USERNAME: ${{ secrets.SVN_USERNAME }} SVN_PASSWORD: ${{ secrets.SVN_PASSWORD }} SLUG: smallpict BUILD_DIR: build-wporg/smallpict ASSETS_DIR: .wordpress-orgneeds: lint berarti job deploy baru mulai jika ketujuh job lint berhasil. Jika WPCS gagal di versi mana pun, GitHub melewati deploy dan tidak ada commit SVN. Langkah build berjalan di PHP 7.4, versi tertua yang kami dukung. Token job ini hanya punya akses baca ke repositori; satu-satunya operasi tulisnya adalah ke SVN WordPress.org, dengan kredensial yang disimpan sebagai secret repositori.

Gambar 4: Workflow Deploy to WordPress.org. Kelima job WPCS Check berjalan lebih dulu; job Deploy di bawahnya baru berjalan setelah kelimanya lolos.
Gate ini menjalankan WPCS lagi pada commit yang diberi tag, alih-alih memercayai run pull request sebelumnya. Sebuah tag bisa menunjuk ke commit mana pun, termasuk commit yang di-push langsung atau commit yang pemeriksaannya tidak pernah selesai, dan workflow rilis tidak punya cara lain untuk mengetahui apa yang sedang dikirimnya.
Menjalankan pemeriksaan CI secara lokal
Script Composer membungkus perintah yang sama dengan yang dijalankan CI, jadi kegagalan bisa direproduksi sebelum push:
composer installcomposer lint # vendor/bin/phpcs --standard=phpcs.xml.distcomposer lint-fix # vendor/bin/phpcbf: fixes spacing and formatting sniffscomposer phpstan # vendor/bin/phpstan analysefor t in tests/test_.php do php "$t" break doneUntuk mereproduksi kegagalan dari satu job matrix, jalankan perintah yang sama di versi PHP tersebut, misalnya dengan image resmi setelah composer install:
docker run --rm -v "$PWD":/app -w /app php:7.4-cli \ sh -c 'vendor/bin/phpstan analyse --memory-limit=2G && for t in tests/test_*.php; do php "$t" || exit 1; done'phpcbf hanya memperbaiki apa yang bisa diperbaiki sniff secara mekanis. Temuan terkait escaping, sanitasi, dan nonce butuh orang untuk memutuskan fungsi mana yang sesuai dengan konteksnya, dan kami tidak memperbaikinya secara otomatis.
Yang belum dicakup pipeline
Ada tiga celah dalam daftar kami:
- Gate pra-deploy hanya menjalankan WPCS. PHPStan dan tes otomatis berjalan saat push ke
masterdandevelopserta pada pull request, tetapi tidak pada tag rilis. Menambahkan kedua langkah itu ke joblintdi workflow deploy akan menutup celah ini. - Ruleset kami belum menyertakan PHPCompatibility. Menambahkan
phpcompatibility/phpcompatibility-wpdengantestVersionbernilai7.4-akan menandai sintaks dan fungsi yang tidak ada di 7.4 dalam satu kali run. - Tes memakai stub, bukan instalasi WordPress yang sebenarnya. Job integrasi terhadap WordPress 5.9 (versi minimum kami) dan rilis terbaru akan mencakup perilaku core yang tidak bisa dicakup stub.
FAQ
Kenapa matrix testing dari PHP 7.4 hingga 8.5 penting untuk plugin WordPress?
WordPress.org mengirimkan rilis yang sama ke setiap website, dan header plugin (Requires PHP: 7.4) adalah satu-satunya penyaring versi PHP mana yang boleh memasangnya. Antara 7.4 dan 8.5, PHP mengubah cara fungsi internal menangani tipe argumen yang salah, cara angka dibandingkan dengan string, dan pola mana yang memicu deprecation, sehingga kode yang benar di satu versi bisa fatal atau memunculkan warning di versi lain. Run CI di satu versi PHP saja hanya membuktikan plugin berjalan di versi itu. Menjalankan PHPStan dan tes di setiap versi yang didukung menangkap perbedaan tersebut sebelum rilis sampai ke pengguna.
Apakah WPCS menggantikan audit keamanan konvensional?
Tidak. WPCS memeriksa pola di baris tempat pola itu muncul: variabel request yang dibaca tanpa sanitasi, echo tanpa escaping, handler tanpa pengecekan nonce, query tanpa $wpdb->prepare(). WPCS tidak bisa menilai apakah sebuah handler memeriksa capability yang tepat, apakah fungsi escaping cocok dengan konteks output, apakah request dari sisi server mengarah ke URL yang dikendalikan user, atau apakah satu user bisa mengakses data user lain. Komentar phpcs:ignore juga bisa membungkam sebuah sniff. Kami memperlakukan WPCS sebagai batas minimum yang mencegah kesalahan yang sudah dikenal, dan meninjau perubahan yang menyangkut keamanan secara manual.
Bagaimana PHPStan menangani fungsi inti WordPress tanpa mengeksekusi kode?
PHPStan tidak pernah menjalankan kode yang dianalisisnya. PHPStan membaca deklarasi dan docblock untuk membangun model tipe. Untuk WordPress, szepeviktor/phpstan-wordpress menyediakan file stub: signature, tipe parameter, dan return type dari fungsi dan class inti WordPress, tanpa isi fungsinya. PHPStan membaca stub itu seolah-olah itu WordPress, sehingga bisa memeriksa apakah pemanggilan wp_delete_file() atau get_attached_file() mengirim dan menerima tipe yang benar. Konstanta yang didefinisikan WordPress atau plugin saat runtime dimasukkan ke file bootstrap, yang dimuat PHPStan sebelum analisis.
Plugin ini tersedia di WordPress.org dengan nama smallPict. Instalasi dan pengaturan Format keluaran dibahas di panduan instalasi dan referensi pengaturan gambar.