Ngoding Laravel dengan Gaya, pakai Laravel Evo

Ngoding Laravel dengan Gaya, pakai Laravel Evo

Beberapa waktu lalu, saya membuat video cara membuat framework kekinian di kanal youtube saya. Pada framework tersebut, saya menerapkan fitur terbaru pada PHP 8, yaitu attribute.

Dan tidak lama setelahnya, saya coba menerapkan hal yang sama ke Laravel, dengan membuat package khusus Laravel yang saya beri nama Evo.

Berikut ini adalah contoh controller saat menggunakan Evo (baris yang di-highlight adalah kode khusus Evo):

#[RoutePrefix('todos')]
class TodoController
{
    #[Put('{id}')]
    #[Authenticated]
    public function update(
        #[Param] int $id,
        #[Body] UpdateTodoDTO $data,
        #[LoggedUser] User $user,
    ): UpdateTodoResponse
    {
        $todo = Todo::findOrFail($id);
        $todo->title = $data->title;
        $todo->is_completed = $data->completed;
        $todo->completed_by = $data->completed ? $user->id : null;
        $todo->completed_at = $data->completed ? date("Y-m-d H:i:s") : null;
        $todo->save();

        return UpdateTodoResponse::fromArray($todo->toArray()); 
    }
}

Buat kamu yang pernah mencoba Nest.js, mungkin familiar dengan kode diatas. Karena memang saya terinspirasi dari Nest.js.

Pertanyaannya, untuk apa? gaya-gayaan doang atau ada maksudnya?

Nah pada artikel ini, saya mau ceritakan manfaat dari penerapan kode seperti di atas.

Btw, artikel ini saya tujukan orang-orang yang sudah pernah mengembangkan aplikasi dengan Laravel/Lumen. Jadi mungkin untuk yang belum pernah coba framework tersebut, akan sulit untuk memahami maksudnya.

Memperjelas Spesifikasi API Kamu

Bayangin, kamu baru saja menjadi back-end developer di sebuah perusahaan, dan kamu mendapati kode controller seperti di bawah ini:

public function store(Request $request)
{
    $invoice = Invoice::findOrFail($request->invoice_id);
    $result['invoice'] = $invoice->toArray();

    if ($invoice->is_paid) {
        throw new InvoiceAlreadyPaidException();
    }

    DB::beginTransaction();
    $payment = new Payment;
    $payment->invoice_id = $invoice->id;
    $payment->method = $request->payment_method;
    $payment->user_id = user()->id();
    $payment->amount = $request->amount;

    if ($request->payment_method == 'bank_transfer') {
        $payment->account_number = $request->account_number;
        $payment->bank_id = $request->bank_id;

        $bank = Bank::findOrFail($request->bank_id);
        $result['bank_name'] = $bank->name;
    }

    $payment->save();

    $invoice->is_paid = true;
    $invoice->save();
    DB::commit();

    $result = array_merge($result, $payment->toArray());

    return response()->json($result);
}

Hayo coba tebak, pada kode tersebut inputannya apa aja, dan struktur dari result-nya seperti apa?

Sulit kan? tentu saja. Karena Programmernya mengambil input dan mengisi result di sembarang tempat, sehingga kita harus jeli melihat setiap baris untuk mengetahui input dan output-nya seperti apa. Itupun ga bisa hanya dilihat dari file itu aja, pada bagian $result['invoice'] = $invoice->toArray(), untuk tahu isi dari data invoice, kita juga harus lihat struktur database pada table invoice. Ribeud.

Dengan Evo, kita dapat mendefinisikan input dan output langsung pada bagian kepala dari fungsi/method. Sehingga Programmer yang baru gabung, ga perlu membaca logika program hanya untuk tahu inputnya ada apa saja, dan outputnya akan seperti apa.

Berikut adalah kode diatas, ditulis dengan cara Evo:

#[Post]
public function store(
    #[Body] StorePaymentDTO $data,
    #[LoggedUser] User $user,
): StorePaymentResponse
{
    // logic program yang kurang lebih sama
}

Untuk melihat inputan yang tersedia, pada VSCode dengan intelephense, kamu cukup tekan ctrl sambil arahkan cursor ke StorePaymentDTO. Dan kamu akan diperlihatkan class seperti ini, dimana propertinya adalah data dari request body.

class StorePaymentDTO extends DTO
{
    public int $invoice_id;
    public float $amount;
    public string $payment_method;
    public ?string $account_number;
    public ?int $bank_id;
}

Begitu pula untuk mengetahui struktur dari output. Cukup arahkan ke StorePaymentResponse, dan kamu akan diperlihatkan class dengan properti seperti ini:

class StorePaymentResponse extends JsonResponse
{
    public int $id;
    public float $amount;
    public int $user_id;
    public string $method;
    public ?string $account_number;
    public ?int $bank_id;
    public ?string $bank_name;
    public InvoiceDTO $invoice;
}

Disana terlihat jelas, isinya apa saja, tipe datanya apa, serta nullable atau tidaknya.

Membiasakan Diri Menyiapkan Sesuatu dan Membuat Tujuan yang Jelas

Programming pada dasarnya hanyalah tentang bagaimana memproses input menjadi output. Input adalah apa yang kita miliki, sedangkan output adalah hal yang kita tuju. Melakukan coding tanpa tahu input-nya apa saja, dan output-nya akan seperti apa, sama seperti bepergian tanpa tahu isi tas/dompetmu ada apa saja, dan ga tahu mau kemana, alias luntang-lantung.

Menulis kode dengan cara Evo, melatih kamu untuk terbiasa mendefinisikan input dan output sebelum menuliskan logika program. Membiasakan kamu menentukan tujuan dan menyiapkan perbekalan sebelum bepergian.

Menjaga Keharmonisan antara Back-end dengan Mobile Developer

Kekurangan dari membuat RESTful API dengan bahasa yang menganut dynamic typing seperti PHP atau Javascript adalah seringkali kita khilaf, lupa melakukan casting tipe data. Sedangkan aplikasi mobile biasanya dibuat menggunakan bahasa pemrograman yang static typing, yang mana salah mengirimkan tipe data bisa berakibat fatal. Hal ini seringkali menimbulkan perselisihan antara mobile developer dan back-end developer.

Evo mencegah ini dengan melakukan automatic type casting, dimana setiap request dan response akan otomatis di cast sesuai dengan tipe data yang kita definisikan pada DTO maupun Response class.

Sebagai contoh, kita diminta membuat response dengan struktur seperti ini:

{
    "id": 1,
    "title": "lorem ipsum dolor sit amet",
    "is_completed": false,
    "created_at": "2021-01-02 03:04:05"
}

Pada database MySQL, boolean itu diwakili dengan tipe data integer berisi 0 dan 1, yang kalau kita asal kirim isi dari database, maka mobile developer akan mendapatkan integer 0 atau 1, lebih parahnya lagi malah kadang string "0" atau "1".

Dengan Evo, saat kamu mengirimkan response class seperti di bawah ini.

class StoreTodoResponse extends JsonResponse
{
    public int $id;
    public string $title;
    public bool $is_completed;
    public string $created_at;
}

Evo akan otomatis melakukan type casting, id menjadi integer, title menjadi string, is_completed menjadi boolean, dan created_at menjadi string.

Validasi Otomatis

Evo melakukan pengecekan tipe data pada seluruh inputan yang ada di dalam HTTP request, entah itu header-nya, query-nya, parameter-nya, cookie-nya, serta body-nya.

Contoh, saat kamu menuliskan #[Query] int $page, Evo akan menolak request dengan URL seperti ?page=bukan-angka.

Untuk validasi lebih advance-pun, Evo menyediakan atribut yang dapat kamu gunakan seperti ini:

class RegisterDTO extends DTO
{
    #[Email(message: "Emailnya ga valid wei")]
    #[Unique(table: "users", column: "email", message: "Email sudah kepakai bro")]
    public string $email;

    #[Min(6, message: "Password minimal 6 karakter gan")]
    #[Max(255, message: "Ga sepanjang itu juga jir")]
    public string $password;

    #[Max(255, "Itu nama atau paragraf masbro")]  
    public string $name;
}

Disitu kita menuliskan validasi lewat atribut, yang mana lebih ramah untuk Text Editor atau IDE. Jadi kalau kamu lupa parameternya, IDE/Text Editor akan bantu mengingatkan tanpa kamu perlu bolak-balik buka dokumentasi Laravel. Keuntungan ini ga bisa kamu dapatkan dengan penjabaran validation rules via string ala Laravel.

Lebih Mudah dibaca Mesin

Saat menjabarkan input dan output dengan cara Evo, tidak cuma tim kamu yang bisa dengan mudah membaca spesifikasi API kamu. Tapi mesin juga.

Terus kenapa kalau bisa dibaca mesin?

Saat mesin bisa memahami keinginan kita, kita bisa menyuruh mesin melakukan sesuatu untuk kita. Saat ini saya sedang membuatkan Swagger generator pada Evo, dimana Evo akan membaca kode kamu dan menampilkan dokumentasi API secara otomatis, tanpa perlu kamu menuliskan OpenAPI secara manual.

Yap, untuk yang satu ini memang masih tahap pengembangan. Tapi hal ini sangat memungkinkan. Karena Nest.js pun bisa melakukan itu.

Malah mungkin ga cuma Swagger generator. Tapi juga test generator, jadi Evo akan generate file test serta assertion-nya secara otomatis mengikuti spesifikasi yang kita tuliskan.

Penutup

Itulah beberapa alasan kenapa saya membuat tools yang memanfaatkan penuh fitur atribut seperti pada Evo ini. Jadi bukan cuma untuk gaya-gayaan ya, banyak juga kok manfaatnya.

Untuk Evo ini masih saya kembangkan sambil saya gunakan pada side-project saya. Kalau kamu mau intip-intip pengembangannya bisa mampir ke https://github.com/emsifa/evo.

Sekian tulisan kali ini. 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