Jumat, 07 July 2023

Jadikan Sesuatu Lebih Sederhana

Mari kita membuat sesuatu jadi lebih sederhana agar mata kita tidak langsung sakit ketika melihat pekerjaan kita yang di masalalu.

Laravel

Dalam case ini saya berencana untuk menampilkan single user, yang mana ini pasti nya sangat simple. Jika kita ingin mudah. Bisa dengan menggunakan model binding seperti:

web.php
Route::get('{user}', [ProfileController::class, 'show']);

Namun, karena user bisa di identifikasi dari dua kolom yaitu hash dan username. Terpaksa kita harus membuat route model binding pada RouteServiceProvider seperti:

RouteServiceProvider.php
public function boot(): void
{
    Route::bind('user', function ($value) {
        return \App\Models\User::where('username', $value)->orWhere('hash', $value)->firstOrFail();
    });
}

Dengan begitu kita dapat dengan mudah memanggilnya pada controller.

ProfileController.php
class ProfileController extends Controller
{
    public function show(User $user)
    {
        return inertia('profile/show', [
            'user' => new UserSingleResource($user),
        ]);
    }
}

Namun, ada beberapa relasi yang ingin saya tampilkan disini.

  1. users (limited)
  2. users comments (limited)
  3. comments commentable
  4. commentable belongs to model
  5. users articles (limited)
  6. users details (limited)
  7. subscription information
  8. counting comments
  9. counting articles
  10. counting exps

Maksud dari limited di atas adalah dimana saya tidak akan menampilkan resource keseluruhan. Bisa saja artikel hanya 6 misalnya. Atau juga kolom nya tidak semua mesti di get, karena itu mempengaruhi kinerja dari mesin pastinya.

Oleh karena itu, maka ada beberapa yang harus saya refactor di sini. Pertama, untuk menghadirkan mereka semua di dalam json (kebetulan saya memakai inertia.js). Sehingga harus melakukan yang namanya eager loading.

Nah ketika kita menampilkan single user seperti ini, hal yang bisa kita lakukan adalah menggunakan load() dan juga loadCount. Tapi tetap aja messi, karena sebenarnya saya juga ingin menampilkan users dengan tidak mengambil semua kolom nya. Melainkan menambil yang penting saja dengan menggunakan select pastinya.

Untuk itu, maka saya berfikir akan langsung menampilkan user nya dengan tidak menggunakan sistem model binding.

Yang pertama, saya akan merobah route yang tadi binding user menjadi identifier, sebenarnya tidak mesti sih. Cuma biar jelas aja, bahwa di sini saya tidak menggunakan model, sehingga tidak perlu memanggil nama model nya.

web.php
Route::get('{identifier}', [ProfileController::class, 'show']);

Dengan menggantinya jadi identifier, sehingga saya yakin bahwa saya sedang tidak menggunakan model binding. Setelah itu, karena kita tadinya ingin menampilkan user dari kolom username ataupun hash, sehingga kita perlu membuat custom binding pada RouteServiceProvider. Yang mana rencana itu harus kita batalkan. Jadi saya akan menghapusnya, tinggal lah seperti ini.

RouteServiceProvider.php
public function boot(): void
{
    //
}

Setelah itu, maka kita bisa melanjutkan untuk memodifikasi controller nya. Ingat, bahwa kita tidak lagi menggunakan model binding, sehingga kita akan memanggil {identifier} sebagai identifikasi dari user yang akan muncul.

ProfileController.php
class ProfileController
{
    public function show($identifier)
    {
        $user = User::query()
            ->select('id', 'name', 'email', 'about', 'username', 'hash', 'public', 'created_at', 'deleted_at')
            ->where('public', true)
            ->where('username', $identifier)
            ->orWhere('hash', $identifier)
            ->firstOr(callback: fn () => abort(404));

        return inertia('profile/show', [
            'user' => new UserSingleResource($user),
        ]);
    }
}

Kode di atas tentu berjalan dengan mulus tanpa error. Namun, jika kita perhatikan lebih lanjut. Semua relasi nampaknya tidak pernah di load dalam query ini. Maka oleh karena itu, kita akan menambahkannya.

ProfileController.php
class ProfileController
{
    public function show($identifier)
    {
        $user = User::query()
            ->select('id', 'name', 'email', 'about', 'username', 'hash', 'public', 'created_at', 'deleted_at')
            ->where('public', true)
            ->where('username', $identifier)
            ->orWhere('hash', $identifier)
            ->with([
                'profileInformation:id,user_id,website,github,twitter,facebook,instagram,threads',
                'articles' => fn ($query) => $query
                    ->select('id', 'user_id', 'title', 'picture', 'slug', 'created_at')
                    ->where('status', ArticleStatus::PUBLISHED)
                    ->limit(6),
                'subscribe:user_id,started_at,ended_at',
                'comments' => fn ($query) => $query
                    ->without('author', 'children')
                    ->with([
                        'commentable' => fn ($commentable) => $commentable
                            ->select('id', 'title', 'series_id', 'episode')
                            ->with('series:id,name,slug'),
                    ]),
            ])
            ->withCount(['comments', 'articles', 'likes'])
            ->firstOr(callback: fn () => abort(404));

        return inertia('profile/show', [
            'user' => new UserSingleResource($user),
        ]);
    }
}

Hmmmmmm. Wait a minute... Ini sangat messy kelihatan nya, mungkin sekarang tidak masalah. Namun nanti ketika kita sudah masuk ke tahap pengembangan, mata kita akan terasa sakit melihat hal ini tiba-tiba ada di controller. Karena bayangkan, ada beberapa method di dalam controller ini yang 1 method saja sudah membuat kepala berakar.

Dengan case ini, maka model scope sangat berperan penting. Pada model User, sekarang kita bisa membuat satu fungsi dengan nama scopeWithUserDetails seperti:

User.php
class User
{
    public function scopeWithUserDetails($query, $identifier)
    {
        return $query->select('id', 'name', 'email', 'about', 'username', 'hash', 'public', 'created_at', 'deleted_at')
            ->where('public', true)
            ->where('username', $identifier)
            ->orWhere('hash', $identifier)
            ->with([
                'profileInformation:id,user_id,website,github,twitter,facebook,instagram,threads',
                'articles' => fn ($query) => $query
                    ->select('id', 'user_id', 'title', 'picture', 'slug', 'created_at')
                    ->where('status', ArticleStatus::PUBLISHED)
                    ->limit(6),
                'subscribe:user_id,started_at,ended_at',
                'comments' => fn ($query) => $query
                    ->without('author', 'children')
                    ->with([
                        'commentable' => fn ($commentable) => $commentable
                            ->select('id', 'title', 'series_id', 'episode')
                            ->with('series:id,name,slug'),
                    ]),
            ])
            ->withCount(['comments', 'articles', 'likes']);
    }
}

Dengan begitu, sekarang kita bisa merubah semua yang ada di controller menjadi lebih sederhana seperti:

ProfileController.php
class ProfileController
{
    public function show($identifier)
    {
        $user = User::query()->withUserDetails($identifier)->firstOr(callback: fn () => abort(404));

        return inertia('profile/show', [
            'user' => new UserSingleResource($user),
        ]);
    }
}

Dengan begitu, maka seandainya kita kembali 6-12 bulan lagi untuk melihat controller ini, mata tidak langsung capek.

Semoga artikel ini bermanfaat. Share jika menurut kalian ini penting, dan sukai untuk sekalian bookmark.