ویژگی های جدید لاراول 9 - قسمت دوم

ویژگی های جدید لاراول 9 - قسمت دوم

در بخش قبلی مقاله ویژگی های جدید لاراول 9 تعدادی از قابلیت های اضافه شده به این فریمورک مطرح شد. در این بخش به بررسی قابلیت های جدید و مهم تر این فریم ورک ادامه می دهیم.

نیاز داشتن به نسخه 8 PHP

از آن جایی که نسخه 9 لاراول از نسخه 6 Symfony استفاده می کند و این نسخه از Symfony برای اجرا به PHP 8 یا بالاتر نیاز دارد، لاراول 9 هم نمی تواند با کمتر از نسخه 8 زبان PHP اجرا شود.

ظاهر جدید برای لیست مسیر ها

اگر برنامه ای داشته باشیم که مسیر های زیادی دارد و دستور route:list را اجرا کنیم، ممکن است خروجی نا‌منظمی ببینیم. در نسخه جدید خروجی این دستور بهبود چشم گیری داشته است.

گزینه coverage برای اجرای تست ها

در نسخه جدید هنگام اجرای دستور php artisan test می توانیم این گزینه را هم بدهیم تا درصد پوشش تست های برنامه مان مشخص شود.

Interface جدید برای QueryBuilder

اگر شما هم تا کنون هنگام کار با QueryBuilder و type hint کردن ورودی ها دچار شک می شدید که کدام یک از کلاس های Illuminate\Contracts\Database\QueryBuilder و Illuminate\Database\Eloquent\Concerns\DecoratesQueryBuilder را استفاده کنید، می توانید از این interface مشترک که اضافه شده است استفاده کنید و با خیال راحت تری کد های خود را اجرا کنید. این interface به آنالیزور های استاتیک هم کمک زیادی می کند.

بهبود ارزیابی آرایه های تو در تو

ممکن است نیاز داشته باشید قانون ارزیابی ای که روی آرایه های تو در تو اعمال می شود را بر اساس مقدار هر فیلد تعیین کنید. اکنون می توانیم با استفاده از متد Rule::forEach این کار را انجام دهیم. متد forEach یک closure می گیرد که هنگام ارزیابی هر عضو از آرایه فراخوانی می شود و مقداری که باید ارزیابی شود را به همراه نام آن کلید دریافت می کند تا ما بر اساس آن، آرایه ای از قوانین را بر گردانیم.

use App\Rules\HasPermission;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;

$validator = Validator::make($request->all(), [
    'companies.*.id' => Rule::forEach(function ($value, $attribute) {
        return [
            Rule::exists(Company::class, 'id'),
            new HasPermission('manage-company', $value),
        ];
    }),

]);

توابع کمکی جدید

در نسخه 9 دو تابع helper جدید معرفی شده که می توانیم از آن ها استفاده کنیم.

تابع Str

تابع str برای رشته ی داده شده، یک شی از کلاس Illuminate\Support\Stringable به ما می دهد. صدا زدن این تابع مانند Str::of است.

str('Taylor')->append(' Otwell');

تابع to_route

تابع to_route می تواند یک پاسخ HTTP برای هدایت کاربر به مسیر های نام گذاری شده برنامه ایجاد کند. همچنین می توانیم با استفاده از این تابع استاتوس کد و هدر های خودمان را هم تنظیم کنیم.

return to_route('users.show', ['user' => 1], 302, ['X-Framework' => 'Laravel']);

بهبود صفحه خطا

لاراول از افزونه ای به نام Ignition برای نمایش خطا ها استفاده می کند. تیم معروف Spatie که توسعه این افزونه را انجام می دهد صفحه خطا ها را باز طراحی کرده و قابلیت هایی مثل dark mode و open in editor را به آن اضافه کرده است. نسخه 9 لاراول از نسخه جدید این افزونه استفاده می کند.

بهبود پشتیبانی IDE ها هنگام کار با Collection

برای کلاس Collection در فریم ورک، type هایی به صورت generic تعریف شده است که باعث می شود IDE هایی مانند PHPStorm و آنالیزور های استاتیکی مانند PHPStan درک بهتری از عملکرد متد های این کلاس داشته باشند.

اضافه کردن context به همه لاگ ها 

متد withContext به کلاس Log اضافه شده است که توسط آن می توانیم داده ای را به همه لاگ هایی که در یک درخواست ثبت می شوند اضافه کنیم. برای نمونه می توانیم ابتدای هر درخواست وارد شده به برنامه یک شناسه به آن اختصاص دهیم تا در لاگ ها به راحتی مسیری که هر درخواست پیموده را بررسی کنیم. کد زیر middleware ای است که این کار را انجام می دهد:

public function handle($request, Closure $next)
{
    $requestId = (string) Str::uuid();

    Log::withContext([
        'request-id' => $requestId
    ]);

    return $next($request)->header('Request-Id', $requestId);
}

پاک سازی job های fail شده

با دستور prune:failed که به فریم ورک اضافه شده است می توانیم ردیف های موجود در جدول failed_jobs را پاک کنیم. به صورت پیش فرض این دستور ردیف هایی که از ایجاد آن ها بیش از یک روز گذشته است را پاک می کند. با استفاده از گزینه hours-- می توانیم این زمان را تغییر دهیم.

php artisan prune:failed --hours=12

لغو ارسال اعلان ها

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

public function shouldSend($notifiable, $channel)

{
    return true;

}

پاک سازی مدل ها

در نسخه جدید یک Trait برای مدل ها اضافه شده است که با کمک آن می توانیم ردیف هایی از جدول مربوط به مدل را به عنوان "بی استفاده" مشخص کنیم تا لاراول آن ها را برای ما پاک کند.

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Prunable; // or MassPrunable

class Post extends Model
{
    use Prunable;

    /**
     * Determines the prunable query.
     *
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function prunable()
    {
        return $this->where('created_at', '<=', now()->subMonth());
    }

}

در متد prunable کوئری ای که با آن ردیف های بی استفاده بدست می آید را می نویسیم و اجرای دستور  php artisan model:prune را زمان بندی می کنیم.

نمایش exception ایجاد شده هنگام اجرای تست ها

پیش از این قابلیت زمانی که تست ها را اجرا می کردیم و fail می شدند برای مشاهده خطا مجبور بودیم دوباره آن ها را اجرا کنیم و متد ;()this->withoutExceptionHandling$ را صدا بزنیم تا بتوانیم خطای پیش آمده را ببینیم. در نسخه جدید نیازی به این کار نیست و زمانی که اجرای تست ها fail می شوند می توانیم لاگ کامل خطا را در کنسول مشاهده کنیم.

تبدیل تاریخ ها به حالت تغییر ناپذیر

با اضافه شدن cast جدید به مدل ها با نام های immutable_datetime و immutable_date می توانیم مقدار تاریخ هایی که از دیتابیس خوانده می شوند را در قالب یک شی از کلاس CarbonImmutable دریافت کنیم تا مطمئن باشیم مقدار این تاریخ در هیچ جای برنامه قابل تغییر نیست.

دستوری برای بررسی حجم صف ها

دستور queue:monitor اضافه شده است تا بتوانیم اندازه هر صف را مشاهده کنیم.

متد withTrashed برای مسیر ها

اگر از قابلیت route model binding لاراول استفاده می کنید در نسخه جدید می توانید متد withTrashed را روی مسیر ها صدا بزنید تا هنگام فراخوانی مدل از دیتابیس، مدل های soft delete شده هم خوانده شوند و خطای 404 ایجاد نشود.

Route::post('/user/{user}', function (ImplicitBindingModel $user) {
    return $user;

})->middleware(['web'])->withTrashed();

شیوه راحت تر دریافت بخشی از ورودی ارزیابی شده

متد های جدیدی به کلاس Request اضافه شده تا بتوانیم راحت تر با ورودی های ارزیابی شده کار کنیم.

$formRequest->safe()->only([...]);

$formRequest->safe()->except([...]);

$formRequest->safe()->collect();

متد firstOrFail در کلاس Collection

این متد مانند firstOrFail کلاس QueryBuilder برای Collection ها عمل می کند. در صورتی که هیچ item ای در collection نباشد یک exception از نوع ItemNotFoundException ایجاد خواهد شد.

$collection->where('name', 'fish')->firstOrFail();

متد updateOrFail برای مدل ها

با اضافه شدن این متد نیازی نیست خطا های پیش آمده هنگام آپدیت مدل ها را خودمان handle کنیم و هنگام خطا یک exception ایجاد می شود.

قابلیت تلاش مجدد برای درخواست های HTTP

می توانیم از متد rety اضافه شده به کلاس HTTP استفاده کنیم تا در صورت بروز خطا، درخواست خود را پس از مدت معینی دوباره ارسال کنیم. همچنین توسط ورودی سوم این متد می توانیم با دادن یک callback مشخص کنیم که می خواهیم درخواست ما دوباره ارسال شود یا خیر.

$response = Http::retry(3, 100, function ($exception) {
    return $exception instanceof ConnectionException;

})->post(...);

میانبر کوئری زدن بر اساس یک رابطه

متد whereRelation اضافه شده است تا برای نوشتن کوئری روی یک رابطه کد کمتری نوشته شود. قبل از اضافه شدن این متد کدی مانند زیر می نوشتیم:

User::whereHas('posts', function ($query) {
    $query->where('published_at', '>', now());

})->get();

اکنون می توانیم کد بالا را به شکل زیر بنویسیم:

User::whereRelation('posts', 'published_at', '>', now())->get();

متد whereMorphedTo

این متد برای راحت تر شدن نوشتن کوئری اضافه شد است. قبل از این متد کدی مانند زیر نوشته می شد:

$feedback = Feedback::query()
    ->where('subject_type', $model->getMorphClass())
    ->where('subject_id', $model->getKey())
    ->get();

اکنون کد بالا را به این شکل می نویسیم:

$feedback = Feedback::whereMorphedTo('subject', $model)->get();

متد deleteOrFail

با اضافه شدن این متد نیازی نیست خطا های پیش آمده هنگام حذف یک مدل را خودمان handle کنیم و در صورت بروز خطا یک exception ایجاد خواهد شد.

متد valueOrFail

این متد یک مقدار از اولین نتیجه کوئری را دریافت و در صورت نبودن نتیجه یک exception ایجاد می کند. قبل از اضافه شدن متد valueOrFail کدی مانند زیر می نوشتیم:

$votes = User::where('name', 'John')->firstOrFail('votes')->votes;

اکنون می توانیم کد زیر را بنویسیم:

$votes = User::where('name', 'John')->valueOrFail('votes');

متد collect در کلاس Request

از این به بعد می توانیم ورودی های درخواست را به صورت Collection دریافت کنیم.

قبل از این متد کد زیر نوشته می شد:

collect($request->input('users', []))->each(function ($user) {
    // ...
});
اکنون می توانیم این کد را بنویسیم:
$request->collect('users')->each(function ($user) {
    // ...
});

متد isList در کلاس Arr

این متد بررسی می کند که که همه کلید های آرایه عددی باشند و ترتیب در آن ها رعایت شده باشد.

تبدیل مقدار های دیتابیس به enum

cast جدیدی با نام enum اضافه شده است که با کمک آن می توان یک enum را در دیتابیس ذخیره کرد و یا یک مقدار را به صورت enum از دیتابیس خواند. برای نمونه:

enum StringStatus: string
{
    case pending = 'pending';
    case done = 'done';

}

enum IntegerStatus: int
{
    case pending = 1;
    case done = 2;

}

// Model
class EloquentModelEnumCastingTestModel extends Model
{
    public $timestamps = false;
    protected $guarded = [];
    protected $table = 'enum_casts';

    public $casts = [
        'string_status' => StringStatus::class,
        'integer_status' => IntegerStatus::class,
    ];

}

قانون ارزیابی enum

با استفاده از این قانون می توانیم مطمئن شویم که مقدار یک فیلد برابر با یکی از case های enum داده شده است.

استفاده از callback هنگام احراز هویت

می توانیم داخل آرایه ای که به عنوان مشخصات به متد attempt کلاس Auth می دهیم، یک callback هم بنویسیم. برای نمونه:

Auth::attempt([
    'email' => 'john@doe.com',
    function ($builder) {
        $builder->where('subscription_expires_at', '<', now());
    }

]);

متد can در کلاس Route

برای مشخص کردن کلاس authorize کننده درخواست می توانیم از متد can اضافه شده به کلاس Route استفاده کنیم. پیش از این کدی مانند زیر می نوشتیم:

Route::put('/post/{post}', function (Post $post) {
    

})->middleware('can:update,post');
اکنون کد زیر را می نویسیم:
Route::put('/post/{post}', function (Post $post) {
    //
})->can('update', 'post');

شیوه جدید تعریف mutator ها و accessor ها

در نسخه های قبلی برای تعریف mutator و accessor باید دو متد به شکل زیر تعریف می کردیم:

public function setTitleAttribute($value)

{
    $this->attributes['title'] = strtolower($value);

}public function getTitleAttribute($value)

{
    $this->attributes['title'] = strtolower($value);

}

اکنون می توانیم متدی مانند زیر تعریف کنیم:

protected function title(): Attribute
{
    return new Attribute(
        get: fn ($value) => strtoupper($value),
        set: fn ($value) => strtolower($value),
    );

}

متد date در کلاس Request

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

if ($date = $request->input('when')) {
    $date = Carbon::parse($datetime);

}

و اکنون کد زیر را می نویسیم:

$date = $request->date('when');

متد mergeIfMissing در کلاس Request

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

if ($request->missing('boolean_setting')) {
    $request->merge(['boolean_setting' => 0]);

}
اکنون کد زیر را می نویسیم:
$request->mergeIfMissing(['boolean_setting' => 0])

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

متد controller به کلاس Route اضافه شده است که می توان با استفاده از آن یک گروه از مسیر ها را بر اساس کنترلر تعریف کرد و هنگام تعریف مسیر های آن گروه تنها متد کنترلر را نوشت. برای نمونه:

Route::controller(PlacementController::class)
    ->prefix('placements')
    ->as('placements.')
    ->group(function () {
        Route::get('', 'index')->name('index');
        Route::get('/bills', 'bills')->name('bills');
        Route::get('/bills/{bill}/invoice/pdf', 'invoice')->name('pdf.invoice');
    });

استفاده از قابلیت Full-Text Search برای MySQL و PostgreSQL

هنگام تعریف ستون های دیتابیس در مایگریشن ها می توانیم ستون هایی را به صورت full-text ایندکس کنیم. همچنین کوئری مخصوص این index را استفاده کنیم.

Schema::create('articles', function (Blueprint $table) {
    $table->id('id');
    $table->string('title', 200);
    $table->text('body');
    $table->fulltext(['title', 'body']);

});

$articles = DB::table('articles')
    ->whereFulltext(['title', 'body'], 'database')
    ->get();

$articles = DB::table('articles')
    ->whereFulltext(['title', 'body'], 'database', ['mode' => 'boolean'])
    ->get();

$articles = DB::table('articles')
    ->whereFulltext(['title', 'body'], 'database', ['expanded' => true])
    ->get();

 

از بهترین نوشته‌های کاربران سکان آکادمی در سکان پلاس


online-support-icon