کنترلرهای تمیزتر با Route Model Binding

قابلیت Route Model Binding، در فریم‌ورک لاراول، امکان اینجکت‌کردن یک مدل در روت‌ها را فراهم کرده است؛ شاید معنای این جمله برای شما قابل درک نباشد، برای همین با یک مثال ساده شروع می‌کنیم و می‌خواهیم یک پست را از دیتابیس بازیابی کنیم:

Route::get('posts/{id}', function ($id) {
       $post = Post::find($id);
       if (!$post) return abort(404);
       return view('post.show', compact('post'));
});

در خط دوم این قطعه کد، پست مورد نظر را با متد find که در حالت پیشفرض id موجودیت مورد نظر را می‌گیرد و آن را برمی‌گرداند، پیدا کرده و در متغیر post$ قرار داده‌ایم. در خط بعدی بررسی می‌کنیم تا ببینیم پست مورد نظر یافت شده یا نه؛ در صورتی که پست پیدا نشده باشد، شرط برقرار می‌شود (به دلیل استفاده از نقیض با علامت !) و خطای 404 را return می‌کنیم. اما اگر پست پیدا شده باشد، چیزی return نمی‌شود و در نتیجه پست را با موفقیت به ویوی post.show ارجاع می‌دهیم.


راهی برای ساده‌تر کردن کد بالا وجود دارد:

Route::get('posts/{id}', function ($id) {
       $post = Post::findOrFail($id);
       return view('post.show', compact('post'));
});

تفاوت این کد با کد قبلی در این است که به جای متد find از findOrFail استفاده کرده‌ایم تا از شر خط سوم کد قبلی خلاص شویم؛ پس اگر پست پیدا نشود، متد findOrFail خطای 404 را اجرا می‌کند و دیگر خط بعدی اجرا نخواهد شد.


اما Route Model Binding به ما کمک می‌کند تا از شر کدهای اضافی دیگری هم راحت شویم، به کد زیر توجه کنید:

Route::get('posts/{post}', function ($post) {
       return view('post.show', compact('post'));
});

در حال حاضر لاراول دو نوع از Route Model Binding را پشتیبانی می‌کند:

  1. Implicit Model Binding
  2. Explicit Model Binding

توجه: مثال بالا از نوع Explict Model Binding بود.


حال که Explict Model Binding را دیدیم، وقت آن رسیده که با Implict Model Binding هم آشنا شویم:

Route::get('posts/{post}', function (App\Post $post) {
// هرکاری خواستید با متغیر پست انجام دهید
});

لاراول به اندازه‌ای باهوش است که بداند وقتی متغیری از جنس کلاس مدل Post در Closure اینجکت شده است، باید عددی که در روت ذکرشده توسط کاربر درخواست شده است را در دیتابیس جستجو کند و پست مربوط به آن را بازیابی کند.


چگونه کلید مدل را تغییر دهیم؟!

در اصل جستجو بر اساس ستون id انجام می‌شود، اما اگر مایل باشید که ستون دیگری از ستون‌های موجود در جدول پست‌ها را مورد جستجو قرار دهید، می‌بایست Route Key مربوط به مدل را تغییر دهید و این کار با Overrideکردن متد getRouteKeyName در مدل مربوط به پست امکان‌پذیر است. برای نمونه می‌خواهیم که از slug به جای id استفاده می‌کنیم و به این طریق عمل می‌کنیم:

class Post extends Model {
      public function getRouteKeyName() {
            return 'slug';
      }
}

بعد از این کار، به جای اینکه پستِ ما در آدرس http://awesome.dev/posts/24 باشد، از آدرس http://awesome.dev/posts/my-post-slug استفاده می‌کنیم. 😊

کمی از روش Explicit Model Binding

همانطور که از اسمش پیداست (کلمه‌ی Explict در زبان انگلیسی به معنای صریح است، مترجم)، باید صراحتاً به لاراول بگویید که می‌خواهید یک پارامتر موجود در url را به یک مدل مرتبط کنید. دو راه برای این کار وجود دارد، یکی اینکه از Route Facade استفاده کنیم و دیگری اینکه از RouteServiceProvider کمک بگیریم که من این راه را پیشنهاد می‌کنم.

استفاده از Route Facade

Route::bind('post', 'App\Post');

البته می‌توانیم کار معنادارتری انجام دهیم، برای مثال می‌توانیم پست‌هایی که هنوز در حالت پیش‌نویس هستند را برنگردانیم یا آنکه پارامترهای دیگری را هم دخیل کنیم، پس یک Closure نیاز داریم:

Route::bind('post', function ($value) {
      return App\Post::find($value)->where('status', '=', 'published')->first();
});

نکته‌ای کنکوری از مترجم: استفاده از '=' در شرط بالا لازم نیست و بدون آن هم کار می‌کند!

استفاده از RouteServiceProvider

تفاوت این روش با روش سابق در این است که ما باید از Service Providerها استفاده کنیم و از متد boot آنها کمک بگیریم، به این مثال نگاهی بیندازید:

public function boot(Router $router)
{
      parent::boot($router);
      $router->bind('post', function ($value) {
            return App\Post::find($value)->where('status', '=', 'published')->first();
      });
}

استفاده از استثناهای سفارشی

من APIهای زیادی می‌سازم، پس استفاده از یک استثنا (Exception) سفارشی و مخصوص برای کسانی مثل من خیلی مفید و کاربردی است. با لاراول انجام‌دادن این کار خیلی ساده است، تنها باید در متد boot از کلاس RouteServiceProvider به تعریف آن بپردازیم.

$router->model($routeParameter, $modelToBind, function () {
      throw new NotFoundHTTPException;
});

نتیجه‌گیری

شما می‌توانید با مراجعه‌ی به مستندات لاراول، معلومات بیشتری درباره‌ی Route Model Binding کسب کنید. امیدوارم که این مقاله‌ی کوتاه بتواند در کم‌ترکردن خطوط کد و تمیزتر و مرتب‌تر کردن کنترلرهایتان مفید باشد.

ترجمه‌شده توسط «وب‌پژوه» از مقاله‌ی زیر:

https://scotch.io/tutorials/cleaner-laravel-controllers-with-route-model-binding