فصل سوم - مسیرها و کنترلرها
مسیرها و کنترلرها
وظیفه اساسی هر فریمورک اپلیکیشن وب این است که درخواستها را از کاربر دریافت کرده و پاسخها را معمولاً از طریق HTTP(S) ارسال کند. این بدین معناست که تعریف مسیرهای یک اپلیکیشن اولین و مهمترین پروژهای است که هنگام یادگیری یک فریمورک وب باید به آن پرداخته شود؛ بدون مسیرها، شما توانایی کمی برای تعامل با کاربر نهایی دارید.
در این فصل، ما به بررسی مسیرها در لاراول خواهیم پرداخت؛ نحوه تعریف آنها، نحوه ارجاع به کدی که باید اجرا شود و نحوه استفاده از ابزارهای مسیریابی لاراول برای مدیریت مجموعهای متنوع از نیازهای مسیریابی را خواهید دید.
مقدمه ای سریع به MVC، افعال HTTP و REST
بیشتر آنچه که در این فصل به آن اشاره خواهیم کرد مربوط به ساختار اپلیکیشنهای مدل-نما-کنترلر (MVC) است و بسیاری از مثالهایی که خواهیم دید از نامها و افعال مسیریابی مشابه REST استفاده میکنند، بنابراین بیایید یک نگاه سریع به هر دو داشته باشیم.
MVC چیست؟
در MVC، سه مفهوم اصلی وجود دارد:
مدل (Model)
نمایانگر یک جدول خاص از پایگاه داده (یا یک رکورد از آن جدول) است—به طور مثال "شرکت" یا "سگ".
نما (View)
نمایانگر الگوی (template) است که دادهها را به کاربر نهایی نمایش میدهد—به طور مثال "الگوی صفحه ورود با این مجموعه از HTML، CSS و JavaScript".
کنترلر (Controller)
مانند یک افسر پلیس، درخواستهای HTTP را از مرورگر دریافت کرده، دادههای صحیح را از پایگاه داده و سایر مکانیزمهای ذخیرهسازی استخراج کرده، ورودیهای کاربر را اعتبارسنجی کرده و در نهایت یک پاسخ به کاربر ارسال میکند.
در شکل 3-1، شما میتوانید مشاهده کنید که کاربر نهایی ابتدا از طریق مرورگر خود با کنترلر تعامل خواهد داشت و درخواست HTTP ارسال میکند. کنترلر در پاسخ به آن درخواست ممکن است دادهها را از مدل (پایگاه داده) بنویسد و یا از آن بخواند. سپس کنترلر احتمالاً دادهها را به یک نما ارسال خواهد کرد و سپس نما به کاربر نهایی بازگردانده میشود تا در مرورگر آنها نمایش داده شود.
شکل 3-1. یک تصویر ساده از معماری MVC
ما برخی از موارد استفاده لاراول را بررسی خواهیم کرد که با این نگاه نسبتاً ساده به معماری اپلیکیشن سازگار نیستند، بنابراین به MVC زیاد گیر نکنید، اما این حداقل شما را آماده میکند تا به باقی فصل نگاه کنید، وقتی که در مورد نماها و کنترلرها صحبت میکنیم.
انواع HTTP
REST چیست؟
ما REST را به طور مفصلتر در "مبانی APIهای شبیه به REST با فرمت JSON" در صفحه 345 پوشش خواهیم داد، اما به طور مختصر، این یک سبک معماری برای ساخت APIها است. زمانی که در این کتاب در مورد REST صحبت میکنیم، بیشتر به چند ویژگی اشاره خواهیم کرد، از جمله:
-
-
ساختار آن حول یک منبع اصلی در هر زمان (مثلاً tasks)
-
تعاملات با استفاده از ساختارهای URL قابل پیشبینی و افعال HTTP (همانطور که در جدول 3-1 مشاهده میکنید)
-
بازگشت دادهها به فرمت JSON و معمولاً درخواست آنها با فرمت JSON
-
این موارد بیشتر هستند، اما معمولاً "RESTful" به معنای "الگوبرداری از این ساختارهای مبتنی بر URL به گونهای که بتوانیم درخواستهای قابل پیشبینی مانند GET /tasks/14/edit برای صفحه ویرایش داشته باشیم" است. این موضوع حتی زمانی که API نمیسازیم هم مهم است، زیرا ساختارهای مسیریابی لاراول بر اساس ساختارهای شبیه به REST است، همانطور که در جدول 3-1 مشاهده میکنید.
APIهای مبتنی بر REST عمدتاً از همین ساختار پیروی میکنند، با این تفاوت که آنها مسیری برای ایجاد یا ویرایش ندارند، زیرا APIها فقط اقداماتی را نمایندگی میکنند، نه صفحاتی که برای این اقدامات آماده میشوند.
تعریف مسیریابی ها
افعال مسیریابی
شاید متوجه شده باشید که در تعریف مسیرها از Route::get() استفاده کردهایم. این بدین معنی است که ما به لاراول گفتهایم که این مسیرها را تنها زمانی تطبیق دهد که درخواست HTTP از روش GET استفاده کند. اما اگر یک فرم با روش POST باشد، یا شاید برخی از درخواستهای JavaScript با روشهای PUT یا DELETE ارسال شوند؟ چند گزینه دیگر برای فراخوانی متدها در تعریف مسیر وجود دارد که در مثال 3-3 نشان داده شده است.
Route::get('/', function () {
return 'Hello, World!';
});
Route::post('/', function () {
// رسیدگی به زمانی که کسی یک درخواست POST به این مسیر ارسال کند
});
Route::put('/', function () {
// رسیدگی به زمانی که کسی یک درخواست PUT به این مسیر ارسال کن
});
Route::delete('/', function () {
// رسیدگی به زمانی که کسی یک درخواست DELETE به این مسیر ارسال کند
});
Route::any('/', function () {
// رسیدگی به هر نوع درخواست (با هر متدی) به این مسیر
});
Route::match(['get', 'post'], '/', function () {
// رسیدگی به درخواستهای GET یا POST به این مسیر
});
مدیریت مسیرها
همانطور که احتمالاً حدس زدهاید، ارسال یک closure به تعریف مسیر تنها راهی نیست که میتوانید به لاراول بگویید چطور مسیر را حل کند. closures سریع و ساده هستند، اما هر چه اپلیکیشن شما بزرگتر شود، قرار دادن تمام منطق مسیریابی در یک فایل دشوارتر میشود. علاوه بر این، برنامههایی که از route closures استفاده میکنند نمیتوانند از کش مسیریابی لاراول بهرهمند شوند (در مورد آن بعداً صحبت خواهیم کرد)، که میتواند تا صدها میلیثانیه از زمان هر درخواست کم کند.
گزینه رایج دیگر این است که به جای closure، نام کنترلر و متد آن را به عنوان یک رشته ارسال کنید، مانند مثال 3-4.
مثال 3-4. مسیرهایی که متدهای کنترلر را فراخوانی میکنند
use App\Http\Controllers\WelcomeController;
Route::get('/', [WelcomeController::class, 'index']);
این به لاراول میگوید که درخواستهای مربوط به این مسیر را به متد index() از کنترلر App\Http\Controllers\WelcomeController ارجاع دهد. این متد همان پارامترها را دریافت خواهد کرد و دقیقاً مشابه closure که بهجای آن ممکن بود قرار دهید، رفتار خواهد کرد.
نحوه ارجاع به متدهای کنترلر در لاراول
لاراول یک ساختار برای ارجاع به متد خاصی در یک کنترلر خاص دارد: |
پارامترهای مسیر
نام گذاری مسیرها
سادهترین روش برای ارجاع به این مسیرها در سایر نقاط برنامه، استفاده از مسیر آنها است. Laravel یک تابع کمکی url() ارائه میدهد که این پیوندها را در نمایشهایتان سادهتر میکند، اگر به آن نیاز داشته باشید؛ به عنوان مثال در مثال 3-9 مشاهده کنید. این تابع کمکی مسیر شما را با دامنه کامل سایتتان پیشوند میدهد.
مثال 3-9. تابع کمکی url()
<a href="<?php echo url('/'); ?>">
// خروجی: <a href="http://myapp.com/">
با این حال، Laravel به شما این امکان را میدهد که برای هر مسیر یک نام مشخص کنید، که به شما اجازه میدهد به آن بدون نیاز به ارجاع صریح به URL، اشاره کنید. این کار مفید است زیرا به شما این امکان را میدهد که برای مسیرهای پیچیده نامهای ساده بدهید و همچنین با ارجاع به آنها با نام، دیگر نیازی به نوشتن دوباره پیوندهای فرانتاند نخواهید داشت اگر مسیرها تغییر کنند (به مثال 3-10 مراجعه کنید).
مثال 3-10. تعریف نام مسیرها
// تعریف یک مسیر با استفاده از name() در routes/web.php:
Route::get('members/{id}', [\App\Http\Controller\MemberController::class, 'show'])
->name('members.show');
// لینک دادن به مسیر در یک نمایش با استفاده از تابع کمکی route():
<a href="<?php echo route('members.show', ['id' => 14]); ?>">
این مثال چند مفهوم جدید را معرفی میکند. اولاً، ما از تعریف روان مسیرها استفاده میکنیم تا نام را با استفاده از روش name() پس از متد get() اضافه کنیم. این روش به ما اجازه میدهد تا نام مسیر را تعیین کنیم و آن را به یک مستعار کوتاه تبدیل کنیم تا ارجاع به آن در سایر بخشها راحتتر باشد.
در مثال ما، نام این مسیر members.show است؛ استفاده از resourcePlural.action یک عرف رایج در Laravel برای نامگذاری مسیرها و نمایشها است.
عرفهای نامگذاری مسیرها
شما میتوانید هر نامی برای مسیر خود انتخاب کنید، اما عرف رایج این است که از شکل جمع نام منبع استفاده کنید، سپس یک نقطه و در نهایت عمل مورد نظر را بنویسید. بنابراین، در اینجا مسیرهایی که معمولاً برای یک منبع با نام "photo" استفاده میشوند آورده شده است:
photos.index
photos.create
photos.store
photos.show
photos.edit
Photos.update
photos.destroy
برای یادگیری بیشتر در مورد این عرفها، به فصل "Resource Controllers" در صفحه 45 مراجعه کنید.
این مثال همچنین معرفی کنندهی هلیپر route() است. درست مانند url()، این هلیپر برای استفاده در ویوها طراحی شده است تا لینکدهی به یک مسیر نامگذاریشده را سادهتر کند. اگر مسیر پارامتر نداشته باشد، میتوانید به سادگی نام مسیر را به آن بدهید (route('members.index')) و یک رشته مسیر دریافت خواهید کرد ("http://myapp.com/members"). اگر مسیر پارامترهایی داشته باشد، آنها را به صورت یک آرایه در پارامتر دوم مانند مثال 3-10 ارسال کنید. به طور کلی، من توصیه میکنم که به جای استفاده از مسیرها از نام مسیرها برای ارجاع به مسیرها استفاده کنید و به همین دلیل از هلیپر route() به جای url() استفاده کنید. گاهی اوقات ممکن است کمی پیچیده شود—برای مثال، اگر با زیر دامنههای متعدد کار میکنید— اما این روش انعطافپذیری بسیار زیادی فراهم میکند تا ساختار مسیریابی برنامه را بدون هزینه زیاد تغییر دهید.
انتقال پارامترهای مسیر به هلیپر route()
زمانی که مسیر شما پارامترهایی دارد (مثلاً users/id)، باید این پارامترها را هنگام استفاده از هلیپر route() برای تولید لینک به مسیر مشخص کنید.
چندین روش مختلف برای ارسال این پارامترها وجود دارد. بیایید یک مسیر تعریف شده به صورت users/userId/comments/commentId را فرض کنیم. اگر شناسه کاربر 1 و شناسه کامنت 2 باشد، چند گزینه مختلف که در اختیار داریم را بررسی میکنیم:
گزینه 1:
route('users.comments.show', [1, 2])
// http://myapp.com/users/1/comments/2
گزینه 2:
route('users.comments.show', ['userId' => 1, 'commentId' => 2])
// http://myapp.com/users/1/comments/2
گزینه 3:
route('users.comments.show', ['commentId' => 2, 'userId' => 1])
// http://myapp.com/users/1/comments/2
گزینه 4:
route('users.comments.show', ['userId' => 1, 'commentId' => 2, 'opt' => 'a'])
// http://myapp.com/users/1/comments/2?opt=a
همانطور که میبینید، مقادیر آرایههای بدون کلید به ترتیب اختصاص داده میشوند؛ مقادیر آرایههای با کلید با پارامترهای مسیر که با کلیدهای آنها مطابقت دارند، تطابق داده میشوند و هر چیزی که باقی میماند به عنوان پارامتر پرس و جو (query parameter) اضافه میشود.
گروههای مسیر (Route Groups)
اغلب یک گروه از مسیرها ویژگی خاصی را به اشتراک میگذارند—مثلاً نیاز به احراز هویت خاصی، پیشوند مسیر، یا شاید فضای نام کنترلر. تعریف دوباره این ویژگیهای مشترک در هر مسیر نه تنها خستهکننده به نظر میرسد بلکه میتواند شکل فایل مسیرهای شما را به هم بریزد و برخی از ساختارهای برنامه شما را مبهم کند.
گروههای مسیر این امکان را فراهم میکنند که با گروهبندی چندین مسیر با هم و اعمال تنظیمات پیکربندی مشترک به یکباره به کل گروه، این تکرار را کاهش دهید. علاوه بر این، گروههای مسیر نشانههای بصری برای توسعهدهندگان آینده (و برای مغز خودتان) هستند که نشان میدهند این مسیرها با هم گروهبندی شدهاند.
برای گروهبندی دو یا چند مسیر، شما مسیرها را با یک گروه مسیر احاطه میکنید، همانطور که در مثال 3-11 نشان داده شده است. در واقع، شما یک closure به تعریف گروه میدهید و مسیرهای گروهبندیشده را در داخل آن closure تعریف میکنید.
مثال 3-11. تعریف یک گروه مسیر:
Route::group(function () {
Route::get('hello', function () {
return 'Hello';
});
Route::get('world', function () {
return 'World';
});
});
به طور پیشفرض، یک گروه مسیر در واقع کاری انجام نمیدهد. هیچ تفاوتی بین استفاده از گروه در مثال 3-11 و جدا کردن یک بخش از مسیرها با نظرات کد وجود ندارد.
میانه افزار (Middleware)
پیشوندهای مسیر
اگر یک گروه از مسیرها دارید که بخشی از مسیرشان مشابه است—for example, if your site’s dashboard is prefixed with /dashboard—میتوانید از گروههای مسیر برای سادهسازی این ساختار استفاده کنید (مشاهده کنید مثال 3-13).
مثال 3-13. افزودن پیشوند به یک گروه از مسیرها
Route::prefix('dashboard')->group(function () {
Route::get('/', function () {
// Handles the path /dashboard
});
Route::get('users', function () {
// Handles the path /dashboard/users
});
});
توجه داشته باشید که هر گروهی که پیشوند دارد، یک مسیر / نیز دارد که نمایانگر ریشه پیشوند است—in Example 3-13 that’s /dashboard.
مسیریابی زیر دامنهها
پیشوندهای نام مسیر
اینکه نام مسیرها بازتابدهنده زنجیره ارثبری عناصر مسیر باشد امری رایج است، بنابراین مسیر users/comments/5 توسط مسیری به نام users.comments.show پاسخ داده میشود. در این حالت، معمول است که از یک گروه مسیر برای تمام مسیرهایی که زیرمنطقه منابع users.comments هستند استفاده شود.
دقیقا همانطور که میتوانیم پیشوندهایی را برای بخشهای URL اضافه کنیم، میتوانیم پیشوندهایی را نیز به نام مسیر اضافه کنیم. با پیشوندهای نام گروه مسیر، میتوانیم تعریف کنیم که هر مسیری در این گروه باید یک رشته خاص را به نام خود اضافه کند. در این زمینه، ما "users." را به هر نام مسیر اضافه میکنیم، سپس "comments." (مشاهده کنید مثال 3-16).
مثال 3-16. پیشوندهای نام گروه مسیر
Route::name('users.')->prefix('users')->group(function () {
Route::name('comments.')->prefix('comments')->group(function () {
Route::get('{id}', function () {
// ...
})->name('show');
});
});
گروه مسیرهای کنترلر
زمانی که مسیرهایی را گروهبندی میکنید که توسط همان کنترلر پاسخ داده میشوند، مانند زمانی که ما در حال نمایش، ویرایش و حذف کاربران هستیم، میتوانیم از متد controller() گروه مسیر استفاده کنیم، همانطور که در مثال 3-17 نشان داده شده است، تا نیازی به تعریف کامل زوج نام کنترلر و متد برای هر مسیر نباشد.
مثال 3-17. گروه مسیرهای کنترلر
use App/Http/Controllers/UserController;
Route::controller(UserController::class)->group(function () {
Route::get('/', 'index');
Route::get('{id}', 'show');
});
مسیرهای پیشفرض (Fallback Routes)
در لاراول میتوانید یک "مسیر پیشفرض" تعریف کنید (که باید در انتهای فایل مسیرها قرار بگیرد) تا درخواستهای غیرمستقیم را شناسایی کند:
Route::fallback(function () {
//
});
مسیرهای امضا شده
امضای یک مسیر
برای ساخت یک URL امضاشده جهت دسترسی به یک مسیر خاص، مسیر باید نام داشته باشد:
Route::get('invitations/{invitation}/{answer}', InvitationController::class)->name('invitations');
برای تولید یک لینک عادی به این مسیر، شما میتوانید از کمکفنآوری route() استفاده کنید، همانطور که قبلاً توضیح دادهایم، اما میتوانید از فاساد URL نیز برای انجام همان کار استفاده کنید:
URL::route('invitations', ['invitation' => 12345, 'answer' => 'yes']);
برای تولید یک لینک امضاشده به این مسیر، به سادگی از متد signedRoute() استفاده کنید.
و اگر میخواهید یک مسیر امضاشده با تاریخ انقضا ایجاد کنید، از temporarySignedRoute() استفاده کنید:
// تولید یک لینک عادی
URL::route('invitations', ['invitation' => 12345, 'answer' => 'yes']);
// تولید یک لینک امضاشده
URL::signedRoute('invitations', ['invitation' => 12345, 'answer' => 'yes']);
// تولید یک لینک امضاشده موقت (با تاریخ انقضا)
URL::temporarySignedRoute(
'invitations',
now()->addHours(4),
['invitation' => 12345, 'answer' => 'yes']
);
استفاده از کمکفنآوری now() لاراول یک کمکفنآوری now() ارائه میدهد که معادل Carbon::now() است؛ این کمکفنآوری یک شیء Carbon برمیگرداند که نمایانگر تاریخ و زمان کنونی است. Carbon یک کتابخانه تاریخ و زمان است که با لاراول گنجانده شده است. |
مدیریت تغییرات در مسیرها برای لینکهای امضا شده
حالا که یک لینک به مسیر امضا شده تولید کردهاید، باید در برابر دسترسیهای بدون امضا محافظت کنید. سادهترین گزینه استفاده از میانهافزار signed است:
Route::get('invitations/{invitation}/{answer}', InvitationController::class)
->name('invitations')
->middleware('signed');
اگر ترجیح میدهید، میتوانید بهطور دستی از روش hasValidSignature() در شیء Request برای اعتبارسنجی استفاده کنید به جای استفاده از میانهافزار signed:
class InvitationController
{
public function __invoke(Invitation $invitation, $answer, Request $request)
{
if (! $request->hasValidSignature()) {
abort(403);
}
//
}
}
ویوها
بازگشت مسیرهای ساده به صورت مستقیم با استفاده از Route::view()
از آنجا که بسیار رایج است که یک مسیر فقط ویو را بدون دادههای سفارشی بازگشت دهد، لاراول این امکان را میدهد که یک مسیر به عنوان مسیر "ویو" تعریف شود بدون اینکه حتی نیاز به ارسال یک بستن مسیر یا ارجاع به کنترلر/متد داشته باشد، مانند مثال 3-20.
مثال 3-20. استفاده از Route::view()
// بازگشت به resources/views/welcome.blade.php
Route::view('/', 'welcome');
// ارسال داده ساده به Route::view()
Route::view()
Route::view('/', 'welcome', ['User' => 'Michael']);
استفاده از View Composers برای اشتراکگذاری متغیرها با هر ویو
گاهی اوقات ارسال مکرر یک متغیر میتواند دردسرساز شود. ممکن است متغیری وجود داشته باشد که بخواهید در تمام ویوهای سایت یا در دسته خاصی از ویوها یا یک زیرویو خاص در دسترس باشد—برای مثال، تمام ویوهای مربوط به وظایف یا partial مربوط به header.
امکان بهاشتراکگذاری برخی متغیرها با تمام templateها یا فقط templateهای خاص وجود دارد، مانند کد زیر:
view()->share('variableName', 'variableValue');
برای یادگیری بیشتر، به بخش "View Composers and Service Injection" مراجعه کنید.
کنترلرها
گرفتن ورودی کاربر
دومین عمل رایج در متدهای کنترلر، گرفتن ورودی از کاربر و عمل کردن بر روی آن است. این موضوع چند مفهوم جدید را معرفی میکند، بنابراین بیایید نگاهی به یک کد نمونه بیندازیم و قطعات جدید را بررسی کنیم.
اول، مسیر خود را متصل میکنیم؛ به مثال 3-25 نگاه کنید.
مثال 3-25. اتصال عملیات فرم پایه
// routes/web.php
Route::get('tasks/create', [TaskController::class, 'create']);
Route::post('tasks', [TaskController::class, 'store']);
توجه کنید که ما عملیات GET برای tasks/create (که فرم ایجاد یک وظیفه جدید را نشان میدهد) و عملیات POST برای tasks (که دادههای فرم ما به آنجا ارسال میشود زمانی که وظیفه جدیدی ایجاد میکنیم) را متصل کردهایم. میتوانیم فرض کنیم که متد create() در کنترلر ما فقط فرم را نمایش میدهد، پس بیایید نگاهی به متد store() در مثال 3-26 بیندازیم.
مثال 3-26. متد کنترلر ورودی فرم رایج
// TaskController.php
...
public function store()
{
Task::create(request()->only(['title', 'description']));
return redirect('tasks');
}
این مثال از مدلهای Eloquent و عملکرد redirect() استفاده میکند، و ما در مورد آنها بیشتر صحبت خواهیم کرد، اما فعلاً بیایید سریعاً درباره چگونگی دریافت دادهها بحث کنیم.
ما از کمکفن request() برای نمایش درخواست HTTP استفاده میکنیم (و در ادامه بیشتر در مورد آن صحبت خواهیم کرد) و از متد only() آن برای دریافت فقط فیلدهای عنوان و توضیحات که کاربر ارسال کرده استفاده میکنیم.
سپس این دادهها را به متد create() مدل Task خود ارسال میکنیم که یک نمونه جدید از Task را ایجاد میکند با این تفاوت که عنوان به عنوان عنوان ورودی و توضیحات به عنوان توضیحات ورودی تنظیم میشود. در نهایت، ما کاربر را به صفحهای که تمام وظایف را نمایش میدهد هدایت میکنیم.
در اینجا چند لایه انتزاع وجود دارد که در ادامه به آنها خواهیم پرداخت، اما بدانید که دادههایی که از متد only() میآید از همان مجموعه دادهای است که تمامی متدهای رایج مورد استفاده در شیء Request از آن استفاده میکنند، از جمله all() و get(). مجموعه دادهای که هرکدام از این متدها از آن میکشند، تمامی دادههای ارائه شده توسط کاربر را شامل میشود، چه از پارامترهای query یا مقادیر POST. پس کاربر دو فیلد در صفحه "افزودن وظیفه" پر کرده است: "عنوان" و "توضیحات."
برای شکستن کمی از این انتزاع، request()->only() یک آرایه انجمنی از نامهای ورودی را گرفته و آنها را بازمیگرداند:
request()->only(['title', 'description']);
// returns:
[
'title' => 'Whatever title the user typed on the previous page',
'description' => 'Whatever description the user typed on the previous page',
]
و Task::create() یک آرایه انجمنی را گرفته و یک وظیفه جدید از آن میسازد:
Task::create([
'title' => 'Buy milk',
'description' => 'Remember to check the expiration date this time, Norbert!',
]);
ترکیب اینها با هم یک وظیفه با تنها فیلدهای "عنوان" و "توضیحات" ارائه شده توسط کاربر ایجاد میکند.
تزریق وابستگیها به کنترلرها
کنترلرهای منبع (Resource Controllers)
گاهی اوقات نامگذاری متدها در کنترلرها میتواند سختترین بخش نوشتن یک کنترلر باشد. خوشبختانه، لاراول برای تمامی مسیرهای یک کنترلر REST/CRUD سنتی (که در لاراول به آن resource controller گفته میشود) یک سری قواعد نامگذاری دارد. علاوه بر این، لاراول یک تولیدکننده (generator) داخلی و یک روش تعریف مسیر راحت ارائه میدهد که به شما اجازه میدهد کل یک کنترلر منبع (resource controller) را بهصورت یکجا متصل کنید.
برای مشاهدهی متدهایی که لاراول برای یک کنترلر منبع (resource controller) انتظار دارد، یک کنترلر جدید را از طریق خط فرمان ایجاد کنید:
php artisan make:controller MySampleResourceController --resource
اکنون فایل app/Http/Controllers/MySampleResourceController.php را باز کنید. خواهید دید که این فایل از پیش شامل چندین متد است. بیایید بررسی کنیم که هر یک از آنها چه مفهومی دارند. بهعنوان نمونه، ما از Task استفاده خواهیم کرد.
متدهای کنترلرهای منبع در لاراول
جدولی که قبلاً دیدهایم را به خاطر دارید؟ جدول 3-1 نشان میدهد که هر متد پیشفرض در کنترلرهای منبع لاراول، با چه فعل HTTP (HTTP verb)، آدرس URL، نام متد کنترلر و نام مسیر مرتبط است.
اتصال یک کنترلر منبع (Binding a Resource Controller)
پس، دیدیم که لاراول نامهای مسیر استانداردی را پیشنهاد میدهد و همچنین ایجاد یک کنترلر منبع که دارای متدهای پیشفرض برای هر یک از این مسیرها باشد، بسیار آسان است. خوشبختانه، نیازی نیست که این مسیرها را بهصورت دستی برای هر متد از کنترلر خود تعریف کنید، مگر اینکه بخواهید این کار را انجام دهید.
یک ترفند برای این کار وجود دارد که اتصال کنترلر منبع (resource controller binding) نام دارد. به مثال 3-28 توجه کنید:
مثال 3-28. اتصال کنترلر منبع
// routes/web.php
Route::resource('tasks', TaskController::class);
این دستور بهصورت خودکار تمامی مسیرهای فهرستشده در جدول 3-1 را برای این منبع (resource) به متدهای مناسب در کنترلر مشخصشده متصل میکند. همچنین این مسیرها را نامگذاری میکند؛ برای مثال، متد index() در کنترلر TaskController بهصورت tasks.index نامگذاری خواهد شد.
artisan route:listاگر در شرایطی قرار گرفتید که نمیدانستید چه مسیرهایی (routes) در برنامهی شما در دسترس هستند، یک ابزار برای این کار وجود دارد:
با این کار، لیستی از تمامی مسیرهای موجود در برنامهی خود دریافت خواهید کرد. اما من دستور زیر را ترجیح میدهم:
این دستور باعث میشود مسیرهای اضافهای که وابستگیهای پروژه (dependencies) برای عملکرد خود ثبت کردهاند، نمایش داده نشوند (به شکل 3-2 مراجعه کنید).
|
کنترلرهای API Resource
هنگام کار با RESTful APIs، لیست عملیاتهای ممکن روی یک منبع (resource) مشابه کنترلرهای HTML معمولی نیست.
به عنوان مثال، در API میتوان یک درخواست POST برای ایجاد یک منبع ارسال کرد، اما نمایش یک فرم برای ایجاد منبع (مانند صفحات HTML) بیمعنی است.
برای ایجاد یک API resource controller که دقیقاً مشابه resource controller معمولی است اما اکشنهای create و edit را شامل نمیشود، میتوانید از فلگ --api در هنگام ایجاد کنترلر استفاده کنید:
php artisan make:controller MySampleResourceController --api
برای اتصال (bind) یک API resource controller، به جای resource() از متد apiResource() استفاده کنید، همانطور که در مثال 3-29 نشان داده شده است.
مثال 3-29: اتصال API resource controller
// routes/web.php
Route::apiResource('tasks', TaskController::class);
کنترلرهای تکعملکردی (Single Action Controllers)
اتصال مدل به مسیر (Route Model Binding)
یکی از الگوهای رایج مسیریابی در لاراول این است که اولین خط هر متد کنترلر سعی میکند منبع (resource) موردنظر را بر اساس شناسه (ID) پیدا کند، همانطور که در مثال 3-31 مشاهده میکنید.
مثال 3-31: دریافت یک منبع بر اساس مسیر
Route::get('conferences/{id}', function ($id) {
$conference = Conference::findOrFail($id);
});
لاراول ویژگیای را فراهم میکند که این الگو را ساده میکند و به آن route model binding گفته میشود. این ویژگی به شما اجازه میدهد مشخص کنید که یک نام پارامتر خاص (مثلاً {conference}) به resolver مسیر اعلام کند که باید یک رکورد Eloquent با آن ID را در دیتابیس جستوجو کرده و آن را بهعنوان پارامتر ارسال کند، نه فقط خود ID را.
دو نوع route model binding وجود دارد: implicit و custom (یا explicit).
اتصال مدل به مسیر بهصورت ضمنی (Implicit Route Model Binding)
سادهترین روش برای استفاده از Route Model Binding این است که نام پارامتر مسیر را متناسب با مدل تنظیم کنید (مثلاً بهجای $id از $conference استفاده کنید)، سپس این پارامتر را در متد کنترلر یا Closure typehint کنید و از همان نام متغیر در داخل متد استفاده کنید.
این روش در عمل سادهتر از توضیح آن است، پس به مثال 3-32 توجه کنید:
مثال 3-32: استفاده از Implicit Route Model Binding
Route::get('conferences/{conference}', function (Conference $conference) {
return view('conferences.show')->with('conference', $conference);
});
چون پارامتر مسیر ({conference}) با پارامتر متد ($conference) یکسان است و پارامتر متد نیز با مدل Conference (Conference $conference) typehint شده است، لاراول این را بهعنوان Route Model Binding در نظر میگیرد.
هر بار که این مسیر فراخوانی شود، لاراول فرض میکند که مقدار ارسالشده در URL بهجای {conference}، یک شناسه (ID) است که باید برای جستجوی یک مدل Conference استفاده شود. سپس، نمونهی بازیابیشده از مدل بهصورت خودکار به متد کنترلر یا Closure ارسال میشود.
سفارشیسازی کلید مسیر برای یک مدل Eloquent هر زمان که یک مدل Eloquent از طریق یک بخش URL جستجو شود (معمولاً به دلیل Route Model Binding)، بهصورت پیشفرض Eloquent مقدار را از ستون کلید اصلی (ID) جستجو میکند. برای تغییر ستونی که Eloquent برای جستجو در URL استفاده میکند، کافی است متدی به نام getRouteKeyName() را در مدل خود اضافه کنید:
اکنون، در مسیری مانند conferences/{conference}، لاراول مقدار دریافتشده را بهجای ID از ستون slug گرفته و جستجو را بر اساس آن انجام خواهد داد. |
سفارشیسازی کلید مسیر در یک مسیر خاص
در Laravel، بهجای تغییر کلید مسیر بهصورت کلی، میتوانید آن را فقط در یک مسیر خاص تغییر دهید. برای این کار، کافی است در تعریف مسیر، بعد از نام متغیر مسیر، یک کولون (:) و نام ستونی که میخواهید جستجو بر اساس آن انجام شود را اضافه کنید:
|
اگر در URL خود دو بخش داینامیک داشته باشید (برای مثال: organizers/{organizer}/conferences/{conference:slug})، لاراول بهطور خودکار تلاش میکند تا پرسوجوهای مدل دوم را فقط به موارد مرتبط با مدل اول محدود کند. بنابراین، لاراول مدل Organizer را برای رابطه conferences بررسی خواهد کرد و اگر این رابطه وجود داشته باشد، فقط کنفرانسهایی را برمیگرداند که با Organizer پیدا شده در بخش اول مرتبط هستند.
use App\Models\Conference;
use App\Models\Organizer;
Route::get(
'organizers/{organizer}/conferences/{conference:slug}',
function (Organizer $organizer, Conference $conference) {
return $conference;
});
وصل کردن سفارشی مدل به روت
کش کردن مسیرها
شبیهسازی روش فرم
گاهی اوقات باید به صورت دستی مشخص کنید که یک فرم باید با کدام فعل HTTP ارسال شود. فرمهای HTML تنها اجازه ارسال درخواست به روشهای GET یا POST را میدهند، بنابراین اگر میخواهید از فعل دیگری استفاده کنید، باید خودتان آن را مشخص کنید.
افعال HTTP در لاراول
شبیهسازی روش HTTP در فرمهای HTML
برای اطلاع دادن به لاراول که فرمی که در حال ارسال آن هستید باید به عنوان چیزی غیر از یک POST در نظر گرفته شود، یک متغیر مخفی با نام _method و مقدار "PUT"، "PATCH" یا "DELETE" اضافه کنید، و لاراول آن ارسال فرم را مانند یک درخواست با آن فعل تطبیق خواهد داد و مسیریابی خواهد کرد.
فرم در مثال 3-35، از آنجا که فعل "DELETE" را به لاراول ارسال میکند، با مسیرهای تعریفشده با Route::delete() تطبیق پیدا خواهد کرد، اما با مسیرهای Route::post() تطبیق پیدا نخواهد کرد.
مثال 3-35. شبیهسازی روش فرم
<form action="/tasks/5" method="POST">
<input type="hidden" name="_method" value="DELETE">
<!-- یا: -->
@method('DELETE')
</form>
حفاظت در برابر CSRF
اگر تاکنون سعی کردهاید فرمی را در یک اپلیکیشن لاراول ارسال کنید، از جمله فرم موجود در مثال 3-35، احتمالاً با خطای TokenMismatchException مواجه شدهاید. بهطور پیشفرض، تمام مسیرها در لاراول به جز مسیرهای "فقط خواندنی" (آنهایی که از GET، HEAD یا OPTIONS استفاده میکنند) در برابر حملات جعل درخواست بینسایتی (CSRF) محافظت میشوند و بهوسیله نیاز به یک توکن، به شکل یک ورودی به نام _token، که باید همراه با هر درخواست ارسال شود، محافظت میشوند. این توکن در ابتدای هر جلسه ایجاد میشود و هر مسیر غیر از مسیرهای فقط خواندنی، _token ارسالشده را با توکن جلسه مقایسه میکند.
CSRF چیست؟ حملات جعل درخواست بینسایتی (Cross-Site Request Forgery یا CSRF) زمانی رخ میدهد که یک وبسایت خود را بهجای وبسایت دیگری جا میزند. هدف این است که کسی دسترسی کاربران شما به وبسایت شما را با ارسال فرمها از وبسایت خود به وبسایت شما از طریق مرورگر کاربر وارد شده، تصرف کند. بهترین راه برای مقابله با حملات CSRF، محافظت از تمام مسیرهای ورودی—POST، DELETE و غیره—با استفاده از یک توکن است که لاراول بهطور پیشفرض این کار را انجام میدهد. |
شما دو گزینه برای رفع این خطای CSRF دارید. اولین و ترجیح دادهشدهترین روش، افزودن ورودی _token به هر یک از ارسالهای فرمهای شما است. در فرمهای HTML، راه سادهای برای انجام این کار وجود دارد، همانطور که در مثال 3-36 میبینید.
مثال 3-36. توکنهای CSRF
<form action="/tasks/5" method="POST">
@csrf
</form>
در برنامههای JavaScript، کمی کار بیشتری نیاز است، اما نه خیلی زیاد. رایجترین راهحل برای سایتهای استفادهکننده از فریمورکهای JavaScript، ذخیره کردن توکن در هر صفحه در یک تگ مانند این است:
<meta name="csrf-token" content="<?php echo csrf_token(); ?>">
ذخیره کردن توکن در تگ این امکان را میدهد که آن را به هدر HTTP صحیح متصل کنید، که میتوانید این کار را بهصورت گلوبال برای تمام درخواستها از فریمورک JavaScript خود انجام دهید، همانطور که در مثال 3-37 نشان داده شده است.
مثال 3-37. اتصال گلوبال هدر برای CSRF
// در jQuery:
$.ajaxSetup({
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
}
});
// با Axios: این بهطور خودکار آن را از یک کوکی میگیرد. هیچ کاری لازم نیست!
لاراول هدر X-CSRF-TOKEN (و X-XSRF-TOKEN که توسط Axios و سایر فریمورکهای JavaScript مانند Angular استفاده میشود) را در هر درخواست بررسی میکند و توکنهای معتبر ارسالشده در آنجا باعث میشوند که محافظت CSRF بهعنوان برآوردهشده علامتگذاری شود.
اتصال توکنهای CSRF با Vue Resource راهاندازی توکن CSRF در Vue Resource کمی متفاوت از لاراول است؛ برای مثالها به مستندات Vue Resource مراجعه کنید. |
ریدایرکت ها
redirect()->to()
امضای متد برای متد to() برای ریدایرکتها به این صورت است:
function to($to = null, $status = 302, $headers = [], $secure = null)
$to یک مسیر داخلی معتبر است، $status وضعیت HTTP است (که بهطور پیشفرض 302 است)، $headers به شما این امکان را میدهد که مشخص کنید کدام هدرهای HTTP را همراه با ریدایرکت ارسال کنید، و $secure به شما این امکان را میدهد که انتخاب پیشفرض HTTP یا HTTPS (که معمولاً بر اساس URL درخواست فعلی شما تنظیم میشود) را تغییر دهید. مثال ۳-۳۹ استفاده از این متد را نشان میدهد.
مثال ۳-۳۹. redirect()->to()
Route::get('redirect', function () {
return redirect()->to('home');
// یا همان، استفاده از میانبر:
return redirect('home');
});
redirect()->route()
redirect()->back()
به دلیل برخی از راحتیهای داخلی پیادهسازی جلسه در لاراول، برنامه شما همیشه از صفحهای که کاربر قبلاً بازدید کرده اطلاع دارد. این امکان فراهم میکند که از redirect()->back() برای هدایت کاربر به صفحهای که قبلاً آمده استفاده کنید. همچنین یک میانبر جهانی برای این کار وجود دارد: back().
دیگر متدهای ریدایرکت
redirect()->with()
در حالی که ساختار آن مشابه سایر متدهایی است که میتوانید روی redirect() فراخوانی کنید، with() متفاوت است زیرا جایی که شما ریدایرکت میکنید را تعریف نمیکند، بلکه دادههایی که همراه با ریدایرکت ارسال میکنید را مشخص میکند. هنگامی که کاربران را به صفحات مختلف هدایت میکنید، معمولاً میخواهید دادههای خاصی را همراه با آنها ارسال کنید. شما میتوانید دادهها را به صورت دستی به جلسه ارسال کنید، اما لاراول برخی از متدهای راحتی برای کمک به شما دارد.
معمولاً میتوانید یک آرایه از کلیدها و مقادیر یا یک کلید و مقدار تک ارسال کنید، همانطور که در مثال 3-42 نشان داده شده است. این دادهها را به جلسه ذخیره میکند تا فقط در بارگذاری صفحه بعدی قابل استفاده باشند.
مثال 3-42. ریدایرکت با دادهها
Route::get('redirect-with-key-value', function () {
return redirect('dashboard')
->with('error', true);
});
Route::get('redirect-with-array', function () {
return redirect('dashboard')
->with(['error' => true, 'message' => 'Whoops!']);
});
زنجیرهکردن متدها در ریدایرکتها مانند بسیاری از فسادهای دیگر، بیشتر فراخوانیها به فساد Redirect میتوانند زنجیرهای از متدها را بهصورت روان (fluent) بپذیرند، مانند فراخوانیهای with() در مثال 3-42. در بخش “What Is a Fluent Interface?” بیشتر با مفهوم fluency آشنا خواهید شد. |
شما همچنین میتوانید از withInput() استفاده کنید، مانند مثال 3-43، تا ورودی فرم کاربر را که به صورت فلش شده است، با هدایت بازگردانید؛ این بیشتر در موارد خطای اعتبارسنجی رایج است که میخواهید کاربر را به فرمی که قبلاً از آن آمده است بازگردانید.
مثال 3-43. هدایت با ورودی فرم
Route::get('form', function () {
return view('form');
});
Route::post('form', function () {
return redirect('form')
->withInput()
->with(['error' => true, 'message' => 'Whoops!']);
});
راحتترین روش برای دریافت ورودی فلش شدهای که با withInput() ارسال شده است، استفاده از کمکتابع old() است که میتواند برای دریافت تمام ورودیهای قدیمی (با old()) یا فقط مقدار برای یک کلید خاص استفاده شود، همانطور که در مثال زیر نشان داده شده است، با پارامتر دوم به عنوان پیشفرض اگر مقداری برای ورودی قدیمی وجود نداشته باشد. معمولاً این را در نمایها خواهید دید، که این HTML را میتوان هم در نمای "ایجاد" و هم در نمای "ویرایش" برای این فرم استفاده کرد:
<input name="username" value="<?=
old('username', 'Default username instructions here');?>">
در مورد اعتبارسنجی صحبت میکنیم، همچنین یک روش مفید برای ارسال خطاها همراه با پاسخ هدایت شده وجود دارد: withErrors() . شما میتوانید هر "ارائهدهنده" خطا را به آن ارسال کنید، که ممکن است یک رشته خطا، آرایهای از خطاها، یا معمولاً یک نمونه از Illuminate\Validator باشد که در فصل 10 پوشش داده خواهد شد. مثال 3-44 استفاده از آن را نشان میدهد.
مثال 3-44. هدایت با خطاها
Route::post('form', function (Illuminate\Http\Request $request) {
$validator = Validator::make($request->all(), $this->validationRules);
if ($validator->fails()) {
return back()
->withErrors($validator)
->withInput();
}
});
withErrors() به طور خودکار یک متغیر $errors را با نمایهای صفحهای که به آن هدایت میشود، به اشتراک میگذارد تا بتوانید آن را به هر شکلی که بخواهید مدیریت کنید.
متد validate() در درخواستها ظاهر مثال 3-44 را دوست ندارید؟ یک ابزار ساده و قدرتمند وجود دارد که به شما کمک میکند آن کد را تمیزتر کنید. در بخش “validate() on the Request Object” بیشتر بخوانید. |
متوقف کردن درخواست
پاسخ های سفارشی
چند گزینه دیگر برای بازگشت پاسخها وجود دارد، بنابراین بیایید رایجترین پاسخها پس از نمایهها، هدایتها و متوقف کردنها را مرور کنیم. همانطور که با هدایتها دیدیم، شما میتوانید این متدها را روی هر دو response() کمککننده یا Response facade اجرا کنید.
response()->make()
response()->json() و ->jsonp()
برای ایجاد یک پاسخ HTTP کدگذاری شده به صورت JSON به طور دستی، محتوای JSON-able خود (آرایهها، مجموعهها، یا هر چیز دیگری) را به متد json() ارسال کنید. برای مثال: return response()->json(User::all()). این متد شبیه به make() است، با این تفاوت که محتوای شما را به فرمت JSON کدگذاری میکند و هدرهای مناسب را تنظیم میکند.
response()->download(), ->streamDownload(), و ->file()
تست
خلاصه
مسیرهای لاراول در فایلهای routes/web.php و routes/api.php تعریف میشوند. شما میتوانید مسیر مورد انتظار هر مسیر را تعریف کنید، بخشهایی که ثابت هستند و بخشهایی که پارامتر هستند، کدام متدهای HTTP میتوانند به مسیر دسترسی داشته باشند و چگونه آن را حل کنید. همچنین میتوانید میانافزارها را به مسیرها متصل کرده، آنها را گروهبندی کرده و به آنها نام بدهید.
آنچه از مسیر یا متد کنترلر برمیگردد، نحوه پاسخدهی لاراول به کاربر را تعیین میکند. اگر یک رشته یا یک نمای باشد، به کاربر ارائه میشود؛ اگر دادههای دیگری باشد، به JSON تبدیل شده و به کاربر نمایش داده میشود؛ و اگر یک ریدایرکت باشد، باعث ریدایرکت میشود.
لاراول مجموعهای از ابزارها و امکانات را برای سادهسازی وظایف و ساختارهای رایج مربوط به مسیرها فراهم میکند. اینها شامل کنترلرهای منبع، اتصال مدل به مسیر و جعل متد فرم هستند.