رسول اسماعیلی - برنامه نویس
لاراول 8 در 10 دقیقه
تیم لاراول در تاریخ هشتم سپتامبر امسال نسخه 8 این فریم ورک را منتشر کرد. این نسخه (LTS(Long Term Support نیست و تا تاریخ April 6th, 2021 پشتیبانی عمومی و تا تاریخ September 8th, 2021 (یک سال دیگر) پشتیبانی امنیتی می شود.
از تغییرات و قابلیت های جدید این نسخه میشه از تغییر پوشه مدل ها، بهبود حالت نگهداری (Improved Maintenance Mode) و تغییر Factory ها به کلاس نام برد.
لیست تغییرات این نسخه:
- Models Directory
- Model Factory Classes
- Migration Squashing
- Job Batching
- Improved Rate Limiting
- Improved Maintenance Mode
- Closure Dispatch / Chain catch
- Dynamic Blade Components
- Event Listener Improvements
- Time Testing Helpers
- Artisan serve Improvements
- Tailwind Pagination Views
- Routing Namespace Updates
در ادامه سعی میکنم به صورت خلاصه در مورد هر کدوم از این تغییرات بنویسم و توضیح بدم.
Models Directory
از همون اول هم که model ها در پوشه قرار نداشتن و داخل پوشه app بودن، حس بهم ریختگی و شلوغی بعد از بزرگ شدن پروژه همه رو کلافه می کرد. حالا بالاخره در این نسخه با توجه به درخواست های زیاد، پوشه ی model در داخل پوشه app برای مدل ها در نظر گرفته شده است و از این نسخه به بعد باید برای پیدا کردن مدل ها به app/model مراجعه کنیم.
Model Factory Classes
در واقع factory ها در تست نویسی کاربرد دارند، آنجایی که نیاز به اطلاعاتی در دیتابیس داشته باشیم تا بتونیم شرایط مناسب برای اجرای تست رو فراهم کنیم.
پیش از این factory ها به صورت کلاس نبودند و باید با متغیری که نمونه ای از \Illuminate\Database\Eloquent\Factory بود کار میکردیم و factory خودمون رو تعریف می کردیم.
حالا در این نسخه تبدیل به کلاسی جداگانه شده اند، در واقع باید برای تعریف factory، یک کلاس جدید ایجاد کنیم که البته از Command های artisan برای این کار استفاده میکنیم تا این کلاس رو برای ما ایجاد کند ولی به هر ترتیب قالب نوشتاری factory در نسخه هشتم لاراول به صورت کلاس تغییر پیدا کرده است و با این تغییر قابلیت های جدیدی نیز به factory اضافه شده است.
بر اساس داکیومنت لاراول روابط بین مدل ها نیز در factory ها لحاظ شده است و بدین ترتیب میشه factory مدل های دیگه که با مدل مد نظر ما ارتباط دارند رو نیز صدا کرد تا اطلاعات مورد نیاز ما برای اون مدل هم در دیتابیس نوشته بشه.
برای مثال فرض کنید که مدل user ما با مدل post ارتباط دارد و می خواهیم تعدادی user به همراه post هاشون در دیتابیس ایجاد کنیم: (با فرض اینکه factory های لازم رو از قبل نوشته باشیم.)
$users = User::factory()
->hasPosts(3, ['published' => false])
->create();
به همین راحتی !!!
همچنین این امکان وجود دره که ما متد هایی به کلاس Factory خودمون اضافه کنیم و هنگام استفاده از factory ها اون هارو صدا کنیم.
Migration Squashing
طی زمانی که پروژه بزرگ و بزرگتر میشه ما هم به مرور migration های زیادی می نویسیم که تعدادشون خیلی زیاد میشه در برخی موارد به صد ها migration هم میرسه و این موضوع باعث شلوغ شدن پوشه migration ها میشه و مشکل دیگه که به وجود میاد اینکه برای مثال فرض کنید که در ماه سوم تصمیم میگیریم که ستونی به پروژه اضافه کنیم و migration مورد نیاز رو بنویسیم و در ماه پنجم تصمیم به تغییر یا حذف اون ستون میگیریم و migration مورد نیاز اون رو هم می نویسیم حالا فرض کنید که عضو جدیدی به تیمتون اضافه میشه و بعد از pull کردن پروژه migration هارو اجرا میکنه خب چی میشه؟ با توجه به migration هایی که در مثال فرض کردیم به ترتیب migration ماه سوم و بعد ماه پنجم اجرا میشه و در واقع یک عملی انجام می شود و در ادامه یا تغییر میکند یا اون عمل به طور کامل پاک میشود در صورتی که ما فقط به آخرین شکل دیتابیس نیاز داریم و طی این مراحل داریم یک عمل اضافه ای رو انجام میدیم. برای درک عمق بد بودن ماجرا تصور کنید که صد ها migration داریم و تعداد این عمل های اضافی بسیار زیاد است و همچنین فرض کنید که برای هر بار اجرای تست های پروژه که دیتابیس خود را در حافظه نگه می دارند این اتفاق می افتد.
شاید فکر کنید مشکل کار از migration ها باشه ولی در حقیقت این ذات اون هاست که دقیقا تغییرات جز به جز دیتابیس پروژه رو ذخیره کنند و این یکی از مزیت های اونهاست.
برای بهبود ماجرا آقای Taylor Otwell عزیز زحمت کشیدن و migration squashing رو طراحی کردند. کاری که این ابزار انجام میده اینکه ما میتونیم کامند php artisan schema:dump را اجرا کنیم تا یک فایل در پوشه database/schema ساخته بشه و حاوی کد های sql ای باشه که ساختار فعلی دیتابیس پروژه را ایجاد میکنه، حالا هر بار قبل از اجرا شدن migration ها، این فایل اجرا میشه و بعد از اون تنها migration هایی اجرا میشن که در این فایل لحاظ نشده باشن.
Job Batching
این قابلیت جدید به برنامه نویس اجازه میده تعداد زیادی از job هارو اجرا کنه و بعد از اون با توجه به نتیجه تابع call back مناسب با اون نتیجه رو بنویسه، برای اینکه بهتر متوجه بشین اول یک مثال میزنم:
$batch = Bus::batch([
new ProcessPodcast(Podcast::find(1)),
new ProcessPodcast(Podcast::find(2)),
new ProcessPodcast(Podcast::find(3)),
new ProcessPodcast(Podcast::find(4)),
new ProcessPodcast(Podcast::find(5)),
])->then(function (Batch $batch) {
// All jobs completed successfully...
})->catch(function (Batch $batch, Throwable $e) {
// First batch job failure detected...
})->finally(function (Batch $batch) {
// The batch has finished executing...
})->dispatch();
خب در مثال بالا دسته ای از job ها برای اجرا نوشته شده و بعد از اون هم توابعی برای حالت های مختلف، در ادامه این توابع رو بررسی میکنیم.
تابع then: میتونیم بعد اجرا شدن موفق job ها دستوراتی رو اجرا کنیم.
تابع catch: میتونیم بعد از رخ دادن خطا دستوراتی رو اجرا کنیم.
تابع finally: میتونیم فارغ از هر نتیجه ای دستوراتی را اجرا کنیم.
Improved Rate Limiting
این امکان در لاراول وجود داشت که route ها رو محدود کنیم تا در مدت زمان مشخصی یک نفر فقط بتواند تعداد مشخصی request ارسال کند، حالا در این نسخه این قابلیت هم بهبود یافته هم ارتقاء پیدا کرده است.
با استفاده از facade به نام RateLimiter و تابع for می تونیم این محدودیت رو تعریف کنیم. تابع for دوتا ورودی داره که اولیش نام و دومی یک تابع callback است و از اونجایی که در این تابع callback دسترسی به request داریم میتونیم با توجه به اطلاعات اون درخواست، مثلا وضعیت authenticate ارسال کننده درخواست و غیره شرایط محدودیت رو به صورت داینامیک و پویا تعیین کنیم.
برای مثال میخواهیم کاربران VIP هیچ محدودیتی نداشته باشند و بقیه کاربران فقط بتونن تو هر دقیقه 100 درخواست ارسال کنند:
RateLimiter::for('uploads', function (Request $request) {
return $request->user()->vipCustomer()
? Limit::none()
: Limit::perMinute(100)->by($request->ip());
});
حالا برای استفاده از Rate Limit هایی که مینویسیم، کافیه اسم اون هارو به throttle در هنگام تعریف یک route نسبت دهیم:
Route::middleware(['throttle:uploads'])->group(function () {
Route::post('/audio', function () {
//
});
});
Improved Maintenance Mode
در نسخه قبلی لاراول وقتی که کامند php artisan down را اجرا می کردیم این امکان وجود داشت که یک لیست از IP ها رو مشخص کنیم تا در تمام مدتی که کل اپلیکیشن پایین است و از دسترس تمام کاربران خارجه این IP ها به تمام اپلیکیشن دسترسی داشته باشند.
حالا در این نسخه این قابلیت حذف شده است و جای خودش رو به Secret token داده است:
php artisan down --secret="1630542a-246b-4b66-afa1-dd72a4c43515"
هنگامی که اپلیکیشن پایین است به route مخفی زیر یک Request ارسال می کنیم و یک cookie برای ما set میشه و بعد از اون میتونیم به کل اپلیکیشن دسترسی داشته باشیم.
https://example.com/1630542a-246b-4b66-afa1-dd72a4c43515
Pre-Rendering The Maintenance Mode View
قابلیت دیگه ای هست که به این بخش اضافه شده است و باعث میشه قبل از پایین رفتن اپلیکیشن View مورد نیاز Maintenance Mode رندر بشه و آماده باشه. این قابلیت به این دلیل اضافه شده، چون قبلا وقتی در حال update کردن قسمت هایی از پروژه بودیم لاراول برای render کرد view مورد نیاز ممکن بود با مشکل مواجه بشه و کاربران ارور دریافت کنند حالا با این قابلیت view از قبل آماده است و این امکان به حداقل میرسه.
Closure Dispatch / Chain catch
در این نسخه میتونیم برای job هایی که به صورت closuer هستند یک تابع catch بنویسیم تا اگر بعد از تمام تلاش های مجدد باز هم انجام نشد این تابع اجرا بشه:
dispatch(function () use ($podcast) {
$podcast->publish();
})->catch(function (Throwable $e) {
// This job has failed...
});
Dynamic Blade Components
در برخی مواقع هست که تا زمان اجرای کد نرسه، نمیدونیم کدوم component باید render بشه، در این مواقع میشه از این قابلیت جدید استفاده کرد، کافیه اسم component مورد نظر که موقع اجرای کد مشخص شده است را به dynamic-component نسبت دهیم.
<x-dynamic-component :component="$componentName" class="mt-4" />
Event Listener Improvements
برای این بخش امیدوارم با closure event listener ها آشنا باشید چون توضیحش از حوصله این مقاله خارجه، شاید بعدا یک مطلب جداگانه در موردشون نوشتم و توضیحشون دادم.
به هر حال در این نسخه closure event listener ها بهبود یافته اند و هنگام تعریفشون فقط کافیه یک تابع closure بنویسیم و نوع event ورودی رو مشخص کنیم:
Event::listen(function (PodcastProcessed $event) {
//
});
برای queue کردن هم کافیه از تابع Illuminate\Events\queueable استفاده کنیم:
Event::listen(queueable(function (PodcastProcessed $event) {
//
}));
و در آخر هم باید بگم که مانند بقیه job ها متد های onConnection, onQueue , delay و catch اضافه شده اند:
Event::listen(queueable(function (PodcastProcessed $event) {
//
})->onConnection('redis')->onQueue('podcasts')->delay(now()->addSeconds(10)));
Event::listen(queueable(function (PodcastProcessed $event) {
//
})->catch(function (PodcastProcessed $event, Throwable $e) {
// The queued listener failed...
}));
Time Testing Helpers
در تست نویسی خیلی از اوقات نیازه که ما بتونیم زمان فعلی رو تغییر بدیم و به اصطلاح در زمان سفر کنیم تا بتونیم سیستم رو در شرایط مورد نظر قرار بدیم و تست کنیم حالا در این نسخه لاراول این امکان رو برای ما به سادگی فراهم کرده:
public function testTimeCanBeManipulated(){
// Travel into the future...
$this->travel(5)->milliseconds();
$this->travel(5)->seconds();
$this->travel(5)->minutes();
$this->travel(5)->hours();
$this->travel(5)->days();
$this->travel(5)->weeks();
$this->travel(5)->years();
// Travel into the past...
$this->travel(-5)->hours();
// Travel to an explicit time...
$this->travelTo(now()->subHours(6));
// Return back to the present time...
$this->travelBack();
}
Artisan serve Improvements
با دستور php artisan serve که هممون آشنایی داریم، این دستور هم در این نسخه بهبود یافته و دیگر نیاز نیست وقتی متغیری رو در env. تغییر میدیم به صورت دستی دستور serve رو دوباره راه اندازی کنیم و به صورت خودکار لاراول متوجه تغییرات این فایل میشه.
Tailwind Pagination Views
لاراول تصمیم گرفته به صورت پیشفرض از فریم ورک Tailwind برای pagination در view ها استفاده کنه، Tailwind یکی از بهترین فریم ورک های css هست که واقعا استایل دادن رو نسبتا آسون تر میکنه. با این حال هنوز هم Bootstrap نسخه 3 و 4 نیز در دسترس هست.
Routing Namespace Updates
در نسخه های قبلی وقتی میخواستیم یک route تعریف کنیم به این شکل تعریف میکردیم:
Route::get('/user', 'UserController@index');
و فقط اسم controller رو مینوشتیم وخود لاراول namespace لازم برای رسیدن به اون controller رو به ابتدای نام controller اضافه می کرد اما حالا تو این نسخه باید آدرس کامل controller رو ذکر کنیم:
Route::get('/user', [UserController::class, 'index']);
و اگر میخواهیم مثل قبل فقط اسم controller رو بنویسیم باید بریم داخل RouteServiceProvider و property $namespace رو مقداردهی کنیم.
ممنون ازتوجه تون، یک خسته نباشید به شما میگم ویکی هم به خودم برای این مطلب نسبتا طولانی. شادُ خندونُ سلامت باشید. (:
مطلبی دیگر از این انتشارات
حالا چرا لاراول؟ شاهدی به نام صفحهبندی
مطلبی دیگر از این انتشارات
سرویس کانتینر و سرویس پرووایدر در لاراول
مطلبی دیگر از این انتشارات
مقایسه Laravel vs Django... از دید یه لاراول کار که تازه رفته جنگو! بخش اول