28 April 2024

Symfony, framework PHP tua modern

Tua tua modern

Symfony

Pernah menyebut Symfony di halaman tentang saya, tapi belum pernah menulis tentangnya ๐Ÿซฃ

Padahal Remix termasuk sering dibahas ๐Ÿ™„

Iya iya, mangap ๐Ÿ˜ญ

Btw saya gak akan bahas sesuatu sebatas tentang Symfony yak. Bisa ke website-nya langsung kalau cuma butuh tahu itu. Saya akan lebih bercerita mengenai kesan awal dulu saat pertama kali bertemu Symfony, mencoba fitur2nya, sampai akhirnya saya menggelinjang dibuatnya. ๐Ÿ™ƒ

Kesan awal

Saya ini generasi jaya2nya CodeIgniter (CI) gaes. Lupa versi berapa dulu saya mencoba pertama kali. Tapi kesan awal di CI begitu dimudahkan dalam arti sebenarnya. Mengusung konsep MVC yang cukup dikenal dan dipakai di hampir seluruh framework. Ditambah lagi, saya juga bekerja cukup lama menggunakan CI.

Bagaimana dengan Symfony? ๐Ÿ™ƒ

Symfony ini mimpi buruk bagi fanatik CI gaes ๐Ÿคฃ. Masih tahap konfigurasi saja udah bertemu makhluk bernama YAML. Siapa iniiiii ๐Ÿซ . Masuk ke controller bertemu makhluk bernama Annotation. Siapa lagi siihhhh ๐Ÿ˜ตโ€๐Ÿ’ซ. Masuk ke database bertemu Doctrine ORM. Taunya saya orem2 ๐Ÿ˜ญ. Masuk ke view bertemu Twig. Hancuuukkkk ๐Ÿ˜ค

Cukup lama proses perkenalan saya dengan Symfony. Hampir tidak ada kesan yang menyenangkan, yang memuluskan hubungan kami berdua. Apalagi Symfony sangat tidak bersahabat dengan Windows. Lemotnya ber-kali2 lipat dibanding menggunakan CI. Beneran ini, fakta. Yaaaa minimal fakta tersebut masih menghantui sampai akhirnya saya menggunakannya di Linux atau macOS. Beda banget gaes ๐Ÿซ 

Kan bisa pakai WSL ๐Ÿ™„

WSL belum lahir ๐Ÿ˜ญ

Mempelajari Fabien Potencier

Salah satu cara saya belajar Symfony secara meyakinkan adalah dengan mempelajari pendirinya. Fabien dulu cukup aktif menulis, yang tulisannya cukup mewakili pengambilan keputusan untuk masa depan Symfony.

Misalnya saat menentukan template engine menggunakan Twig. Keputusannya cukup gila di lingkungan PHP. Tapi dia punya prinsip yang masuk akal juga. Menurutnya, tim frontend tidak harus paham PHP beserta kerumitannya untuk membuat sebuah tampilan berbasis data. Frontend cukup memanfaatkan variabel yang sudah disediakan oleh backend. Dimana pemanfaatannya menggunakan bahasa khusus yang lebih bersahabat dengan orang HTML CSS yang tidak mengenal PHP.

Nah, dari situ mulai terlihat bagaimana pola pikir Fabien melalui Symfony. Kenapa ada YAML, Annotation, Doctrine ORM, Twig, dll.

Coding standard

Sampai saat ini, saya belum menemukan standar pengkodean serapi Symfony. Khususnya untuk framework PHP yak.

Salah satu yang masih saya pakai, yang mungkin dianggap aneh, adalah Yoda Conditions. Hal sederhana tapi pengaruhnya luar biasa.

// secara naluriah begini
if ($jus == 'jeruk') {
}

// sedangkan si yoda begini
if ('jeruk' == $jus) {
}

Emang beda ya? ๐Ÿค”

Menurut komputer, mereka sama. Tapi menurut manusia, mereka berbeda.

// pengkondisian sekaligus deklarasi variabel
if ($jus = 'jus') {
}

Kode di atas tidak salah menurut komputer. Tapi akan salah jika yang sebenarnya kita maksud adalah membandingkan dua nilai. Dari sini manusia terlihat celahnya gaes, seharusnya dua sama dengan (==) tapi ditulis satu. Pembiasaan membuat perbandingan dengan nilai konstan (atau yang dapat diprediksi) di bagian pertama akan mengurangi resiko kesalahan kriteria.

Kalau dibiasakan, hasilnya kemudian adalah kita memiliki dua cabang pengambilan keputusan yang lebih jelas. Jika ingin membandingkan, maka letakkan nilai konstan di bagian pertama. Jika ingin pengkondisian sekaligus deklarasi variabel, maka letakkan variabel di bagian pertama.

Mulai paham mulai paham ๐Ÿค”

Itu baru masalah kecil, yang lebih luas lagi ada gaes ๐Ÿ˜„. Di Symfony, format penulisan yang dipakai lebih detail dari yang umum kira. Penulisan variabel di Twig menggunakan snake_case. Sedangkan di PHP, penulisan variabel menggunakan camelCase.

Tidak konsisten ๐Ÿซ 

Bukan begitu, begini penjelasannya (menurut saya ๐Ÿ™ƒ).

return [
    'jus_pilihan' => $jusPilihan,
];

Format key untuk array adalah snake_case, jadiโ€ฆ

Ohhh keluaran controller ke view kan pakai array yak ๐Ÿ™ƒ

Lanjutin gih ๐Ÿ™„

Iya, jadi nanti Twig nangkepnya jadi jus_pilihan, makanya coding standard-nya Twig dibuat gitu juga ๐Ÿ˜‹

Bagusss, racun mulai bereaksi ๐Ÿ™ƒ

Mental model

Ini nih yang bikin saya menggelinjang. Symfony membawa penggunanya ke level pemahaman dasar. Hampir setiap hal yang diusung, Symfony selalu mencantumkan referensi dasar. Bahkan banyak hal juga merupakan sesuatu yang sudah teruji lama di dunia pemrograman, bukan hanya di PHP.

Saya melihat ada orientasi yang berbeda dari Symfony dengan kebanyakan framework. Orientasi tersebut yang sering kali menempatkan Symfony sebagai framework tua, namun tetap modern. Framework yang juga tetap dibutuhkan oleh sesama framwork PHP lain untuk menyokong kebutuhannya.

Jika tidak memiliki mental model yang kuat dan teruji, saya rasa gak mungkin Symfony seberpengaruh ini. Bukan hanya untuk penggunanya, tapi juga untuk framework lain. Ahhh keren sih ini.

Secuil โ€vibratorโ€ Symfony

Jelas tidak lengkap kalau tidak saya beri contoh satu dua fitur alias โ€œvibratorโ€ dari Symfony yang membuat saya menggelinjang ๐Ÿ˜ถ

Routing

Bahkan framework populer semacam Laravel, urusan routing masih dideklarasikan di beda tempat. Ini hal sepele sebenarnya, tapi saya tidak menemukan kenikmatan di sana. Membuat file controller, lalu mendaftarkannya di file routing, sebenarnya merupakan sesuatu yang mudah disederhanakan. Sama halnya seperti kebiasaan pengkondisian, tidak menerapkan Yoda Conditions menyebabkan kita punya dua kemungkinan yang berpotensi menyebabkan kesalahan, meskipun di routing level kesalahannya lebih rendah (karena pasti error).

Symfony sendiri telah lama memfasilitasi variasi impelementasi routing. Sampai tulisan ini dibuat, Symfony memiliki empat pilihan implementasi routing. Tapi bukan itu yang keren.

Dengan dasar yang kuat, Symfony selalu memiliki rekomendasi untuk setiap fitur dengan beberapa pilihan. Untuk urusan routing, Symfony merekomendasikan routing dilakukan di file yang sama dengan file controller-nya.

#[Route('/jus-pilihan')]
public function jusPilihan()
{
}

Routing dilakukan di method dari sebuah controller. Sudah, gak usah pindah2 file lagi.

Coba prinsip ini dibawa ke urusan pelayanan publik. Bayangkan betapa nikmatnya sebuah pelayanan publik yang menerapkan prinsip ini. Urusan yang bisa jadi satu kenapa dipisah sih? ๐Ÿ˜ค

Gaes, ini bahasan Symfony gaes ๐Ÿ˜ฐ

Entity Validation

Entity pada dasarnya adalah class yang mewakili eksistensi tabel di basis data. Fitur ini merupakan hasil integrasi Symfony dengan Doctrine ORM. Ya, Symfony melempar tanggung jawab urusan database ke Doctrine ORM. Kalau ditelusuri pertimbangannya, alasannya karena ORM bukanlah mainan yang mudah perawatannya.

Berikut sekilas wujud dari entity gaes.

#[ORM\Entity(repositoryClass: BeverageRepository::class)]]
class Beverage
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private ?int $id = null;

    #[ORM\Column(length: 255)]
    private ?string $name = null;

    public function getId(): ?int
    {
        return $this->id;
    }

    public function setName(?string $name): void
    {
        $this->name = $name;
    }
    
    public function getName(): ?string
    {
        return $this->name;
    }
}

Singkatnya, seluruh property beserta setter getter-nya adalah wujud tabel dari basis data di PHP. Misalnya saat select data, maka entity akan berisi data yang dimaksud. Dimana cara pemanfaatan datanya dilakukan melalui eksekusi getter. Saat insert atau update juga demikian. Cara menentukan datanya dilakukan melalui eksekusi setter.

// saat select
$beverage = $entityManager->getRepository(Baverage::class)->find($id);
echo $beverage->getName();

// saat insert
$beverage = new Beverage();
$beverage->setName('Jus jeruk');

Sebelum ke Symfony, dari sini saya sangat menikmati pendekatan yang dilakukan Doctrine. Membuat entity yang mewakili tabel dilakukan dengan cara PHP tradisional. Pendekatan ini bukan menimbulkan kerumitan, tapi lebih ke perwujudan tabel di basis data berhasil dijelaskan secara eksplisit di entity. Bahkan urusan migrasi tidak perlu harus membuat file migrasi lagi. Dari entity, kita bisa menghasilkan file migrasi tanpa membuatnya manual.

Nah, sekarang masuk ke bagian Symfony, yaitu validasi entity. Pendekatannya sama, deklarasi aturan validasi dilakukan di file entity, tidak di tempat lain.

Wah ini gila sih ๐Ÿคฏ

Biasa napa ๐Ÿ™„

#[Assert\NotBlank]
private ?string $name = null;

Contoh di atas, artinya name memiliki aturan validasi tidak boleh โ€œkosongโ€. Lanjut ke implementasi.

// NOTE: ini hanya contoh proses yang dilalui untuk validasi dan insert

// atur nilai entity
$beverage = new Beverage();
$beverage->setName('Jus jeruk');

// validasi entity
$validator->validate($beverage);

// simpan entity
$entityManager->persist($beverage);
$entityManager->flush();

Lihat alurnya gaes. Satu object (entity) digunakan untuk validasi dan insert sekaligus. Dengan begini, bisa dipastikan data yang divalidasi dan data yang akan masuk ke basis data sama persis. Terlebih lagi, sama persisnya melalui kesadaran kita. Dengan menentukan manual mana saja yang diisi.

Nyok kita lirik implementasi validasi dan insert di Laravel.

// NOTE: ini hanya contoh proses yang dilalui untuk validasi dan insert

// validasi request
$validator = Validator::make([
    'name' => 'Jus jeruk',
], [
    'name' => ['required'],
]);
$validator->fails();
$validated = $validator->validated();

// atur nilai model
$beverage = new Beverage();
$beverage->name = $validated['name'];

// simpan model
$beverage->save();

Senada dengan implementasi routing, implementasi validasi dan insert di Laravel tidak ada yang salah. Tapi lagi2 saya tidak bisa menemukan kenikmatannya.

  1. Kita bisa salah deklarasi apa2 yang butuh divalidasi. Misalnya yang tersedia di tabel adalah name, tapi di deklarasi validasi saya cantumkan description.
  2. Sebaliknya, kita bisa salah memanfaatkan input. Misalnya yang seharusnya terkandung di $validated adalah name, tapi saya menggunakan title.

Kesalahan yang saya sebut di atas termasuk manusiawi, tapi pola yang disediakan juga mendukung terjadinya kesalahan ๐Ÿ˜ฎโ€๐Ÿ’จ. Belum lagi kalau kalian gemar menggunakan โ€œpenutup mataโ€ begini: Beverage::create($request->validated()) ๐Ÿ˜ญ

Kalian pasti tahu contoh pembuatan undang2 yang justru menguntungkan pengguna satu dan merugikan pengguna lainnya kan? Ya itu karena memang celahnya dibuat untuk menguntungkan pengguna terpilih ๐Ÿ˜ค

Gaes, kok masuk ke situ lagi gaes ๐Ÿ˜ถโ€๐ŸŒซ๏ธ

Penutup

Huwaaa cukup sekian ya gaes tulisan singkat mengenai Symfony. Yang jelas, Symfony bukan framework sempurna. Tapi dari yang saya lalui, dia cukup memberi pelajaran di banyak hal terutama yang bersifat mendasar. Hal tersebut yang justru berkontribusi positif pada pemahaman berkode saya selama ini. Yaaaa meskipun jelas masih banyak kekurangannya ๐Ÿ˜ถโ€๐ŸŒซ๏ธ

Bagi kalian pecintah Laravel, maapkan saya karena dengan sengaja memilihnya untuk perbandingan. Terlebih lagi, di sini kayaknya Laravel jauh lebih populer daripada Symfony. Ya kan ya kan? ๐Ÿค”

Terakhir, kalau kalian anak PHP dan belum pernah mempelajari Symfony, saya rekomendasikan kalian untuk mempelajari dan sesekali berkencan dengannya. Bukan untuk ahli Symfony, tapi Symfony akan mendorong keluar potensi kebaikanmu dalam berkode โ˜•๏ธ