تعریف رابطه‌ی جدول‌ها در Modelها

The Relationship of Tables in Models

25 بهمن 1399
Laravel 7.0: تعریف رابطه ی جدول ها در Model ها (قسمت 21)

در قسمت قبل موفق شدیم که با استفاده از لایه پایگاه داده لاراول، در فایل های migration خود با استفاده از یک foreign key دو جدول را به هم متصل کنیم اما هنوز چیزی برای Model ایجاد شده ننوشته ایم. در حالت عادی (دور از فضای لاراول) زمانی که از PHP و MySQL برای چنین کاری استفاده می کنیم، باید دو مرحله را طی کنیم:

  • در مرحله اول با استفاده از کدهای SQL یا با استفاده از PHPMyAdmin یک پایگاه داده می ساختیم و جدول های خودمان را با key های دلخواه به هم متصل می کردیم. این قسمت در سمت پایگاه داده انجام می شد و روابط جدول ها را مشخص می کرد.
  • در مرحله دوم باید حواسمان به نوشتن کوئری هایمان بود تا با استفاده از این foreign key هر دو جدول را هدف بگیریم. به طور مثال از دستورات join استفاده می کردیم (امیدوارم با MySQL آشنا باشید).

در لاراول هم باید همین منطق را پیاده کنیم اما به جای نوشتن دستورات SQL ساده از Eloquent استفاده می کنیم (مانند جلسه قبل). با این تفاسیر در این جلسه نوبت به پیاده سازی مرحله دوم یا نوشتن Model ما می رسد. نکته مهم در تمام این عملیات این است:

هر کاربر یک پروفایل دارد و هر پروفایل متعلق به یک کاربر است (رابطه یک به یک - One to One)

در روابط یک به یک دو کلیدواژه بسیار مهم داریم:

  • hasOne به معنی «دارد» یا «یک ... دارد»
  • belongsTo به معنی «متعلق است به ...»

در قدم اول به Profile.php یا همان فایل Model خود برای جدول profiles می رویم. ما می خواهیم بگوییم هر پروفایل به یک کاربر تعلق دارد بنابراین از belongsTo استفاده می کنیم:

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

من در اینجا یک متد به نام user تعریف کرده ام که خصوصیتی به نام belongsTo را برمی گرداند و به آن کلاس User را داده ام (این کلاس در model آن یعنی User.php قرار دارد). در ضمن class:: یکی از قابلیت های php است و ربطی به لاراول ندارد. این دستور به شما اجازه می دهد که مسیر دقیق یک کلاس ها در namespace های مختلف به دست بیاورید. اگر کد بالا را به صورت انگلیسی ساده و نه کد بخوانید می شود: this$ (اشاره به این کلاس و پروفایل کاربر) متعلق است به کلاس User (که یک model است).

البته نکته بسیار مهمی وجود دارد که باید به آن توجه کنید. نام این متد در اینجا بسیار مهم است. لاراول نام این متد را برداشته و پسوند id_ را به آن اضافه می کند تا نام ستون foreign key ما را تشخیص دهد. بنابراین نام متد (user) گرفته شده و user_id به عنوان نام foreign key ما شناخته می شود و اگر نام این متد را چیز دیگری گذاشته بودیم با خطا روبرو می شدیم. به همین دلیل است که من در جلسه قبل نام user_id را به عنوان ستون foreign key انتخاب کردم. اگر شما دوست دارید که نام این متد را چیز دیگری بگذارید و خودتان به صورت دستی به لاراول بگویید که نام foreign key چیست، باید آن را به عنوان آرگومان دوم به belongsTo پاس بدهید (البته من چنین کاری نمی کنم):

return $this->belongsTo(User::class, 'YOUR_FOREIGN_KEY');

تا اینجا کاری کرده ایم که مشخص شده است هر پروفایل متعلق به یک کاربر است بنابراین می توانیم بر اساس یک پروفایل، یک کاربر را پیدا کنیم اما هنوز برعکس آن را انجام نداده ایم. یعنی هنوز مشخص نکرده ایم که هر کاربر یک پروفایل دارد. اگر در برنامه خود نیازی به دریافت پروفایل بر اساس کاربر ندارید نیازی به ادامه نیست اما برنامه ما آن را لازم دارد بنابراین به فایل Model آن (User.php) رفته و در انتهای کلاس متد خودمان را اضافه می کنیم:

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

public function profile()
{
    return $this->hasOne(Profile::class);
}
// بقیه کدها //

من نام این متد را profile گذاشته ام و باز هم به صورت پیش فرض لاراول در نظر می گیرد که ستون user_id همان foreign key ما است. اگر شما می خواهید به صورت دستی آن را مشخص کنید مثل قبل باید آرگومان دوم را به آن پاس بدهید.

در مرحله بعدی باید یک پروفایل را به صورت دستی به پایگاه داده اضافه کنیم (در آینده این کار را به صورت دستی انجام نمی دهیم اما فعلا برای یادگیری ایرادی ندارد). برای این کار یا از PHPMyAdmin استفاده کنید یا از Tinker. روش PHPMyAdmin بسیار ساده است و فقط باید یک ردیف در جدول profiles اضافه کنید بنابراین آن را توضیح نمی دهیم اما اگر دوست دارید روش Tinker را بدانید، کدهایش را برایتان قرار می دهیم. در ترمینال خود می گوییم:

php artisan tinker
$profile = new \App\Profile();
$profile->title = 'Cool Title';
$profile->description = 'description';
$profile->user_id = 1;
$profile->save();

اگر یادتان باشد در جلسات قبل که در مورد mass assignment صحبت کردیم به شما گفتم که روش دیگر اضافه کردن کاربر به پایگاه داده و استفاده از متد ()save چطور است. این همان روش است.

با اجرا کد بالا خطا می گیرید. چرا؟ به کد زیر در فایل migration نگاه کنید:

$table->foreignId('user_id')->references('id')->on('users')->onDelete('cascade');

من onDelete را روی cascade گذاشته ام تا اگر ردیفی از جدول پدر (Users) حذف شد، ردیف متناظر با آن از جدول فرزند (profiles) نیز حذف شود چرا که اگر کاربری حذف شود قطعا پروفایلش هم باید حذف شود. مشکل اینجاست که برای onUpdate چیزی نگفته ایم و مقدار پیش فرض آن روی Restrict (غیر مجاز) است. یعنی چه؟ یعنی کسی اجازه ندارد foreign key را به صورت مستقیم ویرایش کند (ما در کد بالا profile->user_id$ را به صورت دستی تعریف کرده ایم که نوعی ویرایش محسوب می شود).

روش استاندارد کار در MySQL همین کار است که اجازه ویرایش foreign key ها را به صورت مستقیم ندهید مگر در موارد خاص که foreign key شما روی id تنظیم نشده است و الی آخر. بنابراین من قصد ندارم onUpdate را نیز روی cascade بگذارم اما این کدها را برایتان قرار دادم تا با آن آشنا شوید و اگر خواستید migration را دوباره انجام بدهید مشکلی نیست. باید کد migration را به شکل زیر تغییر دهید:

$table->foreignId('user_id')->references('id')->on('users')->onDelete('cascade')->onUpdate('cascade');

من شخصا ردیف جدید را از PHPMyAdmin اضافه کردم تا راحت باشم. حالا دوباره وارد Tinker شوید (php artisan tinker) و دستور زیر را اجرا کنید:

$profile->user

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

App\User {#3998
     id: 1,
     name: "Amir",
     email: "myEmail@email.com",
     username: "AmirZM",
     email_verified_at: null,
     created_at: "2020-06-26 06:38:31",
     updated_at: "2020-06-26 06:38:31",
   }

آیا متوجه شدید؟ ما profile$ را داشتیم و کاربر (user) را از آن پیدا کردیم! انگار که کاربر درون profile$ است! برای تست می توانیم کد زیر را اجرا کنیم:

$profile

با این کار نتیجه زیر را می گیریم:

App\Profile {#3065
     title: "Cool Title",
     description: "description",
     user_id: 1,
     updated_at: "2020-06-26 06:31:12",
     created_at: "2020-06-26 06:31:12",
     user: App\User {#3998
       id: 1,
       name: "Amir",
       email: "myEmail@email.com",
       username: "AmirZM",
       email_verified_at: null,
       created_at: "2020-06-26 06:38:31",
       updated_at: "2020-06-26 06:38:31",
     },
   }

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

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

با اجرای این دو کد نتیجه زیر را می گیریم:

=> App\Profile {#3999
     id: 2,
     user_id: 1,
     title: "Cool Title",
     description: "description",
     url: null,
     created_at: "2020-06-26 11:09:05",
     updated_at: "2020-06-26 11:09:05",
   }

ما مقداری برای url ننوشته بودیم بنابراین null است. امیدوارم این جلسه را درک کرده باشید.

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

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