کنترلرها

لاراول 5.6.29 در حال تکمیل

مقدمه

شاید بخواهید تا به جای تعریف کلوژر در فایل‌های روت به عنوان لاجیک هندل کننده‌ی ریکوئست‌ها، این رفتار را با استفاده از کلاس‌های کنترلر سازماندهی کنید. کنترلرها می‌توانند لاجیک هندل کردن ریکوئست‌های مرتبط، را در قالب یک کلاس گروه‌بندی کنند. کنترلرها در دایرکتوری app/Http/Controllers قرار می‌گیرند.

کنترلرهای پایه

تعریف کردن کنترلرها

مثال زیر یک کلاس پایه برای یک کنترلر است. توجه داشته باشید که این کنترلر از کلاس کنترلر پایه‌ی لاراول مشتق شده است. کلاس پایه متدهای مناسبی مانند متد middleware را که می‌تواند برای اَتَچ کردن میدلور به اکشن‌های کنترلر استفاده شود، را ارائه می‌دهد.

<?php

namespace App\Http\Controllers;

use App\User;
use App\Http\Controllers\Controller;

class UserController extends Controller
{
    /**
     * Show the profile for the given user.
     *
     * @param  int  $id
     * @return Response
     */
    public function show($id)
    {
        return view('user.profile', ['user' => User::findOrFail($id)]);
    }
}

برای تعریف یک روت برای اکشن کنترلر، می‌توانید مانند زیر عمل کنید:

Route::get('user/{id}', 'UserController@show');

حالا، هنگامی که یک ریکوئست با URI روت مشخص شده، مطابقت داشته باشد، متد show کلاس UserController اجرا خواهد شد. طبیعتا، پارامترهای روت نیز به متد ارسال خواهند شد.

ارث‌بردن از کلاس پایه لاراول برای کنترلرها اجباری نیست. گرچه، در صورت عدم ارث‌بری از کلاس پایه، به فیچرهای خوبی مانند متدهای middleware و validate و dispatch دسترسی نخواهید داشت.

کنترلرها و نیم‌اسپیس‌ها

هنگام تعریف روت برای کنترلر، توجه به این نکته که نیازی به مشخص کردن نیم‌اسپیس کامل کنترلر نداریم، خیلی اهمیت دارد. از آنجایی که RouteServiceProvider فایل‌های روت درون یک گروه روت را به هراه نیم‌اسپیس آن‌ها لود می‌کند، تنها باید آن بخشی از نام کلاس را مشخص کنیم که بعد از بخش App\Http\Controllers نیم‌اسپیس می‌آید.

اگر تصمیم دارید با ساختاری تودرتو به کنترلرهای دایرکتوری App\Http\Controllers عمق بیشتری بدهید، از نام کلاس نسبت به نیم‌اسپیس ریشه App\Http\Controllers استفاده کنید. بنابراین، اگر نام کامل کلاس کنترلر شما App\Http\Controllers\Photos\AdminController باشد، باید روت‌های مربوط به این کنترلر را مانند مثال زیر رجیستر کنید:

Route::get('foo', 'Photos\AdminController@method');

کنترلرهای دارای تنها یک اکشن

اگر می‌خواهید یک کنترلر تعریف کنید که تنها یک اکشن را هندل می‌کند، می‌توانید متد __invoke را برای هندل کردن اکشن مورد نظرتان در کنترلر قرار دهید:

<?php

namespace App\Http\Controllers;

use App\User;
use App\Http\Controllers\Controller;

class ShowProfile extends Controller
{
    /**
     * Show the profile for the given user.
     *
     * @param  int  $id
     * @return Response
     */
    public function __invoke($id)
    {
        return view('user.profile', ['user' => User::findOrFail($id)]);
    }
}

هنگامی که روت‌هایی برای کنترلرهای تک اکشنه رجیستر می‌کنید، نیازی به مشخص کردن متد برای آن‌ها ندارید:

Route::get('user/{id}', 'ShowProfile');

با استفاده از آپشن --invokable کامند آتیزان make:controller می‌توانید یک کنترلر تک اکشنه تولید کنید که در هنگام تولید این کنترلر، متد __invoke در آن تعریف شده است:

php artisan make:controller ShowProfile --invokable

میدلورِ کنترلر

میدلورها می‌توانند در فایل‌های روت برای روت‌های کنترها تعیین شوند:

Route::get('profile', 'UserController@show')->middleware('auth');

گرچه، روش مناسب‌تر، مشخص کردن میدلور در متد constructor کنترلر است. با استفاده از متد middleware در constructor کنترلرتان، به راحتی می‌توانید برای اکشن کنترلر، میدلور مشخص کنید. حتی می‌توانید میدلوری را تنها به متدهای مشخصی از کلاس کنترلر محدود کنید:

class UserController extends Controller
{
    /**
     * Instantiate a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('auth');

        $this->middleware('log')->only('index');

        $this->middleware('subscribed')->except('store');
    }
}

کنترلرها امکان رجیستر کردن میدلورها با استفاده از یک کلوژر را نیز می‌دهند. این امکان روش مناسبی در اختیارتان می‌گذارد تا بدون تعریف یک کلاس میدلور کامل، برای یک کنترلر خاص، یک میدلور تعریف کنید:

$this->middleware(function ($request, $next) {
    // ...

    return $next($request);
});

با اینکه می‌توانید میدلوری را به مجموعه‌ای از اکشن‌های کنترلر تخصیص دهید؛ اما، این کار می‌تواند نشان‌دهنده این باشد که کنترلرتان خیلی در حال بزرگ شدن است. به جای این کار، کنترلرتان را به چندین کنترلر کوچک‌تر بشکنید.

ریسورس کنترلرها

مسیریابی ریسورس‌ها در لاراول، روت‌های CRUD معمول را تنها با یک خط کد، برای یک کنترلر تعیین می‌کند. برای مثال، ممکن است بخواهید کنترلری ایجاد کنید که تمام ریکوئست‌های HTTP برای تصاویر ذخیره شده توسطط اپلیکیشن‌تان را هندل کند. با استفاده از کامند آرتیزان make:controller، به سرعت می‌توانید چنین کنترلری ایجاد کنید:

php artisan make:controller PhotoController --resource

این کامند یک کنترلر در app/Http/Controllers/PhotoController.php تولید خواهد کرد. کنترلر تولید شده، حاوی یک متد برای هر عملیات ریسورسِ در دسترسی خواهد بود.

سپس، می‌توانید یک روت ریسورس‌فول برای این کنترلر رجیستر کنید:

Route::resource('photos', 'PhotoController');

این روت به تنهایی چندین روت برای هندل کردن اکشن‌های مختلف ریسورس را ایجاد می‌کند. کنترلر تولید شده، دارای متدهایی برای هر کدام از این اکشن‌ها است که HTTP Verbها و URIهایی که توسط این اکشن‌ها هندل می‌شوند از قبل برای آن‌ها مشخص شده است.

با ارسال یک آرایه حاوی مسیر و کنترلر به متد resources، می‌توانید چندین ریسورس کنترلر را به یکباره رجیستر کنید:

Route::resources([
    'photos' => 'PhotoController',
    'posts' => 'PostController'
]);

اکشن‌هایی که توسط ریسورس کنترلر هندل می‌شوند

Verb URI Action Route Name
GET /photos index photos.index
GET /photos/create create photos.create
POST /photos store photos.store
GET /photos/{photo} show photos.show
GET /photos/{photo}/edit edit photos.edit
PUT/PATCH /photos/{photo} update photos.update
DELETE /photos/{photo} destroy photos.destroy

مشخص کردن مدلِ ریسورس

اگر از روت مدل بایندینگ (تزریق مدل به واسطه روت) استفاده می‌کنید و می‌خواهید که یک instance از مدل را به متدهای ریسورس کنترلر type-hint کنید، می‌توانید هنگام تولید کنترلر، از آپشن --model استفاده کنید:

php artisan make:controller PhotoController --resource --model=Photo

جعل متدهای فرم (Spoofing Form Methods)

از آنجایی که فرم‌های HTML نمی‌توانند ریکوئست‌های PUT و PATCH و DELETE را بسازند، برای جعل (تغییر) این HTTP verbها نیاز خواهید داشت تا یک فیلد هیدن با نیم _method به فرم خود اضافه کنید. دایرکتیو @method در بلید می‌تواند این فیلد را برایتان ایجاد کند:

<form action="/foo/bar" method="POST">
    @method('PUT')
</form>

ریسورس روت‌های نیمه کامل

هنگامی که یک ریسورس روت تعریف می‌کنید، می‌توانید مجموعه‌ای از اکشن‌هایی که کنترلر باید به جای مجموعه کامل پیش‌فرض خود هندل کند را مشخص کنید:

Route::resource('photos', 'PhotoController')->only([
    'index', 'show'
]);

Route::resource('photos', 'PhotoController')->except([
    'create', 'store', 'update', 'destroy'
]);

مسیرهای اِی‌پی‌آی ریسورس (API Resource)

هنگامی که ریسورس روت‌هایی تعریف می‌کنید که مختص APIها هستند؛ معمولا می‌خواهید روت‌هایی مانند create و edit را که تمپلت HTML نمایش می‌دهند را از ریسورس خارج کنید. با استفاده از متد apiResource، می‌توانید به صورت خودکار این دو روت را حذف کنید:

Route::apiResource('photos', 'PhotoController');

با ارسال یک آرایه به متد apiResources، می‌توانید چندین اِی‌پی‌آی ریسورس کنترلر را به صورت یکباره رجیستر کنید:

Route::apiResources([
    'photos' => 'PhotoController',
    'posts' => 'PostController'
]);

برای تولید سریع یک اِی‌پی‌آی ریسورس کنترلر که متدهای create و edit را اینکلود نمی‌کند، در هنگام اجرای کامند آرتیزان make:controller، از سوئیچ --api استفاده کنید:

php artisan make:controller API/PhotoController --api

نامگذاری ریسورس روت‌ها

به صورت پیش‌فرض، تمامی اکشن‌های ریسورس کنترلرها دارای یک نام مسیر هستند؛ با این وجود؛ با ارسال یک آرایه names با آپشن‌های خود، می‌توانید این نام‌ها را بازنویسی کنید:

Route::resource('photos', 'PhotoController')->names([
    'create' => 'photos.build'
]);

نامگذاری پارامترهای ریسورس روت‌ها

به صورت پیش‌فرض، Route::resource با توجه به ورژن مفرد نام ریسورس، پارامترهای مسیر برای مسیرهای ریسورسی‌تان ایجاد می‌کند. با استفاده از متد parameters به سادگی می‌توانید این پارامترها را برای هر ریسورس بازنویسی کنید. آرایه‌ای که به متد parameters ارسال می‌شود باید یک آرایه انجمنی حاوی نام ریسورس‌ها و نام پارامترها باشد:

Route::resource('users', 'AdminUserController')->parameters([
    'users' => 'admin_user'
]);

مثال بالا، URIهای زیر را برای روت show ریسورس تولید می‌کند:

/users/{admin_user}

محلی‌سازی URIهای تولیدی ریسورس‌ها

Route::resource به صورت پیش‌فرض، URIهایی با verbهای انگلیسی برای ریسورس‌ها تولید می‌کند. اگر می‌خواهید verbهای اکشن‌های create و edit را محلی‌سازی کنید، می‌توانید از متد Route::resourceVerbs استفاده کنید. این کار می‌تواند در متد boot سرویس پرووایدر AppServiceProvider انجام شود.

use Illuminate\Support\Facades\Route;

/**
 * Bootstrap any application services.
 *
 * @return void
 */
public function boot()
{
    Route::resourceVerbs([
        'create' => 'crear',
        'edit' => 'editar',
    ]);
}

هنگامی که verbها کاستومایز شدند، رجیستر کردن یک ریسورس روت مانند Route::resource('fotos', 'PhotoController') منجر به تولید URIهای زیر خواهد شد:

/fotos/crear

/fotos/{foto}/editar

کامل کردن ریسورس کنترلرها

اگر می‌خواهید علاوه بر مجموعه ریسورس روت‌های پیش‌فرض، روت‌های بیشتری به یک ریسورس کنترلر اضافه کنید، باید روت‌های مورد نظر خود را قبل از فراخوانی Route::resource تعریف کنید؛ در غیر اینصورت، روت‌هایی که توسط متد resource تعریف می‌شوند، ممکن است به صورت ناخواسته بر روت‌های مکمل شما ارجحیت بیشتری داشته باشند.

Route::get('photos/popular', 'PhotoController@method');

Route::resource('photos', 'PhotoController');

به خاطر داشته باشید که کنترهای‌تان را متمرکز نگه دارید. اگر معمولا به متدهایی غیر از مجموعه اکشن‌های ریسورسی معمول نیاز دارید، شکستن کنترلر به چندین کنترلر کوچکتر روش بهتری است.

تزریق وابستگی (Dependency Injection) و کنترلرها

تزریق سازنده (Constructor Injection)

در لاراول سرویس کانتینر برای ریزالو کردن تمامی کنترلرهای لاراول استفاده می‌شود. در نتیجه، قادر خواهید بود تا هر وابستگی را که کنترلرتان به آن نیاز دارد در constructor کنترلر type-hint کنید. دیپندنسی‌های تعریف شده، به صورت خودکار ریزالو شده و به instance کنترلر اینجکت خواهند شد:

<?php

namespace App\Http\Controllers;

use App\Repositories\UserRepository;

class UserController extends Controller
{
    /**
     * The user repository instance.
     */
    protected $users;

    /**
     * Create a new controller instance.
     *
     * @param  UserRepository  $users
     * @return void
     */
    public function __construct(UserRepository $users)
    {
        $this->users = $users;
    }
}

طبیعتا، می‌توانید هر کنترکتی از لاراول را type-hint کنید. اگر کانتینر بتواند آن کانترکت را ریزالو کند، می‌توانید آن را type-hint کنید. بسته به اپلیکیشن‌تان، اینجکت کردن دیپندنسی‌ها به کنترلرتان می‌تواند تست‌پذیری بهتری فراهم کند.

تزریق متد (Method Injection)

همچنین علاوه بر تزریق سازنده، می‌توانید دیپندنسی‌ها را به متدهای کنترلرتان type-hint کنید. یک موردِ استفاده معمول برای متد اینجکشن، اینجکت کردن instance (نمونه) Illuminate\Http\Request به متدهای کنترلرتان است:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class UserController extends Controller
{
    /**
     * Store a new user.
     *
     * @param  Request  $request
     * @return Response
     */
    public function store(Request $request)
    {
        $name = $request->name;

        //
    }
}

همچنین، اگر متد کنترلرتان انتظار دریافت ورودی از پارامتر روت دارد، آرگومان‌های روت‌تان را پس از سایر دیپندنسی‌ها لیست کنید. برای مثال، اگر روت‌تان مانند زیر تعریف شده باشد:

Route::put('user/{id}', 'UserController@update');

هنوز هم می‌توانید Illuminate\Http\Request را type-hint کرده و با تعریف متد کنترلر خود به شکل زیر به پارامتر id خود دسترسی داشته باشید:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class UserController extends Controller
{
    /**
     * Update the given user.
     *
     * @param  Request  $request
     * @param  string  $id
     * @return Response
     */
    public function update(Request $request, $id)
    {
        //
    }
}

کش کردن روت‌ها

روت‌هایی که بر پایه کلوژرها رجیستر می‌شوند، قابلیت کش شدن ندارند. برای استفاده از قابلیت کش کردن روت‌ها، باید تمامی روت‌هایی که به شکل کلوژر رجیستر شده‌اند را به کلاس‌های کنترلر تبدیل کنید.

اگر اپلیکیشن‌تان تنها از روت‌های بر پایه کنترلر استفاده می‌کند، باید از مزایای کش کردن روت ارائه شده توسط لاراول بهره‌مند شوید. با کش کردن روت‌ها، به میزان قابل توجهی زمانی که طول می‌کشد تا تمامی روت‌های اپلیکیشن‌تان رجیستر شوند را کاهش می‌دهید. در برخی موارد، رجیستر کردن روت‌های‌تان می‌تواند حتی تا 100 برابر سریعتر انجام شود. برای کش کردن روت‌ها، تنها کافیست کامند آرتیزان route:cache را اجرا کنید:

php artisan route:cache

پس از اجرای این کامند، فایل مربوط به روت‌های کش شده در تمامی ریکوئست‌ها لود خواهد شد. به خاطر داشته باشید، اگر نیاز به افزودن روت جدیدی دارید، باید مجددا روت‌ها را کش کنید. به همین دلیل، کامند route:cache را تنها باید در حین دپلوی کردن پروژه‌تان اجرا کنید.

برای پاک کردن فایل مربوط به روت‌های کش شده، می‌توانید از کامند route:clear استفاده کنید:

php artisan route:clear