آشنایی با قابلیت‌های جدید لاراول ۸

?What's New in Laravel 8

23 آبان 1399
whats-new-in-laravel-8

لاراول 8 به تازگی منتشر شده است و از آنجایی که این نسخه جدید دارای قابلیت های جدیدی نسبت به نسخه های قبلی است، بهتر است دست به کار شده و با امکانات جدید لاراول 8 آشنا شویم.

در قدم اول باید لاراول را روی سیستم خود نصب کنیم:

composer global require laravel/installer

نکته: installer لاراول به روز رسانی شده است بنابراین حتی اگر لاراول را از قبل نصب کرده اید باید آن را دوباره به روز رسانی کنید. برای بررسی نسخه installer لاراول، دستور laravel -v را در ترمینال خود اجرا کنید. نسخه برگرانده شده باید بالای ۴ باشد (در زمان نگارش این مقاله، نسخه ۴.۰.۰ است). در صورتی که دستور laravel -v برای سیستم شما ناشناخته است باید آن را به PATH خود اضافه کنید:

برای ویندوز مسیر زیر را اضافه کنید:

%USERPROFILE%\AppData\Roaming\Composer\vendor\bin

برای Mac OS مسیر زیر را اضافه کنید:

$HOME/.composer/vendor/bin

برای لینوکس (بر اساس distro خودتان) یکی از مسیر های زیر را اضافه کنید:

$HOME/.config/composer/vendor/bin
$HOME/.composer/vendor/bin

پس از نصب، باید دستور زیر را اجرا کنید تا یک پروژه جدید داشته باشیم:

laravel new project

با اجرای این دستور چند دقیقه صبر می کنید تا پکیج شما به طور کامل نصب شود، سپس cd project را اجرا می کنید تا وارد پوشه اصلی پروژه شوید. در اینجا می توانید با دستور php artisan -v ببینید که نسخه نصب شده لاراول برای شما چند است (در زمان نگارش این مقاله 8.۱).

اضافه شدن پوشه Models

اگر یادتان باشد در نسخه 7 لاراول، Model های شما به صورت مستقیم در پوشه app قرار داشتند و namespace پیش فرض آن ها App بود. در نسخه 8 لاراول یک پوشه جدید به نام Models نیز اضافه شده است و از این به بعد میزبان Model های شما خواهد بود. طبیعتا namespace پیش فرض نیز به App/Models تغییر کرده است:

namespace App\Models;

از این به بعد زمانی که دستور php artisan make:model myModel را اجرا می کنید، مدل myModel در پوشه App/Models حضور خواهد داشت. در صورتی که از این تغییر راضی نیستید به راحتی می توانید آن را به حالت قبلی برگردانید. برای این کار ابتدا به پوشه models رفته و هر مدلی که دارید را از آن خارج کنید و مثل قبل مستقیما در پوشه App قرار بدهید. با این کار namespace در ابتدای فایل بهم می ریزد و برای تصحیح آن باید قسمت models را از آن حذف کنید تا به شکل زیر در بیاید:

namespace App;

در مرحله بعدی پوشه models را به طور کامل حذف کنید. از این به بعد اگر از دستور php artisan make:model myModel استفاده کنید، مدل شما مستقیما در پوشه App قرار خواهد گرفت چرا که لاراول می بیند هیچ پوشه ای به نام models وجود ندارد بنابراین مدل های بعدی را مستقیما در App می گذارد.

ادغام migrationها در یک فایل schema

همانطور که می دانید migration ها یکی از بهترین قابلیت های لاراول هستند که به ما کمک بسیار زیادی می کنند. ما برای ارسال migration ها به سرور از دستور php artisan migrate استفاده می کنیم. تمام migration های شما در پوشه database/migrations قرار دارند و در ابتدا فقط ۳ فایل هستند که مشکلی نیست اما زمانی که یک پروژه را در طی چند ماه یا چند سال مدیریت می کنید، تعداد این migration ها بسیار زیاد می شود. شاید بگویید مشکل کجاست؟ مشکل اصلی ما این است که با هر دستور migrate تمام migration ها باید اجرا شوند بنابراین اگر صد فایل migration داشته و در حال توسعه سایت باشیم، فقط یک دستور ساده برای refresh کردن پایگاه داده باعث می شود که چندین دقیقه صبر کنید تا صد migration کامل اجرا شوند. همانطور که می دانید ما در فرآیند توسعه شاید در هر ساعت ۱۰ ها بار دستورات مربوط به migration را اجرا کنیم بنابراین این فرآیند به شدت آزاردهنده خواهد شد.

به عنوان یکی از امکانات جدید لاراول 8 ، لاراول 8 برای حل این مشکل دستور جدیدی به نام schema:dump دارد که به ما اجازه می دهد کل ساختار پایگاه داده خود را در یک فایل schema قرار بدهیم، بنابراین تمام فایل های migration ما در یک فایل shema ادغام می شوند. برای تست این دستور با هم می گوییم:

php artisan schema:dump

برای اجرای این دستور باید پایگاه داده خود را داشته باشید، در غیر این صورت به خطا برخورد می کنید. با اجرای این دستور پیام Database Schema Dumped Successfully را مشاهده خواهید کرد. حالا اگر به پوشه database در پروژه خود بروید، یک پوشه جدید به نام schema پیدا خواهید کرد. حتما می دانید که فایل های schema پسوند sql دارند و می توایند آن ها را از محیط های مختلف مانند PHPMyAdmin روی یک سرور MySQL اجرا کنید و کل ساختار را عینا پیاده کنید، بنابراین عملا یک کپی از ساختار پایگاه داده خود را گرفته اید. در ضمن در صورتی که می خواستید فایل های migration را حذف کنید می توانید پارامتر prune را به این دستور بدهید:

php artisan schema:dump --prune

با اجرای این دستور، ابتدا فایل schema را گرفته و سپس تمام migration های شما حذف خواهند شد. آیا این کار باعث از دست رفتن دستورات php artisan migrate نمی شود؟ خیر! اگر پوشه migrations به صورت کامل حذف شده باشد، دستور migrate سعی در خواندن فایل schema می کند بنابراین حتی بعد از اجرای دستور بالا می توانیم دستوری مثل php artisan migrate:fresh را اجرا کنیم. از اینجا به بعد اگر بخواهید فایل migration جدیدی بسازید، کاملا آزاد هستید. در واقع ما php artisan migrate را اجرا می کنیم و فایل schema روی سرور پایگاه داده اجرا می شود و پایگاه داده ما را می سازد. در مرحله بعدی می توانیم دستور php artisan make:migration create_posts_table را نیز اجرا کنیم تا یک migration جدید داشته باشیم! این migration پایگاه داده ما را خراب نمی کند بلکه آن را ادامه خواهد داد. مثلا اگر از این به بعد دستور php artisan migrate:fresh را اجرا کنیم، ابتدا فایل schema اجرا شده و سپس migration های ما اجرا می شود.

نکته: این ویژگی برای پروژه هایی است که چندین سال در حال نگهداری یا توسعه هستند و تعداد migration های بسیار زیادی دارند بنابراین اگر شما یک پروژه دارید که تعداد migration های آن زیاد نیست (مثلا از ۲۰ عدد بیشتر نیست) نیازی نیست از این دستور استفاده کنید بلکه همان migration ها برای شما کافی و ساده تر هستند.

model factoryها در قالب کلاس!

از دیگر امکانات جدید لاراول 8 مسئله model factory ها است که از این به بعد در قالب کلاس نوشته شده اند. اگر به پوشه database/factories خود نگاهی بیندازید، می بینید که اطلاعات دیگر در قالب تابع نوشته نشده اند بلکه یک کلاس است!

<?php

namespace Database\Factories;

use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;

class UserFactory extends Factory
{
    /**
     * The name of the factory's corresponding model.
     *
     * @var string
     */
    protected $model = User::class;

    /**
     * Define the model's default state.
     *
     * @return array
     */
    public function definition()
    {
        return [
            'name' => $this->faker->name,
            'email' => $this->faker->unique()->safeEmail,
            'email_verified_at' => now(),
            'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
            'remember_token' => Str::random(10),
        ];
    }
}

نحوه کار این factory کمی متفاوت است بنابراین باید آن را در این جلسه توضیح بدهم. در ابتدا یک خصوصیت protected به نام model$ داریم:

protected $model = User::class;

در اینجا شما باید مدل مورد نظر خود را پاس بدهید، یعنی مدلی که این factory برایش داده تولید خواهد کرد. در مرحله بعدی نیز متدی به نام definition را داریم که مشخص می کند این factory به چه شکل عمل خواهد کرد. از آنجایی که این قابلیت در قالب یک کلاس نوشته شده است می توانیم استفاده های بیشتری از آن داشته باشیم. مثلا در لاراول 7 برای ساخت یک کاربر باید php artisan tinker را اجرا می کردیم و سپس دستور زیر را می نوشتیم:

factory('App\User')->make();

این دستور یک کاربر را برای ما می ساخت اما در لاراول 8 اگر بخواهم دستور بالا یا حتی factory را create کنم، خطا می گیرم:

factory('App\User')->create();

چرا؟ به دلیل اینکه دیگر تابعی به نام factory وجود ندارد. در حال حاضر اگر به مدل User (فایل User.php) نگاه کنید، یک trait جدید به نام HasFactory می بینید:

class User extends Authenticatable
{
    use HasFactory, Notifiable;
// بقیه کدها //

این trait جدید به ما اجازه می دهد که factory را مستقیما از روی مدل User صدا بزنیم بنابراین در tinker به چنین شکلی عمل خواهیم کرد:

App\Models\User::factory()->make();

یکی دیگر از مزایای کلاس بودن factory ها این است که می توانیم به آن ها برویم (UserFactory.php) و وضعیت کاربر در definition را با یک متد جدید تغییر بدهیم. مثلا برای ادمین بودن یا نبودن کاربر بدین شکل عمل می کنیم:

public function definition()
{
    return [
        'name' => $this->faker->name,
        'email' => $this->faker->unique()->safeEmail,
        'email_verified_at' => now(),
        'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
        'remember_token' => Str::random(10),
    ];
}

public function admin()
{
    return $this->state([
        'admin' => true
    ]);
}

با این کار قابلیت ادمین کردن یک کاربر را داریم. البته از آنجایی که هنوز خصوصیت admin را در پایگاه داده نداریم می توانیم به migration ساخت کاربران رفته و این ستون را اضافه می کنم:

public function up()
{
    Schema::create('users', function (Blueprint $table) {
        $table->id();
        $table->string('name');
        $table->string('email')->unique();
        $table->timestamp('email_verified_at')->nullable();
        $table->string('password');
        $table->boolean('admin')->default(false);
        $table->rememberToken();
        $table->timestamps();
    });
}

همانطور که می بینید من گفته ام که این ستون از جنس boolean باشد و به صورت پیش فرض روی false قرار بگیرد. حالا دستور php artisan migrate:fresh را اجرا می کنیم تا پایگاه داده به روز رسانی شود. حالا در factory خودمان وضعیت عادی کاربران را نیز روی false می گذاریم. توجه کنید که تا اینجا فقط وضعیت پایگاه داده را روی false گذاشته بودیم و برای factory ها بهتر است به صورت صریح وضعیت را مشخص کنیم تا به مشکل نخوریم:

public function definition()
{
    return [
        'name' => $this->faker->name,
        'email' => $this->faker->unique()->safeEmail,
        'email_verified_at' => now(),
        'admin' => false,
        'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
        'remember_token' => Str::random(10),
    ];
}

حالا متد admin می تواند این خصوصیت admin را روی true قرار بدهد. برای تست آن دستور php artisan tinker را اجرا می کنیم تا وارد tinker شویم. حالا مدل کاربر را وارد می کنیم تا مجبور نباشم هر دفعه namespace کامل App\Models\User را بنویسم:

use App\Models\User;

سپس یک کاربر عادی می سازیم:

User::factory()->make();

خصوصیت admin برای این کاربر false است بنابراین تا اینجا مشکلی نداریم.

نکته: زمانی که کدهایتان را تغییر می دهید باید یک بار از tinker خارج شده و دوباره وارد آن شوید، در غیر این صورت تغییرات شما لحاظ نخواهند شد.

حالا یک کاربر ادمین می سازیم:

User::factory()->admin()->make();

حالا به کاربر ساخته شده نگاه کنید. فیلد admin در آن برابر true است! همانطور که می بینید کلاس بودن factory ها کمک خوبی به ما می کند. به یاد داشته باشید که شما می توانید هر تعداد از این متد ها را تعریف کرده و استفاده کنید. به طور مثال فرض کنید مفهومی به نام subscribe کردن یا فالو کردن یک صفحه داشته باشیم و بخواهیم در تست های خود چند کاربر را از این منظر تغییر بدهیم. برای این کار ابتدا باید ستونی در پایگاه داده داشته باشیم بنابراین به create_user_table (فایل migration) می رویم و این ستون را اضافه می کنیم:

public function up()
{
    Schema::create('users', function (Blueprint $table) {
        $table->id();
        $table->string('name');
        $table->string('email')->unique();
        $table->timestamp('email_verified_at')->nullable();
        $table->string('password');
        $table->boolean('is_active')->default(false);
        $table->boolean('admin')->default(false);
        $table->rememberToken();
        $table->timestamps();
    });
}

ستون is_active به صورت پیش فرض روی false خواهد بود. حالا به فایل factory خود رفته و متد جدیدی برای تغییر وضعیت آن تعریف می کنیم:

public function definition()
{
    return [
        'name' => $this->faker->name,
        'email' => $this->faker->unique()->safeEmail,
        'email_verified_at' => now(),
        'is_active' => false,
        'admin' => false,
        'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
        'remember_token' => Str::random(10),
    ];
}

public function admin()
{
    return $this->state([
        'admin' => true
    ]);
}

public function subscribed()
{
    return $this->state([
        'is_active' => true
    ]);
}

تمام فایل ها را ذخیره کرده و با دستور php artisan tinker وارد محیط tinker شوید. مثل دفعه قبل ابتدا مدل User را وارد می کنیم و در نهایت می توانیم هر دو متد بالا را به make زنجیر کنیم:

use App\Models\User;
User::factory()->admin()->subscribed()->make();

با اجرای این دستور کاربری را می سازیم که علاوه بر ادمین بودن، subscribed نیز می باشد؛ یعنی فیلد های is_active و admin در آن true هستند. بنابراین می توانید به هر تعدادی که خواستید از این متد ها استفاده کنید.

relationship یا روابط بین داده ای

مسئله بعدی در مورد این factory ها مسئله relationship یا روابط بین داده ای است. سوال اینجاست که با کلاس محور شدن factory ها، چطور می توانیم relationship ها را کنترل کنیم؟ برای این کار ابتدا یک مدل به نام post می سازیم که دارای migration و factory نیز باشد:

php artisan make:model Post -mf

در قدم اول به فایل migration آن بروید (نامی شبیه به create_posts_table) و مثل من ستون های زیر را پیاده سازی کنید:

public function up()
{
    Schema::create('posts', function (Blueprint $table) {
        $table->id();
        $table->foreignId('user_id');
        $table->string('title');
        $table->timestamps();
    });
}

در مرحله بعدی به PostFactory.php در مسیر database/factories می رویم و برایش یک factory ساده تعریف می کنیم. حتما می دانیم که نوشتن بدنه اصلی factory در متد definition انجام می شود:

public function definition()
{
    return [
        'user_id' => User::factory(),
        'title' => $this->faker->sentence
    ];
}

برای title که می توانیم از یک sentence (جمله) ساده استفاده کنیم اما برای رابطه user_id چطور؟ این factory برای پست ها است و هر پست متعلق به یک کاربر User است بنابراین یک foreignId نیاز است که آن را در فایل migration تعریف کردیم. سوال اصلی اینجاست که چطور باید این رابطه را در factory نشان بدهیم؟ پاسخ کاری است که من بالاتر انجام داده ام! باید کلاس User را آورده و یک factory به آن بدهید. یادتان نرود که namespace مربوط به User را در بالای این فایل وارد کنید. با انجام این کار لاراول به صورت خودکار می داند که باید بین پست و کاربر رابطه ای را برقرار کند. حالا از ترمینال دستور php artisan migrate را اجرا کنید تا migration های جدید روی سرور پایگاه داده اعمال شوند.

حالا فرض کنید در یکی از تست هایمان می خواهیم بگوییم یک کاربر خاص ۵ پست داشته باشد. چطور این کار را با factory جدید انجام بدهیم؟ ابتدا با php artisan tinker وارد محیط tinker شوید، سپس مدل های User و Post را وارد کنید:

use App\Models\{User,Post};

سپس می توانیم با استفاده از ترفند زیر این کار را انجام بدهیم:

User::factory()->has(Post::factory()->count(5))->create();

ما انتظار داریم که متد has به معنی «داشتن» مشخص کند که کاربر ۵ پست دارد. ما به این متد یک factory را پاس داده ایم که ۵ پست بسازد بنابراین کد بالا باید بدون خطا کار کند درست است؟ خیر! این کد به خطا برمی خورد و می گوید متدی به نام User::Post وجود ندارد. آیا متوجه شدید؟ لاراول فرض می کند که شما relationship های این دو جدول را خودتان تعریف کرده اید به همین دلیل به دنبال متدی به نام post در مدل User می گردد! بنابراین راه حل این است که به User.php برویم و متد مربوطه را اضافه کنیم:

// بقیه کدها //
protected $casts = [
    'email_verified_at' => 'datetime',
];

public function posts()
{
    return $this->hasMany(Post::class);
}

استفاده از hasMany اختصاصی به لاراول 8 ندارد و باید با آن آشنا باشید. هر کاربر چندین پست دارد بنابراین باید از hasMany استفاده کنیم. حالا اگر دوباره به tinker وارد شده و کدهای زیر را وارد کنید یک کاربر برایتان ساخته می شود که ۵ پست دارد:

use App\Models\{User,Post};

User::factory()->has(Post::factory()->count(5))->create();

بعد از اجرای این دستورات می توانید پایگاه داده خود را بررسی کنید. یک کاربر جدید خواهید داشت. همچنین دستور بالا قابلیت خلاصه شدن را نیز دارد. ما می توانیم به جای این دستور طولانی از روش زیر استفاده کنیم:

use App\Models\{User,Post};

User::factory()->hasPosts(5)->create();

لاراول می تواند خودش رابطه را تشخیص بدهد. حالا از شما سوالی دارم؛ اگر بخواهیم برعکس این حالت را انجام بدهیم چطور؟ یعنی اگر بخواهیم برای یک کاربر ۵ پست بسازیم نه اینکه کاربری بسازیم که ۵ پست داشته باشد! در این حالت باید از ()for استفاده کنیم که دقیقا برعکس ()has می باشد، یعنی بگوییم:

use App\Models\{User,Post};

Post::factory()->for(User::factory())->count(3)->create();

من factory را ابتدا روی Post صدا زده ام و سپس از for استفاده کرده ام تا بگویم ۳ پست مورد نظر برای یک کاربر خاص هستند. البته با اجرای کد بالا باز هم با خطا مواجه می شویم چرا که رابطه برعکس را در مدل Post.php ننوشته ایم:

class Post extends Model
{
    use HasFactory;

    public function user()
    {
        $this->belongsTo(User::class);
    }
}

حالا دوباره به tinker رفته و دستور زیر را اجرا می کنیم:

use App\Models\{User,Post};
Post::factory()->for(User::factory())->count(3)->create();

این بار ۳ پست برای یک کاربر ساخته می شود، به همین راحتی!

Maintenance Mode

همانطور که می دانید در نسخه های قبلی لاراول، قابلیتی به نام maintenance mode وجود داشته است که با دستور php artisan down فعال می شود. Maintenance mode برای زمانی است که می خواهید کدهای وب سایت را تغییر بدهید و در این حالت نمی توانیم به کاربران اجازه استفاده از وب سایت را بدهیم. چرا؟ به دلیل اینکه در حال کدنویسی هستیم و بسیاری از قابلیت های وب سایت خراب است و کار نمی کند. همچنین در این حالت نفوذپذیری سایت ما بالا می رود و امکان خرابکاری بسیار بالاتر است. به همین دلیل زمانی که می خواهیم سرور را به روز رسانی کنیم از php artisan down استفاده می کنیم تا به کاربران اعلام کنیم که وب سایت فعلا در دسترس نیست. زمانی که کارمان تمام شد نیز از php artisan up استفاده می کنیم تا وب سایت به حالت عادی خود برگردد.

از دیگر امکانات جدید لاراول 8 این است که در نسخه 8 لاراول این قابلیت به میزان قابل توجهی ارتقاء پیدا کرده است! اگر دستور php artisan help down را اجرا کنید لیستی از دستورات مربوط به این قابلیت را مشاهده خواهید کرد اما بعضی از آن ها جدید هستند:

      --redirect[=REDIRECT]  The path that users should be redirected to
      --render[=RENDER]      The view that should be prerendered for display during maintenance mode
      --retry[=RETRY]        The number of seconds after which the request may be retried
      --secret[=SECRET]      The secret phrase that may be used to bypass maintenance mode
      --status[=STATUS]      The status code that should be used when returning the maintenance mode response [default: "503"]

من می خواهم تک تک این گزینه های جدید را با شما بررسی کنم. من از پارامتر secret به معنی «راز» یا «مخفی» شروع می کنم. شاید ما بخواهیم که maintenance mode برای وب سایت ما فعال باشد اما خودمان جزو مردم حساب نشویم و بتوانیم وب سایت را مشاهده کنیم. برای انجام این کار ابتدا باید یک secret key یا یک کلید مخفی تولید کنید:

php artisan down --secret=YourKeyHere

با اجرای دستور بالا وب سایت ما وارد حالت maintenance mode می شود بنابراین اگر php artisan serve در حال اجرا باشد و به http://127.0.0.1:8000 بروید، صفحه ای با خطای ۵۰۳ می گیرید که می گوید Service Unavailable (یعنی فعلا خارج از سرویس هستیم) اما اگر کلید مخفی خود را در انتهای URL وارد کنید، می توانید وب سایت را ببینید:

127.0.0.1:8000/YourKeyHere

در واقع به محض وارد شدن به آدرس بالا، وب سایت یک بار refresh شده و به صفحه اصلی خود 127.0.0.1:8000 منتقل می شوید و می توانید وب سایت را به صورت کامل مشاهده کنید. همچنین نیازی نیست که برای هر بار باز کردن یک صفحه خاص این کار را انجام بدهید. چرا؟ به دلیل اینکه با وارد شدن به URL بالا، لاراول یک کوکی خاص را در مرورگر شما ذخیره می کند و در تمام صفحات بعدی شما را با آن شناسایی می کند. این کوکی laravel_maintenance نام دارد و یک رشته رمزی به شکل زیر است که می توانید از سربرگ storage در dev tools مرورگر خود (معمولا کلید f12) آن را پیدا کنید:

eyJleHBpcmVzX2F0IjoxNjAyOTA0MDA5LCJtYWMiOiIzOWRkYzBmZWI2ODg0ZGIwZDhiNmJhZGMwMTA1MDc0YmJhODc3NGMxOWY2NWY5NjcxYmM5MzQ2ODE3ZWY3NWU4In0%3D

تا زمانی که این کوکی حذف نشده یا منقضی نشده باشد، به وب سایت خود در حالت maintenance mode دسترسی خواهید داشت. این اولین قابلیت جدید در maintenance mode است.

برخی اوقات در maintenance mode هستیم و پس از نوشتن کدهای سمت سرور دستوری مثلcomposer install یا امثال آن برای به روز رسانی وابستگی ها را اجرا می کنیم. پس از اجرای این دستور متوجه می شویم که به جای نمایش Service Unavailable، واقعا یک خطا در حال نمایش است. این حالت زمانی رخ می دهد که کدهای اشتباهی نوشته باشید و لاراول نتواند خودش را بارگذاری کند. اگر لاراول اصلا اجرا نشود، طبیعتا نمی تواند صفحه maintenance mode را نمایش بدهد. لاراول راه حلی را برای این مشکل ارائه می دهد؛ ما می توانیم به لاراول بگوییم که یک صفحه از قبل آماده شده را برای چنین شرایطی ذخیره کرده و نمایش بدهد.

برای انجام این کار ابتدا به پوشه resources/views می روم و یک view جدید به نام maintenance.blade.php ایجاد می کنم. در این فایل محتوای ساده HTML خودمان را قرار می دهم:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>Sorry! We are working on the website for now. come back later!</h1>
</body>
</html>

من در این صفحه فقط یک تگ h1 دارم که می گوید ما در حال کار روی سایت هستیم، لطفا بعدا مراجعه کنید. در مرحله بعدی باید به لاراول بگوییم که این صفحه را به صورت آماده داشته باشد:

 php artisan down --render="maintenance"

همانطور که می بینید باید نام view خود را به render پاس بدهید. حالا اگر به وب سایت خود برویم (http://127.0.0.1:8000) شاید این view خواهیم بود. البته به جای ساخت یک view جدید می توانید از view های خود لاراول نیز استفاده کنید:

php artisan down --render="errors::503"

errors همان namespace مورد نظر است و 503 نیز نام view ما است. اگر به مرورگر بروید همان صفحه پیش فرض لاراول برای خطای ۵۰۳ و حالت maintenance را می بینید اما تفاوتی وجود دارد. حالا ما این صفحه را به صورت HTML خالص render کرده ایم و دیگر نیازی به لاراول برای render کردن آن نداریم، بنابراین حتی اگر لاراول نیز خراب شده و بالا نیاید کاربران این صفحه را می بینند. در ضمن اگر می خواهید تمام جزئیات کار را خودتان ویرایش کنید باید از دستور زیر کمک بگیرید:

php artisan vendor:publish

با اجرای این دستور laravel-errors را می بینید (در زمان نگارش این مقاله، شماره ۱۲). حالا اگر شماره مورد نظر آن را در ترمینال تایپ کرده و enter بزنید، view های مربوط به این قسمت در پوشه ای به نام errors در آدرس resources/views/errors قرار خواهند گرفت.

پارامتر بعدی redirect-- است. هر آدرسی را که به این پارامتر پاس بدهید، کاربران به آن آدرس منتقل خواهند شد. به طور مثال:

php artisan down --redirect="https://google.com"

در این حالت اگر کاربر وارد وب سایت ما شود، به سایت گوگل منتقل خواهد شد. طبیعتا شما باید یک آدرس دیگر را در نظر داشته باشید و کاربر را به جای گوگل به یک مکان صحیح و مشخص شده منتقل کنید. من از گوگل به عنوان مثال استفاده کردم تا نحوه استفاده از این دستور را بدانید. پارامتر سوم retry-- است که در واحد ثانیه مشخص می شود. به کد زیر نگاه کنید:

php artisan down --retry="20"

این دستور یعنی پس از ۲۰ ثانیه باید درخواست دوباره اجرا شود. کدام درخواست؟ درخواست ورود به وب سایت. مثلا اگر کاربر آدرس mydomain.com را در مرورگر خود وارد کرده باشد و ما در حالت maintenance باشیم، پس از ۲۰ ثانیه دوباره این درخواست اجرا می شود. پارامتر آخر نیز status است که مشخص می کند در حالت maintenance چه status code ای به کاربر برگردانده شود.

قابلیت queue کردن event listener در یک closure

فرض کنید ما یک event خاص داشته باشیم (مثلا فاکتور خرید هدیه). همانطور که می دانید برای ساخت این event باید در ترمینال دستور زیر را اجرا کنیم:

php artisan make:event GiftCertificatePurchased

این دستور باعث ساخت event ای به نام GiftCertificatePurchased می شود اما اگر بخواهیم به این event گوش کنیم، باید یک کلاس listener بسازیم (این دستور را اجرا نکنید):

php artisan make:listener

این قابلیت همیشه در لاراول بوده است و کمک زیادی به توسعه دهندگان می کند اما مسئله اینجاست که برای پروژه های کوچک، چنین سیستم پیچیده ای نیاز نیست و نوعی اضافه کاری حساب می شود. به همین دلیل شخصا زمانی که با پروژه های کوچک کار می کنم، به پوشه routes رفته و یک فایل به نام events.php ایجاد می کنم، سپس به فایل EventServiceProvider.php (پوشه providers) می روم و فایل events.php را در متد boot آن require می کنم:

public function boot()
{
    require base_path('routes/events.php');
}

شما هم برای تست این موضوع یک فایل به نام events.php ایجاد کنید و برای تست متن ساده ای در آن بنویسید. مثلا:

<?php
dd('Hello World');

همچنین آن را در EventServiceProvider.php نیز require کنید. حالا اگر به مرورگر بروید این متن ساده را خواهید دید بنابراین مشکلی نیست اما ما می توانیم به جای استفاده از یک کلاس کامل برای گوش دادن به این event از closure های رویدادمحور استفاده کنیم. به فایل events.php رفته، کد ()dd را حذف کرده و مثل من این کد را به جایش بنویسید:

<?php

use App\Events\GiftCertificatePurchased;
use Illuminate\Support\Facades\Event;

Event::listen(GiftCertificatePurchased::class, function (GiftCertificatePurchased $event) {
    dd('Event fired');
});

به تابع پاس داده شده به listen (پارامتر دوم) یک closure می گوییم. ما در لاراول 7 و نسخه های قبل تر به همین شکل عمل می کردیم، یعنی از Event و متد listen استفاده می کردیم تا به رویداد خاصی گوش بدهیم. همانطور که می بینید تابع بالا زیاد جالب نیست و ظاهری پیچیده دارد اما فعلا با همین روش جلو می رویم. ما پس از این مرحله به فایل routes می رویم (web.php) تا یک مسیر خاص را برای این رویداد تعریف کنیم:

Route::get('/', function () {
    event(new GiftCertificatePurchased());
    return view('welcome');
});

با این کار زمانی که کاربر به آدرس / (صفحه اصلی) برود، رویداد GiftCertificatePurchased اجرا خواهد شد. تمام مواردی که در اینجا توضیح دادم در لاراول 7 و نسخه های قبل تر آن بود اما لاراول 8 کار ما را راحت تر کرده است و دیگر نیازی به پارامتر اول listen نداریم:

use App\Events\GiftCertificatePurchased;
use Illuminate\Support\Facades\Event;

Event::listen(function (GiftCertificatePurchased $event) {
    dd('Event Fired!');
});

کد بالا خلاصه شده و مرتب شده تر از حالت قبل می باشد. یکی از مشکلات این روش این است که راهی برای queue کردن event listener وجود نداشت. queue کردن event listener یعنی آن را  در یک صف قرار بدهیم تا عملیات های دیگر را متوقف نکند. ما در کد بالا فقط یک متن ساده را dd می کنیم بنابراین نیازی به queue کردن نداریم اما به طور مثال اگر در یک event خاص، کارمان با پایگاه داده طولانی باشد (یا هر عملیات دیگری که طول بکشد)، باید تا پایان این عملیات و اتمام event صبر می کردیم تا عملیات های دیگر اجرا شوند. از امکانات جدید لاراول 8 این است که لاراول 8 این مشکل را حل کرده است! از این به بعد می توانیم با استفاده از تابع جدیدی به نام queueable رویداد های خود در این حالت را queue کنیم:

use App\Events\GiftCertificatePurchased;
use Illuminate\Support\Facades\Event;
use function Illuminate\Events\queueable;

Event::listen(queueable(function (GiftCertificatePurchased $event) {
    dd('Event Fired!');
}));

باز هم می گویم که اگر عملیات درون event شما سنگین نیست، اصلا نیازی به queue کردن رویداد ندارید. بدین صورت است که قابلیت queue کردن رویداد ها در یک closure نیز به لاراول 8 اضافه شده است.

آشنایی با مفهوم wormholes

یکی از امکانات جدید لاراول 8 این است که در لاراول 8 قابلیت جدیدی به نام wormhole ارائه شده است که یک کلاس می باشد و به شما اجازه سفر در زمان یا time-travel را می دهد. این قابلیت در testing کاربرد دارد بنابراین در قالب یک تست شما را با آن آشنا می کنم. البته من قصد نوشتن تمام جزئیات آن را ندارم و کلیت کار را به شما نشان خواهم داد. در مرحله اول به ترمینال رفته و دستور زیر را برای ساخت یک تست جدید اجرا می کنیم:

php artisan make:test PostTest --unit

حالا وارد فایل تست (PostTest.php) شده و یک تست جدید برای آن می نویسیم. تست ما می خواهد وجود یک پست و برنامه ریزی آن برای انتشار در سایت را بررسی کند. ما فرض می کنیم مدلی برای پست ها داریم و این مدل ستون published_at (زمان انتشار) را مشخص می کند. البته هر پست به صورت خودکار در حالت پیش نویس قرار می گیرد بنابراین published_at برای آن false خواهد بود مگر آنکه خودمان آن را ویرایش کنیم:

class Post extends Model
{
    use HasFactory;

    protected $casts = [
        'published_at' => 'date'
    ];
}
با این فرض می توانیم تست را به شکل زیر بنویسیم:
class PostTest extends TestCase
{
    /** @test */
    function it_knows_if_it_is_scheduled()
    {
        $post = Post::factory()->create();
        $this->assertFalse($post->isScheduled());
    }
}

به عبارتی تست ما یک پست می سازد و از آنجایی که هر پست باید به صورت پیش فرض در حالت پیش نویس باشد، scheduled نیست (زمان بندی شده برای انتشار خودکار) و طبیعتا false خواهد بود. مشکل اینجاست که ما متد isScheduled را نداریم بنابراین برای جلو رفتن مثال در همان مدل می گوییم:

class Post extends Model
{
    use HasFactory;

    protected $casts = [
        'published_at' => 'date'
    ];

    public function isScheduled()
    {
        return false;
    }
}

در صورتی که تست خود را اجرا کنیم، نتیجه مثبت می گیریم (تست pass می شود). حالا اگر یک هفته به تاریخ آن اضافه کنیم چطور؟

class PostTest extends TestCase
{
    /** @test */
    function it_knows_if_it_is_scheduled()
    {
        $post = Post::factory()->create();
        $this->assertFalse($post->isScheduled());

        $post->published_at = now()->week();
        $this->assertTrue($post->isScheduled());
    }
}

تست بالا در تئوری باید pass شود چرا که یک هفته به زمان published_at اضافه کرده ایم اما اگر آن را اجرا کنید fail می شود. چرا؟ به دلیل اینکه متد isScheduled را به صورت دستی نوشتیم و گفتیم که همیشه false را برگرداند. برای تصحیح این مشکل می توانیم متد isScheduled را به شکل زیر بنویسیم:

class Post extends Model
{
    use HasFactory;

    protected $casts = [
        'published_at' => 'date'
    ];

    public function isScheduled()
    {
        return $this->published_at->gt(now());
    }
}

یعنی اگر published_at از زمان حال (now) جلوتر بود (gt مخفف greater than). این بار اگر تست را اجرا کنید، باز هم fail می شود اما به دلیل دیگری! فرض ما این بود که published_at در ابتدا null است بنابراین بزرگ تر از null معنی نمی دهد. با این حساب باید باز هم متد isScheduled را ویرایش کنیم:

class Post extends Model
{
    use HasFactory;

    protected $casts = [
        'published_at' => 'date'
    ];

    public function isDraft()
    {
        return is_null($this->published_at);
    }

    public function isScheduled()
    {
        if ($this->isDraft()) return false;
        return $this->published_at->gt(now());
    }
}

من در ابتدا متدی به نام isDraft ایجاد کرده ام که مشخص می کند آیا پست ما در حالت پیش نویس است یا خیر. اگر published_at برابر null بود یعنی پست پیش نویس است بنابراین true برگردانده می شود. در مرحله بعدی یک if را در متد isScheduled تعریف کرده ام تا اگر پست ما پیش نویس بود false را برگرداند. این بار اگر تست را اجرا کنید، pass می شود. تا اینجای کار همه چیز طبق لاراول 7 و نسخه های قبل از آن می باشد اما لاراول 8 قابلیت wormhole را اضافه می کند. چطور؟ به کد زیر نگاه کنید:

class PostTest extends TestCase
{
    /** @test */
    function it_knows_if_it_is_scheduled()
    {
        $post = Post::factory()->create();
        $this->assertFalse($post->isScheduled());

        $post->published_at = now()->week();
        $this->assertTrue($post->isScheduled());

        $this->travel(8)->days();
        $this->assertFalse($post->isScheduled());
    }
}

ما در اینجا متد travel را داریم که 8 واحد گرفته است و سپس ()days مشخص می کند که منظور ما از 8 واحد، 8 روز است. این دستور می گوید تصور کن که از زمان حال به 8 روز آینده منتقل شویم («حالا» برابر با 8 روز در آینده باشد). در این حالت حتما پست ما منتشر شده است و دیگر scheduled (آماده انتشار) نیست بنابراین باید false بگیریم. اگر تست را اجرا کنید همه چیز pass می شود. این قابلیت جدید انعطاف پذیری بالایی دارد مثلا به جای روز می توانید از دقیقه، ماه، ساعت و غیره نیز استفاده کنید:

$this->travel(8)->milliseconds();
$this->travel(8)->seconds();
$this->travel(8)->minutes();
$this->travel(8)->hours();
$this->travel(8)->days();
$this->travel(-30)->days();
$this->travel(8)->weeks();
$this->travel(8)->years();

در مثال بالا توجه کنید که می توانید از مقادیر منفی نیز استفاده کنید. همچنین به جای اضافه کردن زمان، می توانید یک نقطه زمانی خاص را به شکل زیر مشخص کنیم:

$this->travelTo(now()->addMonth()); // یک ماه آینده
$this->travelTo(now()->subMonth()); // یک ماه قبل
$this->travelBack(); // برگشتن از زمان آینده به زمان حال یا به عبارتی برگشتن به حالت عادی
$this->travelTo($user->trial_ends_at); // از پایگاه داده کاربر ستون انقضای نسخه آزمایشی را برداشته ایم

سفر در زمان قبلا هم در لاراول وجود داشت اما باید آن را به صورت دستی می نوشتیم. از لاراول 8 به بعد کلاس wormhole اضافه شده است که کار ما را بسیار ساده می کند.

بهبود قابلیت rate limiting

در لاراول 7 اگر به app\Http\Kernel.php بروید قسمتی به نام middlewareGroups$ مشاهده خواهید کرد:

protected $middlewareGroups = [
    'web' => [
        \App\Http\Middleware\EncryptCookies::class,
        \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
        \Illuminate\Session\Middleware\StartSession::class,
        // \Illuminate\Session\Middleware\AuthenticateSession::class,
        \Illuminate\View\Middleware\ShareErrorsFromSession::class,
        \App\Http\Middleware\VerifyCsrfToken::class,
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
    ],

    'api' => [
        'throttle:60,1',
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
    ],
];

در قسمت api چیزی به نام throttle داریم. ما در لاراول 7 و نسخه های قبل از آن بدین صورت از API Rate Limiting استفاده می کردیم. اگر نمی دانید API Rate Limiting چیست به صورت خلاصه برایتان توضیح می دهم؛ کد بالا می گوید برای API برنامه ما، کاربران اجازه دارند ۶۰ درخواست بر دقیقه ارسال کنند. عدد 60,1 به معنی ۶۰ درخواست در یک دقیقه می باشد. برای نشان دادن این قابلیت من آن را روی ۳ درخواست می گذارم:

'api' => [
    'throttle:3,1',
    \Illuminate\Routing\Middleware\SubstituteBindings::class,
],
سپس به فایل api.php در پوشه routes می روم و یک مسیر جدید تعریف می کنم:
Route::middleware('auth:api')->get('/user', function (Request $request) {
    return $request->user();
});

Route::get('foo', fn () => 'bar');

مسیر اول در خود لاراول است و مسیر دوم را من تعریف کرده ام. تنها کاری که این مسیر انجام می دهد برگرداندن کلمه bar است. حالا به مرورگر و مسیر http://127.0.0.1:8000/api/foo بروید. با باز کردن این صفحه فقط کلمه bar را می بینید. ما rate limiting (محدود سازی سرعت ارسال درخواست) را برای این api روی ۳ درخواست بر دقیقه گذاشته بودیم بنابراین باید پشت سر هم این صفحه را refresh کنیم تا درخواست های متعددی به آن ارسال کنیم. اگر از این ۳ درخواست عبور کنید با خطای 429 و پیام  Too Many Requests (تعداد بیش از حد درخواست) روبرو خواهید شد. این قابلیت چیز جدیدی نیست و در لاراول 7 نیز وجود داشته است اما در لاراول 8 کمی تغییر پیدا کرده است.

در لاراول 8 اگر به app\Http\Kernel.php بروید، دیگر کد مربوط به rate limiting را پیدا نخواهید کرد:

protected $middlewareGroups = [
    'web' => [
        \App\Http\Middleware\EncryptCookies::class,
        \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
        \Illuminate\Session\Middleware\StartSession::class,
        // \Illuminate\Session\Middleware\AuthenticateSession::class,
        \Illuminate\View\Middleware\ShareErrorsFromSession::class,
        \App\Http\Middleware\VerifyCsrfToken::class,
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
    ],

    'api' => [
        'throttle:api',
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
    ],
];

البته باید به شما بگویم که هنوز هم می توانید rate limiting را به این قسمت اضافه کرده و مانند لاراول 7 عمل کنید. در لاراول 8 برای تنظیم rate limiting باید از مسیر app\providers به RouteServiceProvider.php بروید. در انتهای این فایل تابعی به نام configure rate limiting وجود دارد:

protected function configureRateLimiting()
{
    RateLimiter::for('api', function (Request $request) {
        return Limit::perMinute(60);
    });
}

ما در اینجا دو کلاس RateLimiter و Limit را داریم. آرگومان اولِ متد for، یک رشته به نام api است بنابراین محدود سازی نرخ درخواست برای api دقیقا ۶۰ درخواست بر دقیقه است. یادتان باشد که رشته api پاس داده شده به متد ()for فقط یک رشته است و شما می توانید آن را به سلیقه خودتان تغییر دهید. باید یادتان باشد که در فایل Kernel.php چنین کدی را داشتیم:

'api' => [
    'throttle:api',
    \Illuminate\Routing\Middleware\SubstituteBindings::class,
],

اینجا نیز همان رشته api را می بینید. ما rate limiting را روی هر middleware ای اعمال کرده ایم که نام api را داشته باشد، بنابراین می توانیم هر زمان که خواستیم آن را عوض کنیم. ما اگر کد بالا را به شکل زیر تغییر بدهیم:

'api' => [
    'throttle:foobar',
    \Illuminate\Routing\Middleware\SubstituteBindings::class,
],

باید به RouteServiceProvider.php برگشته و تابع configureRateLimiting را نیز به شکل زیر ویرایش کنیم:

protected function configureRateLimiting()
{
    RateLimiter::for('foobar', function (Request $request) {
        return Limit::perMinute(60);
    });
}

یادتان باشد که لاراول فقط یک مجموعه پکیج از کدها است بنابراین واقعا نمی داند api چیست بلکه می داند این قسمت از مجموعه کدهای ما یک middleware خاص هستند که نامشان api است اما می توانند هر نامی داشته باشند. با این حساب شما می توانید نرخ هر چیزی را در لاراول محدود کنید و اصلا به api محدود نخواهید بود. به طور مثال بیایید یک rate limiter را برای دانلود های کاربران تعریف کنیم:

protected function configureRateLimiting()
{
    RateLimiter::for('api', function (Request $request) {
        return Limit::perMinute(60);
    });

    RateLimiter::for('downloads', function (Request $request) {
        return Limit::perMinute(2);
    });
}

حالا ما یک rate limiter جدید داریم که ۲ درخواست بر دقیقه را مجاز می داند و روی هر middleware ای اعمال خواهد شد که نامش downloads باشد. باز هم می گویم که لاراول نمی تواند رشته downloads را خوانده و بگوید از این به بعد هر کاربری که بخواهد چیزی را دانلود کند، من آن را limit خواهم کرد! downloads برای لاراول فقط یک رشته است بنابراین باید خودمان آن را به صورت دستی اعمال کنیم. برای این کار به فایل web.php در پوشه routes رفته و یک مسیر جدید را تعریف می کنیم:

Route::get('/downloads', function () {
    return 'some file::download() call should be executed here';
})->middleware('throttle:downloads');

من با استفاده از متد ()middleware می توانم مسیر جدید خودم را به یک گروه middleware خاص منتسب کنم بنابراین به آن throttle:downloads را پاس داده ام تا محدودیت های روی گروه downloads روی این دستور نیز اعمال شوند. حالا اگر در مرورگر خود به آدرس downloads در صفحه اصلی بروید (127.0.0.1:8000/downloads) و صفحه را پشت سر هم refresh کنید، با خطای ۴۲۹ روبرو خواهید شد. این محدودیت بر اساس کاربران اعمال می شود بنابراین می توانیم بین کاربران مختلف تفاوت بگذاریم. به طور مثال:

protected function configureRateLimiting()
{
    RateLimiter::for('api', function (Request $request) {
        return Limit::perMinute(60);
    });

    RateLimiter::for('downloads', function (Request $request) {
        return $request->user()->isPremium() ? Limit::none() : Limit::perMinute(30);
    });
}

من در این مثال از طریق request$ به کاربر دسترسی پیدا کرده ام و سپس متد فرضی isPremium را اجرا کرده ام. این متد وجود ندارد اما فرض کنید وجود داشته و کارش این است که مشخص کند آیا کاربر حساب ویژه و پولی دارد یا کاربر عادی است. اگر کاربر حساب ویژه دارد مقدار true برگردانده شده و در غیر این صورت false می گیریم. اگر true گرفتیم limit را روی none می گذاریم که یعنی هیچ محدودیتی در تعداد درخواست های ارسالی نیست اما اگر false گرفتیم limit را روی ۳۰ درخواست بر ثانیه قرار می دهیم. حتما این مثال را در وب سایت هایی مانند گیت هاب دیده اید. گیت هاب تعداد درخواست های api شما را محدود می کند مگر آنکه حساب ویژه خریده باشید.

آشنایی با Jetstream

Jetstream یک scaffolding package است که جایگزین Laravel UI شده است. اگر یادتان باشد در نسخه های قبلی لاراول، قابلیتی به نام Laravel UI داشتیم که در آن صفحه اصلی و ثبت نام و ورود به حساب کاربری از قبل برایمان ساخته شده بود. به عنوان یکی از امکانات جدید لاراول 8 باید گفت لاراول 8 دیگر Laravel UI را ندارد و به جای آن از JetStream استفاده می کند که بسیار پیشرفته تر است. در قدم اول دستور laravel help new را در ترمینال خود اجرا کنید تا انواع پارامتر های ممکن را برای دستور new ببینید:

Description:
  Create a new Laravel application

Usage:
  new [options] [--] [<name>]

Arguments:
  name                  

Options:
      --dev             Installs the latest "development" release
      --jet             Installs the Laravel Jetstream scaffolding
      --stack[=STACK]   The Jetstream stack that should be installed
      --teams           Indicates whether Jetstream should be scaffolded with team support
  -f, --force           Forces install even if the directory already exists
  -h, --help            Display this help message
  -q, --quiet           Do not output any message
  -V, --version         Display this application version
      --ansi            Force ANSI output
      --no-ansi         Disable ANSI output
  -n, --no-interaction  Do not ask any interactive question
  -v|vv|vvv, --verbose  Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug

از بین این دستورات، ۳ دستور جدید وجود دارد که قبل از لاراول 8 به آن ها دسترسی نداشتیم:

  • پارامتر jet--
  • پارامتر stack--
  • پارامتر teams--

jet-- برای Jetstream است که یک برنامه scaffolding محسوب می شود، یعنی صفحات آماده یا قالب های آماده در اختیار ما می گذارد و معادل جدید Laravel UI محسوب می شود. این قالب انواع و اقسام قابلیت ها را دارد که به وقت خودش بررسی خواهند شد. پارامتر teams-- قابلیت کار تیمی و پشتیبانی از اعضای تیم را به پروژه شما اضافه می کند. پارامتر stack-- مخصوص livewire و inertia.js است. اگر با این دو کتابخانه آشنایی ندارید به صورت خلاصه برایتان توضیح می دهم.

Livewire یک کتابخانه عالی برای ساخت برنامه های پیشرفته، مدرن و بسیار زیبا است که از laravel blade به عنوان template language خود استفاده می کند. حتما شما با blade آشنا هستید بنابراین این قسمت نیازی به یادگیری دوباره ندارد. Livewire برای کسانی ساخته شده است که می خواهند برنامه ای عالی و مدرن داشته باشند اما دوست ندارند یک فریم ورک جاوا اسکریپتی کامل مانند Vue.js یا React.js یا Angular.js را یاد بگیرند. به عبارت دیگر اگر دوست ندارید از Vue.js و امثال آن استفاده کنید، livewire یک گزینه عالی است. اطلاعات بیشتر در documentation لاراول برای livewire موجود است. Livewire در ابتدای کار به نوعی نامرئی است چرا که شما از همان blade استفاده کرده و صفحات خود را می سازید و در هر جایی نیاز به جاوا اسکریپت داشتید می توانید از جاوا اسکریپت ساده استفاده کنید، اما اگر نیاز به مقدار زیادی جاوا اسکریپت داشتید می توانید یک کامپوننت livewire بسازید.

Inertia.js یک کتابخانه دیگر است که  به جای Laravel Blade از Vue.js به عنوان template engine خود استفاده می کند. این کتابخانه از routing خود لاراول استفاده می کند بنابراین دیگر نیازی به درگیری با Vue Router و پیچیدگی های آن نخواهید داشت. شما می توانید با استفاده از inertia.js در backend سایت خود یک کامپوننت Vue ساخته و به راحتی از آن استفاده کنید. اطلاعات بیشتر در documentation لاراول برای Inertia.js موجود است. Inertia.js برخلاف Livewire از همان ابتدا کاملا واضح است چرا که دیگر با blade کار نخواهید کرد.

هر کدام از این دو کتابخانه کاملا متفاوت بوده و باید documentation آن ها را مطالعه کنید تا بتوانید از آن ها استفاده نمایید. ما فرض می کنیم که می خواهیم از livewire استفاده کنیم و یک پروژه جدید را می سازیم:

laravel new jetstream --jet --stack=livewire

در صورتی که می خواستید از قابلیت teams نیز استفاده کنید باید دستور بالا را به شکل زیر اجرا می کردید:

laravel new jetstream --jet --stack=livewire --teams

اما من نیازی به teams ندارم بنابراین همان دستور اول را اجرا می کنم. با اجرای این دستور از شما پرسیده می شود که Will your application use teams و شما باید no را وارد کرده و enter بزنید. اجرای این دستور چند دقیقه طول می کشد و باید فایل هایی را دانلود کند بنابراین صبور باشید. در ضمن از آنجایی که نامی برای این پروژه انتخاب نکردیم، پوشه ای به نام jetstream برایتان ساخته خواهد شد. پس از پایان این عملیات از شما خواسته می شود که وابستگی های front-end را نصب کنید. برای این کار ابتدا cd jetstream می کنیم تا وارد پوشه پروژه شویم، سپس دستور زیر را اجرا می کنیم:

npm install && npm run dev

این دستور ابتدا تمام وابستگی های front-end شما را نصب می کند (npm install) و سپس آن ها را کامپایل می کند (npm run dev). زمانی که تمام این عملیات انجام شد، ویرایشگر خود را باز کرده و نگاهی به پوشه views بیندازید. ما در این پوشه انواع و اقسام فایل ها و پوشه های دیگر را داریم که بعضی از آن ها جدید هستند. همچنین در فایل web.php از پوشه routes یک مسیر جدید را می بینیم که یک view به نام dashboard یا همان داشبورد (صفحه کاربری) را باز می کند:

Route::middleware(['auth:sanctum', 'verified'])->get('/dashboard', function () {
    return view('dashboard');
})->name('dashboard');

البته این route جدید فعلا در مرورگر باز نمی شود چرا که حتما باید پایگاه داده ای برای این پروژه داشته باشید. برای حل این مشکل مثل همیشه به فایل env. رفته و تنظیمات پایگاه داده خود را به آن بدهید. پس از آنکه این پایگاه داده را به لاراول متصل کردید یادتان باشد که حتما دستور php artisan migrate را نیز اجرا کنید تا تمام جدول هایتان ساخته شوند. حالا با مراجعه به صفحه اصلی سایت، در قسمت بالا و سمت راست، دو گزینه login و register را نیز خواهید دید. فعلا هیچ کاربری در پایگاه داده وجود ندارد بنابراین باید روی لینک register کلیک کرده و با هر اطلاعاتی که می خواهید یک کاربر جدید را ثبت نام کنید. به محض اینکه login شوید یک صفحه زیبا را می بینید که همان dashboard شما است (آدرس domain.com/dashboard). در سمت بالا و راست صفحه نام کاربری خود را می بینید که با کلیک روی آن یک منو با سه گزینه نمایش داده می شود:

  • Profile
  • API Tokens
  • Logout

نکته بسیار جالب اینجاست که اگر روی Profile کلیک کنید وارد تنظیمات پروفایل خودتان خواهید شد. اگر به صفحه تنظیمات پروفایل نگاهی بیندازید شاید باورتان نشود که این همه قابلیت به صورت خودکار و بدون زحمت در اختیار شما قرار گرفته است! شما در این صفحه می توانید:

  • تصویر پروفایل خود را تغییر بدهید.
  • نام و ایمیل خود را تغییر بدهید.
  • رمز عبور خود را تغییر بدهید.
  • از two-factor authentication یا تایید دو مرحله ای استفاده کنید.
  • به session های خود به همراه IP هایشان دسترسی داشته باشید.
  • حساب کاربری خود را به طور کامل حذف کنید.

جالب تر اینجاست که اگر فایل dashboard.blade.php را باز کنید، با محتویات زیر روبرو خواهید شد:

<x-app-layout>
    <x-slot name="header">
        <h2 class="font-semibold text-xl text-gray-800 leading-tight">
            {{ __('Dashboard') }}
        </h2>
    </x-slot>

    <div class="py-12">
        <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
            <div class="bg-white overflow-hidden shadow-xl sm:rounded-lg">
                <x-jet-welcome />
            </div>
        </div>
    </div>
</x-app-layout>

همانطور که مشخص است، ما از HTML عادی استفاده نکرده ایم بلکه از کامپوننت های Livewire استفاده می کنیم. ما در کد بالا کامپوننت x-jet-welcome را مشاهده می کنیم اما به نظر شما این کامپوننت کجای پروژه ما تعریف شده است؟ شاید تصور کنید که یک فایل جداگانه در views برای این کامپوننت ها خواهیم داشت اما این تصور اشتباهی است! این کامپوننت به صورت پیش فرض در پکیج jetstream موجود است و از آنجا بارگذاری می شود. آیا این بدان معنی است که دیگر نمی توانیم به این کامپوننت ها دسترسی داشته باشیم؟ این تصور نیز اشتباه است! شما می توانیم با خروجی گرفتن از این پکیج ها به طور کامل روی آن ها کنترل داشته باشید. برای این کار باید دستور خاص آن را در پایگاه داده اجرا کنیم:

php artisan vendor:publish

مثل همیشه با اجرای این دستور لیستی از انواع کامپوننت ها و فایل های مخفی برایمان نمایش داده می شود. اگر یادتان باشد فایل های خطای لاراول (laravel-errors) در این قسمت بودند. فایل های views مربوط به jetstream برای من روی ردیف ۲۳ است بنابراین عدد ۲۳ را در ترمینال خود تایپ کرده و enter بزنید. حالا اگر به پوشه views برویم یک پوشه جدید به نام vendor/jetstream/components را مشاهده خواهیم کرد. این پوشه حاوی تمام view های مربوط به jetstream است و  کامپوننت x-jet-welcome که دنبال آن بودیم در قالب فایل welcome.blade.php قابل مشاهده می باشد.

برای آشنایی شما با این کامپوننت ها یک مثال از قسمت تغییر رمز عبور در تنظیمات پروفایل می زنم. از بین فایل های موجود در vendor، فایل show.blade.php را باز کنید. اگر به قسمت تغییر رمز عبور آن نگاهی بیندازیم، چنین چیزی را خواهید دید:

@if (Laravel\Fortify\Features::enabled(Laravel\Fortify\Features::updatePasswords()))
    <div class="mt-10 sm:mt-0">
        @livewire('profile.update-password-form')
    </div>

    <x-jet-section-border />
@endif

دستور livewire@ مسئول صدا زدن یک کامپوننت یا قسمتی از یک view است و در اینجا فرم  update-password-form را صدا می زند. شما می توانید این فایل را (update-password-form.blade.php) در پوشه profiles پیدا کنید. در خط اول این فایل قسمتی به نام زیر وجود دارد:

<x-jet-form-section submit="updatePassword">
    <x-slot name="title">
        {{ __('Update Password') }}
    </x-slot>
// بقیه کدها //

در قسمت submit می بینید که این فرم در هنگام ثبت شدن، به سرور ارسال نمی شود بلکه متدی به نام updatePassword را صدا می زند. این متد در فایلی به نام UpdatePasswordForm.php تعریف شده است. این فایل خودش در مسیر app\Http\Livewire قرار دارد:

public function updatePassword(UpdatesUserPasswords $updater)
{
    $this->resetErrorBag();

    $updater->update(Auth::user(), $this->state);

    $this->state = [
        'current_password' => '',
        'password' => '',
        'password_confirmation' => '',
    ];

    $this->emit('saved');
}

تمام این دستورات به عنوان بخشی از یک درخواست AJAX اجرا می شود بنابراین باعث refresh شدن صفحه نمی شود. در ضمن اکثر این دستورات در فایل های موجود در آدرس app\Actions انجام می شوند. به طور مثال همین متد با فایل UpdateUserPassword.php در مسیر app\Actions\Fortify در تماس می باشد. همانطور که قبلا نیز توضیح داده بودم برای یادگیری Livewire و Inertia.js باید زمان بگذارید و یادگیری آن نیاز به یک دوره جداگانه یا مطالعه Documentation آن و تمرین دارد. به هر حال امیدوارم از مبحث امکانات جدید لاراول 8 لذت برده و متوجه تفاوت های اصلی لاراول 7 و لاراول 8 شده باشید.


منبع: Laracasts

نویسنده شوید
دیدگاه‌های شما (1 دیدگاه)

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

امیررضا
23 آبان 1399
یه سوالی داشتم و اونم اینه که با پیاده سازیه liwewire یا inertia برای احراز هویت لاراول تمپلیت خودش رو سوار می‌کنه روی احراز هویت و اگه یکی بخواد ui مختص خودش رو داشته باشه با درد سر خاصی مواجه هستش؟؟ و اگر کسی بخواد سیستم احراز هویت خودش رو بدون استفاده از liwewire یا inertia پیاده سازی کنه و فقط با blade ها کار کنه این امکان براش هست؟

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

امیر زوارمی
25 آبان 1399
آقای Taylor Otwell (سازنده ی لاراول) چند روز پیش توییت کردن (https://twitter.com/taylorotwell/status/1325890243493556224) که پکیجی به نام Laravel Breeze برای لاراول ساخته شده! لینک گیت هاب: https://github.com/laravel/breeze اینطوری می تونید بدون liwewire یا inertia کار کنید. این پکیج فقط از همون blade و کتابخانه ی CSS ای به نام Tailwind استفاده می کنه. می تونید نگاهی به این مورد بندازید.

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.