آموزش مقدماتی لاراول – مفهوم تخصیص یکباره اطلاعات و scope

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

بیاید یک ویژگی رو فعلا برای آهنگهامون درنظر بگیریم و اونم منتشر شدن یا نشدن یک آهنگ هست. این مورد رو بیشتر توی سرویسای ایمیلی و داک‌نویسی آنلاین میبینیم، اما اینجا ما هدفمون آموزشه!!!

خب برای اینکار به migration آهنگامون برمی‌گردیم و یک خاصیت به مدلمون اضافه می‌کنیم. و پس از یک مرحله rollback مجددا با اجرا دستور php artisan migrate جدول songs رو بار دیگه می‌سازیم. البته که ما در اینجا توی محیط توسعه هستیم ولی در پروژهای واقعی مخصوصا محصول نهایی هیچ وقت rollback نداریم و برای هر تغییری باید یک migration بسازید.

Schema::create('songs', function (Blueprint $table) {
    $table->bigIncrements('id');
    $table->string('name');
    $table->boolean('publish');
    $table->timestamps();
});
Image for post
Image for post

همانطوری که تصویر بالا نشون میده دستور rollback یکسری گزینه داره که مهم‌ترینش step هست که با کمک اون می‌تونیم تعداد دفعات یا گام‌هایی که migrateهامون میخوایم برگردند رو تعیین کنیم. اگر وارد دیتابیس هم بشین یک جدول بنام migrations وجود داره که migrateهای مارو دنبال میکنه.

Image for post
Image for post

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

Image for post
Image for post

فقط ی نکته‌ای الان هر وقت migrate خودمون رو rollback کنیم کل جدول‌ها پاک میشن خیلی جالب نیس. میتونید برای حل این مشکل با tinker یا ابزارهای ویرایشی دیتابیس وارد جدول migrations بشیم و مقدار batch رو مدیریت کنید. من برای مثال خودمون لازم میدونم که مقدار batch برگردونم به حالت قبلی که در تصویر بالا مشخصه!

خیلی خب برگردیم ابتدا سراغ فرم خودمون و ابتدا یک فیلد برای خاصیت جدید اضافه می‌کنیم:

 <div class="col-sm-5 mb-3">
    <label for="publish" class="form-label">وضعیت آهنگ</label>
     <select class="form-select" id="publish">
         <option selected>انتخاب وضعیت</option>
         <option value="0">عدم انتشار</option>
         <option value="1">انتشار</option>
    </select>                             
 </div>

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

public function store()
{
    \request()->validate([
        'name' => 'required|min:3|max:120',
        'publish' => 'required'
    ]);

    $song = new Song();
    $song->name = \request('name');
    $song->publish = \request('publish');
    $song->save();

    return back();
}

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

یکی از این موارد که خوانایی کد ما کمک میکنه mass assignment هست. 

پیش از شروع اجازه بدید به خروجی تابع validate دقت کنیم:

Image for post
Image for post

نکته بسیار مهم تابع validate اینه که این تابع تنها آرایه‌ای از اطلاعات رو میده که قوانینش رو تعریف کردید. یعنی حتی اگه کاربر بخواد فیلدی رو اینجکت شده به سرور ما ارسال کنه validate تنها اطلاعاتی که ما تعریف کردیم برمی‌گردونه!

خب اجازه بدید برگردیم سر وقت مدلمون! همانطور که گفته شد تمامی مدل‌هایی که تولید می‌کنیم از کلاس model ارث می‌برن که توابعی کمکی و خاصیت‌های مفید زیادی داره.  یکی از این توابع تابع create هست که اگر اطلاعات مورد نیاز برای ایجاد یک رکورد رو بصورت یک آرایه بهون بدیم یک ردیف در دیتابیس برا ما ایجاد می‌کنه:

public function store()
{
    $data = \request()->validate([
        'name' => 'required|min:3|max:120',
        'publish' => 'required'
    ]);

    Song::create($data);

    return back();
}

میبینید! خیلی خواناتر شد. بذارید یک درخواست بدیم و نتیجه رو ببینیم

Image for post
Image for post

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

یکی مقدار fillable که یک آرایه از مواردی هست که می‌خواید در mass assignment مقدار دهی کنید و یا ‍‍guarded که برعکس fillable عمل میکنه و مواردی که نمیخواید رو میتونید در این خاصیت تعریف کنید. بطور خاص در مثال ما یکی از موارد زیر رو باید در مدل وارد کنیم:

    protected $guarded = [];
    protected $fillable = ['name', 'publish'];

خب حالا بیایید یک نمایش برای صفحه آخرین آهنگها ایجاد کنیم تا بتونیم نتیجه کار رو بهتر ببینیم. برای اینکار ابتدا آهنگهایی که منتشر هستند را از غیرمنتشرها جدا کنیم. بنابراین وارد تابع list در کنترلر میشیم و تابع بسیار مهم where که در کوئری‌ها زیاد هم استفاده میشه رو می‌خواییم تعریف کنیم. این تابع شبیه یک فیلتر عمل میکنه به این صورت که پارامتر اول نام کلیدی هست که می‌خواهیم فیلتر کنیم و دومین پارامتر عملگرهای مقایسه‌ای هس و نهایتا آخرین پارامتر هم مقدار مورد نظر ماست:

    public function list()
    {
        $publishSongs = Song::where('publish', '=', 1)->get();
        $unPublishSongs = Song::where('publish', '=', 0)->get();

        return view('songs.index', [
            'publishSongs' => $publishSongs,
            'unPublishSongs' => $unPublishSongs
        ]);
    }

این نکته هم در نظر داشته باشید که تابع get برای اعمال خروجی‌ها بعد از where لازم هست.

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

Image for post
Image for post

حالا بیایید کمی تابع list داخل کنترلر رو هم تمیز کنیم.

یکی از قابلیت‌هایی که در مدل‌های eloquent وجود داره تعریف scope هست این امکان کمی کوئری‌های شمارو ساده‌تر میکنه. خب برای تعریف یک scope شما فقط کافیه وارد مدلتون بشید و یک تابع که اسمش با scope شروع میشه رو برای کوئری‌ که میخواید بنویسید.

برای نمونه من میخوام بجای where در کوئری‌ آهنگ‌های منتشر شده و نشده از  scope استفاده کنم:

Image for post
Image for post

که نهایتا تابع list بصورت زیر خواهد شد:

public function list()
{
    $publishSongs = Song::published()->get();
    $unPublishSongs = Song::unpublished()->get();

    return view('songs.index', [
        'publishSongs' => $publishSongs,
        'unPublishSongs' => $unPublishSongs
    ]);
}

به همین راحتی!

حالا یک تابع دیگه هم وجود داره بنام compact برای ارسال که عجیبه یکم:

Image for post
Image for post

این تابع کمکی هم نام متغییرها رو میگیره و به آرایه تبدیل میکنه!


خب این مقاله هم همینجا به اتمام میرسه امیدوارم از این مطلب لذت برده باشید