6 راز Eloquent لاراول برای بهبود کد

PHP, لاراول5 اردیبهشت 1399

قطعا میدونین که Eloquent ، به صورت پیش فرض ORM فریم ورک لاراول هست. Eloquent، الگوی Active-Record رو پیاده سازی می کنه و راه راحتی رو برای تعامل با دیتا بیس فراهم می آره. هریک از مدل ها، نشان دهنده یک جدول در دیتابیس شما هست که میتونین باهاشون کار کنین. در مطلب امروز قلاب قراره رازهایی رو براتون آشکار کنم از متدها و ویژگی هایی که میتونه به بهبود کدهاتون کمکتون کنه! پس میریم که داشته باشیم💪

1. Snake Attributes در Eloquent

Snake Attributes یکی از جذاب ترین موضوعاتیه که الان قراره در موردش حرف بزنیم. اجازه بدین اول کار بریم یه نگاهی به کد بندازیم ببینیم حرف حسابش چیه😉

/**
 * Indicates whether attributes are snake cased on arrays.
 *
 * @var bool
 */
public static $snakeAttributes = true;

اشتباه بسیار رایجی که اکثر افراد مرتکبش میشن این هست که از این property به عنوان راهی برای تغییر روش دسترسی به property ها استفاده میکنن. بسیاری افراد معتقد هستن که اگر این property رو تغییر بدن، به راحتی میتونن با استفاده از camel-case annotation به attribute ها دسترسی پیدا کنن. در حالی که اینجوری نیست. اکیدا توصیه می کنم که از این روش استفاده نکنین! تنها کاربرد snake attribute اینه که زمانی که خروجی به شکل آرایه هست، مشخص کنه که attribute ها از نوع camel هستند یا snake.

2. pagination در Eloquent

خیلی خوش شانسین اگه از Laravel’s Eloquent ORM استفاده میکنین! چون که یه راه خیلی راحت برای paginate کردن نتایج فراهم آره. ممکنه تیکه کد زیر به چشمتون آشنا بیاد:

$comments = Comment::paginate(20);

با استفاده از این متد، میتونین کامنت های یک سایت رو با 20 آیتم در هر صحفه paginate کنین. شما میتونین با تغییر داددن اون مقدار (یعنی عدد 20) هر تعداد آیتمی که مد نظر خودتون هست در هر صفحه نمایش بدین. اگر هیچ عددی نذارین، مقدار پیش فرض خودش روی 15 آیتم در هر صفحه هستش.

حاالا تصور کنین که میخواین کامنت ها رو در چند محل مختلف از سایتتون نمایش بدین و در هر محل هم دقیقا 30 تا کامنت نمایش داده بشه. خوب یه مقدار آزار دهنده س که هربار بخواین پارامتر 30 رو پاس کنین. پس بهتره کلا مقدار پیش فرض رو به 30 تغییر بدین. به شکل زیر:

protected $perPage = 30;

3. اضافه کردن مقادیر دلخواه به مدل ها در Eloquent

Eloquent یه ویژگی خفن داره که بهش میگن: Accessor. این ویژگی بهتون اجازه میده فیلدهای دلخواهتون رو به مدل ها اضافه کنین (فیلدهایی که در اون مدل یا table وجود ندارن). فرقی هم نداره که از مقادیر موجود استفاده کنین یا فیلدهای کاملا جدیدی تعریف کنین. اینجا یه مثال واستون آوردم که نشون میده Accessor چطور کار میکنه. توی کد زیر، یه مدلی داریم که اسمش user هست:

function getFullNameAttribute() {
    return sprintf('%s %s', $this->first_name, $this->last_name);
}

حالا به شکلی که در زیر میبینید به یک full-name attribute در مدل دسترسی خواهید داشت:

User::latest()->first()->full_name;

مشکلی که حالا وجود داره اینه که اگر یک شی، مثلا یک collection، رو برگردونید (return کنید)، این attribute به مدل کاربر append نمیشه! پس باید appends$ رو به مدل user اضافه کنین. این دستور،باعث میشه بتونیم آرایه ای از یک یا چند فیلد رو بپذیریم که از این به بعد همه شون به صورت خودکار append میشن. پس به شکل زیر عمل میکنیم:

protected $appends = ['full_name'];

4. Mutator ها برای ستون هایی که وجود ندارند.

Mutator ها دقیقا نقطه مقابل accessor ها هستن! می تونین با استفاده از Mutator ها کارهای خیلی باحالی بکنین. به عنوان مثال میتونین برای convert کردن ورودی ها ازش استفاده کنین. میخوام یه چیز جالب بهتون نشون بدم. فرض کنین که میخواین یه نوع دوره زمانی (time period) رو ذخیره کنین. کاری که معمولا انجام میدیم اینه که بر حسب کوچکترین واحد زمانی ممکن این ذخیره سازی رو انجام میدیم. اگه از دیدگاه UX بهش نگاه کنیم، هیچ کاربری قطعا خوشش نمیاد زمان رو بر حسب ثانیه وارد کنه، یه جاهایی ممکنه بر اساس دقیقه نیاز باشه زمان رو وارد کنه و یه جاهایی بر حسب ساعت (به نوع کار و میزان نیاز به دقت در زمان بستگی داره). حالا بهتون میگم همه ی این مشکلات رو چجوری خیلی راحت حل کنین.

class Video extends Model
{
    public function setDurationInMinutesAttribute($value)
    {
        $this->attributes['duration_in_seconds'] = $value * 60;
    }

    public function setDurationInHoursAttribute($value)
    {
        $this->attributes['duration_in_seconds'] = $value * 60 * 60;
    }
}

حالا این یعنی چی؟ این معنیش اینه که شما توی مدلتون ستونی به اسم duration_in_minutes ندارین؛ اما میتونین ازش استفاده کنین. در اصل ستونی که در پس زمینه به روز رسانی میشه، duration_in_seconds هست. برای duration_in_hours هم دقیقا به همین شکلیه که گفتم. به عنوان نمونه می تونین این نتیجه رو در منطق زیر ببینید:

class AnyController
{
    public function store()
    {
        $video->update([
            'title' => request('title'),
            'duration_in_minutes' => request('duration_in_minutes'),
        ]);
    }
}

این کار باعث میشه که در زمان محاسباتی در کنترلر صرفه جویی بشه. به سادگی از یک ستون non-existent استفاده کنین و از یک Mutator برای نگاشت دادنش به ستون مربوطه استفاده کنین و در عین حال محاسبات مربوطه هم انجام بدین.

5. بارگیری مشتاقانه (ٍEager loading) با استفاده از $with در Eloquent

بیاین یکمی در مورد روابط (relation) صحبت کنیم. به صورت پیش فرض، لاراول از بارگیری تنبل (lazy loading ) استفاده میکنه. اما از دیدگاه رابطه ای این چه معنی میده؟ نکته ی خوبش اینه که بارگیری تنبل، باعث صرفه جویی در مصرف حافظه میشه چون که نیاز نداریم تمام داده ها رو نگه داریم و داده رو زمانی لود میکنیم که بهش نیاز داشته باشیم. به نمونه کد زیر توجه کنین لطفا:

$comments = Comment::all();
foreach ($comments as $comment) {
    echo $comment->user->name;
}

در مثال بالا همه ی comment ها رو دریافت می کنیم. بعد یه حلقه داریم که روی comment ها میچرخه و نام کاربری هرکدوم رو نمایش میده. کد کار میکنه اما ما رو با یه مشکلی مواجه میکنه. بارگیری تنبل باعث میشه که پرس و جو (query) برای گرفتن نام کاربری فقط زمانی اجرا بشه که ما می خوایم نتیجه رو در خروجی چاپ کنیم.

باید بگم که به مسئله ی با پیچیدگی N+1 خوش اومدین! حالا چرا N+1؟ خوب N تعداد کامنت ها هست و 1 هم برای پرس و جوی که برای گرفتن همه ی کامنت ها باید انجام بشه. به عنوان مثال اگر 500 تا کامنت داشته باشیم، باید اول یک پرس و جو برای گرفتن اون 500 کامنت انجام بشه و بعدش یک پرس و جو برای پیدا کردن کاربر مورد نظر. بنابراین 500+1 پرس و جو باید انجام بشه. معنیش اینه که هرچقدر که تعداد کامنت ها افزایش پیدا کنه، تعداد پرس و جو ها هم به همون نسبت افزایش پیدا میکنه.

برای جلوگیری از به وجود اومدن مسئله ای با پیچیدگی بالا، راه حلی وجود داره که بهش میگن بارگیری مشتاقانه.

$comments = Comment::with('user')->get();
foreach ($comments as $comment) {
    echo $comment->user->name;
}

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

SELECT id, user_id, body FROM comments;
SELECT name FROM users WHERE user_id IN (1,2,3,4,5...);

فرق نداره که 10 تا کامنت داشته باشیم، 500 تا داشته باشیم، 1000 تا یا هر عدد دیگه. در هر صورت دوتا پرس و جوی بالا کفایت میکنه.

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

protected $with = [];

بنابراین از این بعد میتونین به سادگی با استفاده از property محافظت شده ی زیر بر روی مدل کامنت در هر لحظه ای به صورت خودکار کاربران رو بارگیری کنین:

 $with = ['user'];

با استفاده از بارگیری مشتاقانه، به امکانات بسیار دیگه ای هم میتونیم دست پیدا کنیم: بارگیری ستون های خاص، بارگیری مشتاقانه آشیانه ای (nested eager loading)، بارگیری مشتاقانه چندگانه (multiple eager loading) و … . برای اطلاعات بیشتر میتونین به مستندات لاراول مراجعه کنین.

6. Model keys در Eloquent

گاهی ممکنه نیاز داشته باشیم همه ی ID ها رو به یکباره با یک پرس و جو واکشی کنیم. در چینن مواقعی اکثر برنامه نویسا به روش زیر عمل میکنن:

User::all()->pluck('id');

خب تا اینجا این عالی کار میکنه. اما حالا سعی کنین که یک collection رو بگیرین. به منظور گرفتن یک آرایه، باید مجددا این کارو برای متد toArray() انجام بدین.

User::all()->pluck('id')->toArray();

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

User::all()->modelKeys();

متد بالا یک آرایه رو برمیگردونه. در مثال مورد نظر ما، منظور ID کاربران هست. درک این نکته مهم هست که این متد همیشه ID رو برنمیگردونه. همونطور که از اسمش مشخصه تمام کلیدهای اصلی (primary keys) رو به شکل یک آرایه برمیگردونه. میتونین توی مدلتون هرچیزی رو به عنوان کلید اصلی تعریف کنین. به صورت پیش فرض ID کلید اصلی هست.

protected $primaryKey = 'id';

خب اینم از قلاب امروز، امیدوارم که مفید بوده باشه و بتونین باهاش ماهی (🐟) چاق و چله شکار کنین! با قلاب های بعدی ما همراه باشین…

Please Post Your Comments & Reviews

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

*