PSR, singkatan dari PHP Standard Recommendation, adalah rekomendasi teknis yang diterbitkan oleh PHP-FIG (PHP Framework Interoperability Group), yaitu wadah kolaborasi antarproyek PHP. Rekomendasinya mengatur titik temu antara kode yang ditulis pihak yang berbeda, sehingga pustaka dari sumber yang berlainan tetap dapat dipasangkan. Topik ini membahas tiga PSR yang paling sering digunakan sehari-hari, yaitu PSR-12 tentang gaya penulisan kode, PSR-3 tentang antarmuka pencatat log (logger), dan PSR-4 tentang pemetaan autoload. Setelah membaca, kelas baru dapat ditulis dengan gaya yang diterima ekosistem, logger buatan sendiri dapat menggantikan logger milik pustaka mana pun yang mengikuti standar, dan isi berkas pemetaan milik Composer dapat dipahami.
Gaya kode PSR-12#
PSR-12 adalah standar gaya penulisan kode yang menyeragamkan bentuk berkas PHP tanpa mengubah perilaku programnya. Aturan yang paling berdampak pada pekerjaan harian antara lain declare(strict_types=1); sebagai pernyataan pertama, satu kelas per berkas, visibilitas yang tercantum pada setiap anggota, kurung kurawal pembuka kelas dan metode yang berada pada baris tersendiri, serta daftar use yang ditulis setelah deklarasi namespace. Bentuk yang seragam membuat kode dari penulis yang berbeda tetap terbaca dengan cara yang sama.
Dua potongan berikut memuat kelas yang sama dengan dua gaya yang berbeda. Keduanya sah menurut PHP dan berperilaku identik ketika dijalankan. Perbedaannya ada pada kepatuhan terhadap PSR-12, dan potongan pertama melanggarnya di beberapa titik.
// tidak-dijalankan
// berkas ini sah menurut PHP, tetapi melanggar PSR-12
<?php
namespace Ukuran;
class konverter_satuan {
var $meter;
function __construct($meter){ $this->meter=$meter; }
function keSentimeter(){ return $this->meter*100; }
}Potongan pertama menyimpan properti dengan var dan mendeklarasikan metode tanpa visibilitas, padahal PSR-12 mewajibkan visibilitas eksplisit pada setiap anggota. Kurung kurawal pembuka kelas ditempatkan pada baris yang sama dengan deklarasinya, sementara standar menetapkan baris tersendiri untuk kelas dan metode. Nama kelasnya tidak mengikuti gaya PascalCase, dan declare(strict_types=1); tidak ada sama sekali.
// tidak-dijalankan
// berkas yang sama setelah ditulis ulang mengikuti PSR-12
<?php
declare(strict_types=1);
namespace Ukuran;
class KonverterSatuan
{
private float $meter;
public function __construct(float $meter)
{
$this->meter = $meter;
}
public function keSentimeter(): float
{
return $this->meter * 100;
}
}Potongan kedua memperbaiki seluruh titik tersebut. Urutan pernyataannya kini lengkap, yaitu declare, namespace, kemudian use apabila ada kelas yang diimpor. Setiap anggota memuat visibilitas dan tipe datanya, serta kurung kurawalnya berada pada baris sendiri. Kelas lengkap berikut dipasang di folder topik sebagai berkas PenghitungKata.php dengan gaya yang sama.
<?php
declare(strict_types=1);
namespace Teks;
class PenghitungKata
{
private string $teks;
public function __construct(string $teks)
{
$this->teks = $teks;
}
public function jumlahKata(): int
{
return str_word_count($this->teks);
}
public function kataTerpanjang(): string
{
$kata = str_word_count($this->teks, 1);
$terpanjang = '';
foreach ($kata as $item) {
if (strlen($item) > strlen($terpanjang)) {
$terpanjang = $item;
}
}
return $terpanjang;
}
}Kebenaran sintaks berkas tersebut diperiksa menggunakan opsi -l pada PHP, yaitu pemeriksaan sintaks cepat yang dikenal sebagai lint. Pemeriksaan ini membaca berkas, mencari error sintaks, dan tidak menjalankan kodenya. Perintahnya menampilkan nama berkas beserta status pemeriksaannya, sehingga berkas yang bermasalah langsung terlihat sebelum program dijalankan.
# periksa sintaks berkas kelas dengan pemeriksa bawaan PHP
php -l PenghitungKata.phpNo syntax errors detected in PenghitungKata.phpKeluaran di atas menyatakan tidak ada error sintaks, tetapi tidak menyatakan kepatuhan gaya. Pemeriksaan sintaks dan pemeriksaan gaya adalah dua hal yang berbeda, dan PSR-12 termasuk pemeriksaan gaya. Kepatuhan gaya baru dapat dinyatakan oleh alat yang memahami standarnya, bukan oleh penafsir PHP.
Log dengan PSR-3#
PSR-3 menetapkan Psr\Log\LoggerInterface, yaitu antarmuka yang menjadi kontrak bagi setiap pencatat log. Antarmuka ini memuat delapan metode bernama menurut level keparahannya, dari emergency() hingga debug(), ditambah satu metode umum log() yang menerima level apa pun. Komponen yang membutuhkan logger mencantumkan LoggerInterface sebagai dependensinya, sehingga implementasi logger dapat ditukar tanpa menyentuh kode pemanggil.
Antarmuka tersebut dipasang sebagai package psr/log, bersama trait LoggerTrait yang mendelegasikan delapan metode level ke log(). Dengan trait itu, logger buatan sendiri hanya perlu mengimplementasikan satu metode. Perintah pemasangannya sama seperti penambahan dependensi pada umumnya.
# pasang package psr/log yang memuat antarmuka PSR-3
composer require psr/log:^3.0 --no-interaction./composer.json has been created
Running composer update psr/log
Loading composer repositories with package information
Updating dependencies
Lock file operations: 1 install, 0 updates, 0 removals
- Locking psr/log (3.0.2)
Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 1 install, 0 updates, 0 removals
- Installing psr/log (3.0.2): Extracting archive
Generating autoload files
No security vulnerability advisories found.Kelas LoggerKonsol berikut mengimplementasikan LoggerInterface menggunakan LoggerTrait. Metode log() mencetak level beserta pesannya ke layar, sedangkan metode privat susunPesan() menggantikan penanda seperti {nama} di dalam pesan dengan nilai dari konteksnya, sebagaimana diatur PSR-3. Delapan metode level lainnya diperoleh dari trait, sehingga kontrak antarmukanya terpenuhi tanpa salinan kode yang berulang.
// tidak-dijalankan
// berkas kelas dimuat melalui uji-logger.php, bukan dijalankan sendiri
<?php
declare(strict_types=1);
namespace Aplikasi;
use Psr\Log\LoggerInterface;
use Psr\Log\LoggerTrait;
use Stringable;
class LoggerKonsol implements LoggerInterface
{
use LoggerTrait;
public function log(mixed $level, string|Stringable $message, array $context = []): void
{
echo strtoupper((string) $level), ': ', $this->susunPesan((string) $message, $context), PHP_EOL;
}
private function susunPesan(string $pesan, array $konteks): string
{
$ganti = [];
foreach ($konteks as $kunci => $nilai) {
if (is_scalar($nilai)) {
$ganti['{' . $kunci . '}'] = (string) $nilai;
}
}
return strtr($pesan, $ganti);
}
}Skrip berikut menggunakan logger tersebut. Penyertaan vendor/autoload.php mengaktifkan pemetaan namespace Psr\Log milik package, lalu berkas LoggerKonsol.php dimuat dengan require_once karena kelas proyek sendiri tidak berada dalam pemetaan autoloader Composer. Objek logger kemudian dibuat, kepatuhan antarmukanya diperiksa, dan tiga pesan dengan level yang berbeda dicetak.
<?php
declare(strict_types=1);
use Aplikasi\LoggerKonsol;
require_once 'vendor/autoload.php';
require_once 'LoggerKonsol.php';
$logger = new LoggerKonsol();
var_dump($logger instanceof Psr\Log\LoggerInterface);
$logger->info('Sinkronisasi arsip dimulai');
$logger->warning('Berkas {nama} tidak ditemukan', ['nama' => 'arsip-2025.csv']);
$logger->error('Koneksi basis data gagal');bool(true)
INFO: Sinkronisasi arsip dimulai
WARNING: Berkas arsip-2025.csv tidak ditemukan
ERROR: Koneksi basis data gagalBaris bool(true) menegaskan bahwa kelas berbasis trait itu memenuhi kontrak LoggerInterface, sehingga objeknya dapat diserahkan ke komponen mana pun yang mencantumkan dependensinya sebagai antarmuka standar. Baris berlabel WARNING memperlihatkan interpolasi konteks, yaitu penanda {nama} yang telah digantikan dengan nilai arsip-2025.csv. Tiga baris terakhir memperlihatkan tiga level yang berbeda dialirkan ke satu metode log() yang sama, persis seperti pola kerja traitnya.
Autoload PSR-4#
PSR-4 menautkan awalan namespace (prefix) ke sebuah folder dasar. Kelas yang dicari dicocokkan dengan awalannya, bagian namespace sisanya menjadi subfolder, dan nama kelas menjadi nama berkas. Satu berkas wajib memuat tepat satu kelas, dan autoloader tidak boleh melempar error ketika berkas kelasnya tidak ditemukan, sehingga pemeriksaan atas kelas yang tidak ada tetap aman dilakukan. Tabel berikut memperlihatkan cara satu nama kelas yang sama, yaitu Tugas, diterjemahkan di bawah tiga awalan yang berbeda.
| Awalan namespace | Folder dasar | Berkas kelas Tugas |
|---|---|---|
App\ | src/ | src/Tugas.php |
App\Model\ | src/Model/ | src/Model/Tugas.php |
App\Model\Arsip\ | src/Model/Arsip/ | src/Model/Arsip/Tugas.php |
Kolom terakhir selalu berupa nama kelas dengan akhiran .php, tanpa perubahan kapitalisasi, sehingga kelas Tugas hanya ditemukan di berkas Tugas.php. Aturan yang sama berlaku untuk sub-namespace, yaitu setiap segmen tambahan menjadi satu tingkat folder. Pemetaan dinyatakan pada kunci autoload di composer.json, dengan awalan yang wajib diakhiri garis miring terbalik.
Composer menerapkan pemetaan tersebut dengan menggenerasi berkas vendor/composer/autoload_psr4.php setiap kali autoloader dibangun ulang. Berkas ini hanya mengembalikan satu array berisi pasangan awalan dan folder, sebagaimana kutipan berikut yang diambil dari proyek yang baru saja memasang psr/log. Isinya murni hasil generasi, sehingga membacanya sudah cukup untuk mengetahui folder setiap package yang terpasang.
// tidak-dijalankan
// kutipan hasil composer require psr/log, bagian atas berkas dilewati
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
'Psr\\Log\\' => array($vendorDir . '/psr/log/src'),
);Pemetaan Psr\Log\ menunjuk folder vendor/psr/log/src, dan itulah sebabnya kelas Psr\Log\LoggerInterface yang digunakan pada bagian PSR-3 dapat dimuat tanpa satu pun require manual. Autoloader yang sama memuat seluruh awalan dalam satu array, mulai dari milik proyek sendiri hingga milik setiap dependensi. Berkas hasil generasi ini tidak disunting dengan tangan karena setiap perubahan composer.json akan menimpanya pada generasi berikutnya.
Kesalahan umum#
- Menulis
usesebelumnamespace. PHP menghentikan berkas dengan error fatalNamespace declaration statement has to be the very first statement, karena deklarasi namespace wajib menjadi pernyataan pertama setelahdeclare(strict_types=1);. Pindahkan seluruh daftarusetepat di bawah deklarasinamespace. - Mengimplementasikan
LoggerInterfacetanpa metodelog(). PHP menghentikan program dengan pesan error apabila kelas mengikuti antarmuka tersebut tetapi tidak menyediakan satu-satunya metode wajibnya, karena kelasnya masih memuat metode abstrak. GunakanPsr\Log\LoggerTraitagar delapan metode level tersedia, lalu tulislog()-nya. - Menyimpan dua kelas dalam satu berkas. Autoloader PSR-4 mencari berkas berdasarkan nama kelasnya, sehingga hanya kelas yang namanya sama dengan nama berkas yang dapat dimuat. Kelas kedua tetap tidak ditemukan dan memunculkan error class not found di tengah program. Pisahkan setiap kelas ke berkasnya sendiri.
Ringkasan#
- PSR-12 menyeragamkan bentuk kode dengan poin utama berupa
declare(strict_types=1);di awal berkas, satu kelas per berkas, visibilitas pada setiap anggota, dan kurung kurawal pembuka kelas atau metode pada baris tersendiri. - PSR-3 menetapkan
LoggerInterfacesebagai kontrak pencatat log, dan traitLoggerTraitdari packagepsr/logmenyisakan satu metodelog()untuk diimplementasikan logger buatan sendiri. - PSR-4 memetakan awalan namespace ke folder dasar, menerjemahkan sisa namespace menjadi subfolder, dan menjadikan nama kelas sebagai nama berkas dengan akhiran
.php. - Composer merealisasikan pemetaan tersebut di
vendor/composer/autoload_psr4.php, yang dihasilkan ulang otomatis dan tidak pernah disunting manual.
Referensi#
- PSR-12: Extended Coding Style Guide — PHP-FIG — Spesifikasi resmi gaya penulisan kode PHP, dari urutan pernyataan hingga penempatan kurung kurawal.
- PSR-3: Logger Interface — PHP-FIG — Spesifikasi antarmuka logger beserta metode levelnya dan aturan interpolasi konteks.
- PSR-4: Improved Autoloading — PHP-FIG — Spesifikasi pemetaan namespace ke folder yang diikuti autoloader Composer.