فصل ۱۲-۳: ساخت انواع کوئری برای روابط در لاراول

06 آبان 1397
درسنامه درس 17 از سری لاراول
Laravel-Main-relationship-queries

با مطالعه‌ی دو فصل ۱۲-۱ و ۱۲-۲ به انواع روابط جداول در دیتابیس تسلط پیدا کردید. حال می‌توانید با دقت بسیار روابط بین جدول ها را با توجه به کلیدهای خارجی آنها تعیین کرده و یک دیتابیس منظم و دقیق با ساختار مشخص را تولید کنید. در ادامه به توضیح برخی کوئری‌ها در این روابط می‌پردازیم و سپس تفاوت بین متدهای رابطه‌ای و ویژگی‌های پویا را اشاره خواهیم کرد. در نهایت روش‌های بهینه برای دستیابی به اطلاعات را متناسب با ابزار Eager Loading مطرح می‌کنیم.

ساخت کوئری روابط

با مطالعه‌ی مباحث گذشته دریافتیم که برای دسترسی به مقادیر موجود در هر یک از جداول می‌توان از متدهایی که درون مدل آنها تعریف می‌شود استفاده کرد. مثلا متد posts در  مدل User تعریف شده و می‌توان تمام پست‌هایی که یک کاربر نوشته است را استخراج کرد. علاوه بر این می‌توان انواع روابط Eloquent را با استفاده از Query Builder ها ترکیب کرد تا به اطلاعات دقیق تر و فیلترشده تری از دیتابیس توسط فرمان‌های SQL دست پیدا کنیم. فقط قبل از ورود به این مبحث خواهشمندیم مقاله‌ی زیر را که مرتبط با انواع Query Builder ها است مطالعه بفرمایید:

بسیار عالی. پس از آشنایی دقیق با انواع کوئری ها این آموزش را با ارائه یک مثال ادامه خواهیم داد.

فرض کنید یک وبلاگ در اختیار داریم که تعداد زیادی پست دارد و هر پست به یک کاربر اختصاص پیدا می‌کند. بنابراین مدل‌هایی به نام‌های User.php و Post.php داریم. حال به نوشتن روابط این دو مدل می‌پردازیم. نوع رابطه به صورت یک به چند است. بنابراین داریم:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * Get all of the posts for the user.
     */
    public function posts()
    {
        return $this->hasMany('App\Post');
    }
}

همین رابطه را نیز در مدل Post اجرا می‌کنیم و از متد belongsTo استفاده خواهیم کرد. بنابراین در مدل User.php داریم:

 

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    /**
     * Get all of the posts for the user.
     */
    public function user()
    {
        return $this->belongsTo('App\User');
    }
}

می‌توان با استفاده از رابطه‌ی posts که در مدل User و اضافه کردن قیدهای متفاوت به اطلاعات مشخصی از یک کوئری دست پیدا کنیم:

$user = App\User::find(1);

$user->posts()->where('active', 1)->get();

همینطور که ملاحظه کردید می‌توان تمام Query Builder ها را ترکیب کرد و به نتایج مفیدتری دست پیدا کرد.

متد has

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

// Retrieve all posts that have at least one comment...
$posts = App\Post::has('comments')->get();

همچنین می‌توان از عملگرهای مختلف و شمارنده‌ها برای شخصی‌سازی کوئری‌ها استفاده کرد:

// Retrieve all posts that have three or more comments...
$posts = Post::has('comments', '>=', 3)->get();

برای عبارات has تو در تو نیز می‌توان از علامت دات (.) استفاده کرد. مثلا برای دستیابی که تمام پست‌هایی که حداقل یک نظر و رأی دارند به صورت زیر عمل می‌کنیم:

// Retrieve all posts that have at least one comment with votes...
$posts = Post::has('comments.votes')->get();

متد whereHas و orWhereHas

فرض کنید می‌خواهید در یک پست به نظراتی که متن آنها از عبارت foo تشکیل‌شده است دست پیدا کنید. در این حالت می‌توان از دستور زیر استفاده کرد:

// Retrieve all posts with at least one comment containing words like foo%
$posts = Post::whereHas('comments', function ($query) {
    $query->where('content', 'like', 'foo%');
})->get();

متد dosentHave

این متد دقیقا معکوس has عمل می‌کند یعنی بررسی می‌کند که آیا رابطه‌ای وجود ندارد؟ در صورتیکه وجود نداشته باشد مقادیر را بر می‌گرداند. به عنوان مثال در عبارت زیر تمام پست‌هایی که هیچگونه نظری در آنها ثبت نشده است بازیابی خواهند شد:

$posts = App\Post::doesntHave('comments')->get();

متد whereDosentHave

از این متد تمام نظرات یک پست را که شامل یک عبارت خاص نیستند به نمایش می‌گذارد:

$posts = Post::whereDoesntHave('comments', function ($query) {
    $query->where('content', 'like', 'foo%');
})->get();

متد withCount

برای دسترسی به تعداد رکوردهایی که با مدل ارتباط دارند می‌توان از این متد استفاده کرد. برای دسترسی به این مقدار باید از relation_count استفاده کرد. مثلا برای دستیابی به تعداد نظرات یک پست ابتدا باید از متد withCount نتایج را در متغییر ذخیره کرده و سپس با ویژگی comments_count تعداد آنها را باز گردانیم:

$posts = App\Post::withCount('comments')->get();

foreach ($posts as $post) {
    echo $post->comments_count;
}

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

$posts = Post::withCount(['votes', 'comments' => function ($query) {
    $query->where('content', 'like', 'foo%');
}])->get();

echo $posts[0]->votes_count;
echo $posts[0]->comments_count;

از طرفی می‌توان یک نام مستعار برای نتایج شمارش شده مشخص کرد. در مثال زیر برای نظرات از عبارت comments و برای نظرات تایید شده از دستور comments AS pending_comments بهره برده‌ایم:

$posts = Post::withCount([
    'comments',
    'comments AS pending_comments' => function ($query) {
        $query->where('approved', false);
    }
])->get();

echo $posts[0]->comments_count;

echo $posts[0]->pending_comments_count;

متدهای رابطه و ویژگی‌های پویا

اگر نیازی به استفاده از Query Builderها در روابط ندارید. می‌توان با استفاده از ویژگی‌ها و متدهای یک رابطه به نتایج مشابه دست پیدا کرد. همان مثال پست و کاربر را در وبلاگ درنظر بگیرید. برای دستیابی به تمام پستهای یک کاربر می‌توان به صورت زیر عمل کرد:

$user = App\User::find(1);

foreach ($user->posts as $post) {
    //
}

همانطور که ملاحظه کردید از ویژگی پویا posts استفاده کرده و تمام اطلاعات را بازگردانی کردیم. اما باید توجه داشته باشید که Dynamic Properties یا ویژگیهای پویا lazy loading (بارگذاری تنبل) هستند. برای جلوگیری از این موضوع توسعه‌دهندگان از روش eager loading (باگذاری مشتاق) برای بارگذاری روابط قبل از اجرای آنها، اقدام می‌کنند. استفاده از تکنیک Eager Loading باعث می‌شود حجم کوئری‌های SQL شما کاهش پیدا کند. در ادامه به توضیح این مبحث می‌پردازیم.

بارگذاری تنبل (Lazy Loading) و بارگذاری مشتاق یا (Eager Loading)

از این روش بارگذاری برای خواندن کوئری‌ها تا یک مرحله خاص استفاده می‌شود. به عنوان مثال درنظر بگیرید جدولی به نام posts دارید که  تمام عناوین و توضیحات پست‌ها در آن قرار گرفته است که برای دسترسی به آنها یک کوئری نیاز است. حال فرض کنید که می‌خواهید به اطلاعات جزئی‌تری مثلا نظرات (comments) یک پست دست پیدا کنید. در این صورت با این کوئری نمی‌توان نظرات پست را بازیابی کرد و همواره باید یک کوئری جدید نوشته شود. به این روش فراخوانی اطلاعات بارگذاری تنبل یا Lazy Loading گفته می‌شود (یعنی بارگذاری اطلاعات کلی). اما روش دیگری به نام بارگذاری مشتاق یا Eager Loading وجود دارد که با استفاده از آن می‌توان به جزئی‌ترین اطلاعات یک جدول تنها و تنها با یک کوئری دست پیدا کرد.

از بارگذاری تنبل یا Lazy Loading برای دستیابی به اطلاعات کلی و حجیم استفاده می‌شود که در آنها جزئیات زیاد ضروری نیست. اما از بارگذاری مشتاق یا Eager Loading هنگامیکه دستیابی به جزئیات یک جدول مورد نظر است، بکار گرفته خواهد شد.

برای مثال فرض کنید می‌خواهیم به تمام نویسنده‌های کتابهای موجود در پایگاه داده دست پیدا کنیم. بنابراین مدل Book با مدل Author رابطه یک به چند خواهد داشت. یعنی به ازای هر نویسنده چندین کتاب وجود دارد. بنابراین با اجرای دستور زیر به تمام کتابها دست پیدا کرده و در نهایت آن به اسامی نویسنده ها می‌رسیم:

$books = App\Book::all();

foreach ($books as $book) {
    echo $book->author->name;
}

این حلقه از یک کوئری برای دستیابی به تمام کتاب‌های موجود در جدول books استفاده کرده و سپس با کوئری‌های دیگر به نام تمام نویسندگان این کتاب دست پیدا می‌کند .با اجرای دستور فوق اگر ۲۵ کتاب در پایگاه داده داشته باشیم. ۲۶ بار این یک کوئری اجرا شده که اولین کوئری برای لود کردن کتاب‌ها و ۲۵ تای دیگر برای نمایش نویسنده‌های هر کتاب می‌باشد. می‌توان نحوه‌ی کوئری نمایش اطلاعات فوق را با استفاده از Eager Loading به ۲ کوئری کلی کاهش دهیم. یعنی به جای ۲۶ کوئری تنها ۲ کوئری اجرا شود:

برای استفاده از این تکنیک (بارگذاری مشتاق) بهتر است همواره از متد with استفاده کنید:

$books = App\Book::with('author')->get();

foreach ($books as $book) {
    echo $book->author->name;
}

بنابراین در طول برنامه تنها دو کوئری زیر اتفاق می‌افتد:

select * from books

select * from authors where id in (1, 2, 3, 4, 5, ...)

استفاده از Eager Loading در چندین رابطه

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

$books = App\Book::with('author', 'publisher')->get();

بارگذاری مشتاق تودرتو

برای روابط تودرتو در بارگذاری مشتاق همواره می‌توان از علامت دات (.) بهره برد. به عنوان مثال برای دستیابی به تمام نویسنده‌های کتابها و همه‌ی اطلاعات تماس نویسندگان می‌توان از دستور زیر استفاده کرد:

$books = App\Book::with('author.contacts')->get();

دستور بالا کتاب‌هایی که نویسنده دارند و آن نویسنده‌ها اطلاعات تماس مشخصی دارند، در متغییر books ذخیره می‌کند.

قیود در بارگذاری Eager Loading

با استفاده از قید‌ها می‌توان نتایج مناسب‌تری در هر بارگذاری بدست آورد. برای اینکار کافی‌ست از آرایه‌ها درون متد with استفاده کرد:

$users = App\User::with(['posts' => function ($query) {
    $query->where('title', 'like', '%first%');
}])->get();

در این مثال، Eloquent تنها پست‌هایی که دارای ستون عنوان هستند و متن آن شامل کلمه‌ی first است، بازیابی می‌کند. البته می‌توان از سایر دستورهای سازنده‌ی کوئری یا Query Buildersها نیز استفاده کرد. برای مثال در نمونه‌ی زیر با استفاده از بارگذاری مشتاق یا Eager Loading تمام نویسنده‌هایی که پست دارند را بر حسب زمان ایجاد پست‌ها، مرتب کرده‌ایم:

$users = App\User::with(['posts' => function ($query) {
    $query->orderBy('created_at', 'desc');
}])->get();

بارگذاری مشتاق تنبل (Lazy Eager Loading)

گاهی اوقات نیاز است علاوه بر بارگذاری مشتاق از بارگذاری تنبل نیز استفاده کنیم. یعنی ابتدا تمام اطلاعات موجود در یک جدول را بارگذاری کنیم سپس متناسب با شرایطی دینامیک و پویایی که برای مدل‌ها درنظر می‌گیریم بارگذاری مشتاق را اجرا کنیم. به فرض مثال در نمونه‌ی زیر ابتدا تمام کتابها را فراخوانی کرده و سپس متناسب با دستور شرطی if یک بارگذاری مشتاق با متد load برای فیلدهای author و publish ایجاد می‌کنیم:

$books = App\Book::all();

if ($someCondition) {
    $books->load('author', 'publisher');
}

همچنین همانند قبل می‌توان از سایر دستورهای سازنده کوئری استفاده کرد. به عنوان مثال برای مرتب کردن تمام نویسنده‌های کتابها بر اساس تاریخ انتشار آن می‌توان به صورت زیر عمل کرد:

$books->load(['author' => function ($query) {
    $query->orderBy('published_date', 'asc');
}]);

بسیار عالی! به شما تبریک می‌گوییم با مطالعه این بخش از فصل ۱۲ توانایی ساخت انواع کوئری و استخراج داده‌ها از جداول دارای رابطه، پیدا کردید. در فصل ۱۲-۴ به چگونگی ذخیره داده‌های دارای رابطه در Eloquent خواهیم پرداخت. با ما همراه باشید.

تمام فصل‌های سری ترتیبی که روکسو برای مطالعه‌ی دروس سری لاراول توصیه می‌کند:
نویسنده شوید
دیدگاه‌های شما

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