Route Binding لاراول: پشت صحنهای که نمیدیدی
خلاصهٔ کاملتر
Route Binding یکی از اون قابلیتهای لاراوله که آنقدر روانه که دیگه بهش فکر نمیکنیم. مسیر /users/{user} رو مینویسیم، یه User $user توی کنترلر type-hint میزنیم، و لاراول مدل رو آماده تحویل میده. بدون کوئری دستی، بدون findOrFail() تکراری. ولی این سادگی ظاهری یه جریان پیچیدهی داخلی داره که دونستنش خیلی چیزها رو شفافتر میکنه.
مدل ذهنی که باید داشته باشیم اینه: روتر اول URI رو با مسیر تعریفشده تطبیق میده و پارامترهای خام رو ذخیره میکنه. مثلاً برای آدرس /users/42، پارامتر باگ اینه: user = '42'. بعد از این مرحله، یه میانافزار وارد میشه و این مقادیر خام رو به آبجکتهای واقعی تبدیل میکنه. پس route binding اصلاً «جادوی کنترلر» نیست؛ خیلی زودتر از اون اتفاق میافته.
قلب ماجرا میانافزار Illuminate\Routing\Middleware\SubstituteBindings هست. متد handle() این میانافزار دو کار اصلی انجام میده: اول substituteBindings() رو صدا میزنه که explicit bindingها رو حل میکنه، و بعد substituteImplicitBindings() رو که implicit bindingها رو پردازش میکنه. همچنین اگه مدل پیدا نشه، ModelNotFoundException رو میگیره و به callback مسیر (missing()) پاس میده.
public function handle($request, Closure $next)
{
$route = $request->route();
try {
$this->router->substituteBindings($route);
$this->router->substituteImplicitBindings($route);
} catch (ModelNotFoundException $exception) {
if ($route->getMissing()) {
return $route->getMissing()($request, $exception);
}
throw $exception;
}
return $next($request);
}Explicit Binding وقتی Route::model('user', User::class) یا Route::bind() صدا میزنی، لاراول یه binder توی روتر ذخیره میکنه. داخلاً این کار از طریق RouteBinding::forModel() انجام میشه. وقتی درخواست میاد، substituteBindings() این binder رو اجرا میکنه. جالب اینجاست که روتر مستقیماً به دیتابیس دسترسی نمیزنه؛ بلکه یه نمونه خالی از مدل میسازه و از خود مدل میخواد خودش رو resolve کنه:
$routeBindingMethod = $route?->allowsTrashedBindings() && $instance::isSoftDeletable()
? 'resolveSoftDeletableRouteBinding'
: 'resolveRouteBinding';
if ($model = $instance->{$routeBindingMethod}($value)) {
return $model;
}این طراحی هوشمندانهست چون روتر نیازی نداره بدونه هر مدل چطور باید کوئری بزنه؛ این مسئولیت رو به خود لایه مدل واگذار میکنه.
Implicit Binding که پرکاربردترین نوعه، از طریق کلاس ImplicitRouteBinding و متد resolveForRoute() پردازش میشه. این متد اول backed enumها رو resolve میکنه، بعد signature کنترلر یا closure رو بررسی میکنه، پارامترهایی که UrlRoutable پیادهسازی میکنن رو پیدا میکنه، نمونه خالی مدل رو از container میسازه، resolveRouteBinding() رو صدا میزنه، و در نهایت مقدار خام رو با مدل واقعی جایگزین میکنه.
foreach ($route->signatureParameters(['subClass' => UrlRoutable::class]) as $parameter) {
if (! $parameterName = static::getParameterName($parameter->getName(), $parameters)) {
continue;
}
$parameterValue = $parameters[$parameterName];
if ($parameterValue instanceof UrlRoutable) {
continue;
}
// ... resolve and substitute
}وقتی از custom key مثل {post:slug} استفاده میکنی، لاراول اون فیلد رو به resolveRouteBinding() پاس میده تا کوئری روی اون ستون اجرا بشه. برای scoped binding هم (مثل /users/{user}/posts/{post:slug})، لاراول مطمئن میشه که post واقعاً متعلق به همون user هست.
دونستن این جریان کمک میکنه که قابلیتهایی مثل withTrashed()، missing()، overrideکردن resolveRouteBinding()، و Route::bind() سفارشی خیلی قابل پیشبینیتر بشن و دیگه «جادو» به نظر نرسن.
نکات کلیدی:
- Route Binding در میانافزار SubstituteBindings اتفاق میافته، نه توی کنترلر
- اول explicit bindingها پردازش میشن، بعد implicit bindingها
- روتر مستقیماً کوئری نمیزنه؛ از متد resolveRouteBinding() خود مدل استفاده میکنه
- missing() کار میکنه چون میانافزار ModelNotFoundException رو میگیره و به route callback پاس میده
- Implicit binding از طریق بررسی signature کنترلر و پیادهسازی UrlRoutable کار میکنه
- برای soft delete، متد resolveSoftDeletableRouteBinding() بهجای نسخه معمولی صدا زده میشه




