در بخش اول این سری از آموزش درک پکیج ORM لاراول سه مفهوم رابطه ای رو بخوبی درک کردیم و پروژه محور پیش بردیم.
در بخش دوم و پایانی قصد دارم دو مفاهیم through و polymorphic رو شرح بدم و باهم کلی چیزا یاد بگیریم.
برای این قسمت هم، پروژه ابتدایی که بررسی کردیم رو جلو میبریم و روی همون کار میکنیم، اجازه بدید یه نگاه دیگه به دیاگرام هامون بندازیم :
همونطور که در بخش اول گفته شد، در پروژه ما برای شروع MVP میتونیم 7 جدول داشته باشیم که روال کار رو بصورت ساده برای یک پلتفرم مارکت پلیس انجام بدیم.
ضمن اینکه مقاله قسمت قبل رو پیشنهاد میکنم حتما مطالعه کنید.
حالا فرض کنیم ما بدون در نظر گرفتن فروشگاه های یه کاربر میخوایم همه محصولات متعلق به اون کاربر رو بگیریم و بهش در پنل نمایش بدیم، اینجاست که رابطه میانی به کمکون میاد و به ما این امکان رو میده تا بتونیم با استفاده از یک جدول رابط به جدول دیگه که هیچ ارتباطی (کلید خارجی) با جدول اصلیمون نداره دسترسی پیدا کنیم و اطلاعات اون رو بگیریم.
اینجا جدول اصلیمون users هست و جدول رابطمون shops هستش و ما قصد داریم از طریق جدول فروشگاه ها (shops) به جدول محصولات (products) به کاربر برسیم. در واقع میخوایم هر کاربری با هر تعداد فروشگاهی که داره همه محصولات فروشگاه هاش رو بگیریم.
به این شکل میتونیم در مدل کاربر این کار رو انجام بدیم :
در تصویر بالا اگه ببینید ما برای گرفتن محصولات یه کاربر از متود hasManyThrough که در کلاس Model اصلی هسته لاراول قرار داره استفاده میکنیم و میتونیم شش پارامتر به متود hasManyThrough پاس بدیم و به ترتیب به این شکل هست :
حالا چه اتفاقی در کوئری sql میوفته و این دستور به چه شکلی اجرا میشه؟! به همین راحتی با استفاده از inner join، پکیج ORM لاراول این کوئری رو اجرا میکنه :
select * from `products` inner join `shops` on `shops`.`id` = `products`.`shop_id` where `shops`.`user_id` = ?
رابطه چند ریختی به ما این امکان رو میده تا بتونیم از یک جدول برای دو یا چند جدول، بجای ایجاد جدول های جدید برای هر کدوم از اونها استفاده کنیم.
شاید یکم سخت و گنگ باشه اما با یه مثال خیلی راحت میشه!
فرض کنید ما قصد داریم برای کاربران و فروشگاه هاشون عکس در نظر بگیریم و بیان و عکس مورد نظر خودشون رو بذارن و استفاده کنن.
اگر در حالت عادی بخوایم جلو بریم باید هم برای فروشگاه و هم برای کاربران یک جدول مجزا ایجاد کنیم و آدرس عکس های انتخابی کاربر رو در جدول هامون ذخیره کنیم.
رابطه polymorphic اینجا به کارمون میاد، جایی که به ما این امکان رو میده از ایجاد جدول اضافی جلوگیری کنیم و بجاش از یک جدول متمرکز که دیاگرامش رو در شکل زیر میبینید استفاده میکنیم :
در دیاگرام بالا دو فیلد pictureable_id و pictureable_type مهم ترین بخشی هستن که باید بهش توجه کنید.
ما میخوایم این جدول به صورت متمرکز عمل کنه و هر جدولی که قراره محتوای تصویری هم داشته باشه با این جدول مدیریت بشه.
خیلی خوب اگه این دو فیلد رو خوب یاد گرفتی یعنی هیچ مشکلی برای ادامه نداری و فقط کافیه با چندتا متود که مربوط به polymorhpic هست آشنا بشی.
من میام، مدل و فایل migration های جدول pictures رو با دستور زیر ایجاد میکنم.
$ php artisan make:model Picture -m
با دستور بالا دو فایل "Picture.php" (مدل) و "create_pictures_table.php" (مایگریشن) ایجاد میشن؛ بعد میام و طبق دیاگرام بالا فیلد های pictures رو توسط Schema Blueprint لاراول ایجاد میکنم.
همونطور که بالا میبینید هیچ نامی از هیچ جدولی آورده نشده و فیلد pictureable_type قرار هست که این کار رو انجام بده و جدول ها متنوعی رو شناسائی کنه.
حالا میخوام از این جدول ابتدا برای جدول کاربران (users) استفاده کنم و به این شکل عمل میکنم :
در تصویر بالا از متود morphOne که یک رابطه یک به یک، چند ریختی هست استفاده کردم (یعنی هر کاربر میتونه یک تصویر پروفایل داشته باشه)، به این متود میتونیم 5 پارامتر پاس بدیم :
بعد از انجام همه این کار ها، در صورتی که نام جدول ها و فیلد هامون رو به درستی وارد کرده باشیم میتونیم از اونها به این شکل استفاده کنیم، مثال :
$user->picture
با توجه به اینکه متود picture رو در مدل User استفاده کردیم، میدونیم که برای گرفتن آبجکت مدل جدول مرتبط باید اسم متود رو به عنوان پراپرتی استفاده کنیم تا آبجکت مدل به ما داده بشه.
به همین راحتی، ابتدا در مدل Picture یه متود به اسم pictureable ایجاد میکنیم و با استفاده از متود morphTo اینکارو انجام میدیم :
همونطور که میبینید پارامتر ها هم دقیقا مثل متود morphOne هست فقط فرقش اینه که این یکی کمتره و دیگه به پارامتر اول یعنی نام کلاسی که قرار هست چند ریختی رو انجام بده، نیازی نداره.
به همین راحتی میتونیم کاربری که این عکس متعلق به اون هست رو بگیریم :
$picture->picturable
اگه بخوایم عکس جدید برای کاربر بذاریم؟!
$user->picture()->create([
'url' => 'user_profile_new.jpg'
])
فقط دقت داشته باشید، برای اینکه یه عکس جدید برای کاربر بذاریم، این راه رو میریم اما قبلش باید یه کوئری انجام بدیم که آخرین عکسی که آپدیت شده رو بذاریم یا اینکه بیایم و تصویر قبلی رو حذف کنیم.
$user->picture()->latest()->first()
حالا میرسیم به اصل ماجرا یعنی بیایم و از همین جدول pictures برای shops هم استفاده کنیم.
اینجا متوجه میشیم که این polymorphic چه موجود جذابیه که در ORM لاراول کارش رو انجام میده! ?
برای استفاده از pictures در shops ابتدا مدل Shop رو باز میکنم و یک متود با نام picture دقیقا مثل متودی که در User ایجاد کردیم درست میکنم. (میتونیم این متود رو به trait تبدیل کنیم تا از ریپیت کردن هم جلوگیری کنیم)
همونطور که میبینید دقیقا مثل متودی هست که من در مدل User استفاده کردم و هیچ فرقی نداره.
حالا به همین راحتی میتونم تصویر فروشگاه رو بگیرم که در صورت اینکه هیچ تصویری نباشه، null برگشت داده میشه.
$shop->picture
به همین راحتی تونستیم بدون ایجاد کردن جدول دیگه، از یک جدول برای دو جدول مختلف استفاده کنم.
متود morphOne این کوئری رو اجرا میکنه :
select * from `pictures` where `pictures`.`pictureable_id` = ? and `pictures`.`pictureable_id` is not null and `pictures`.`pictureable_type` = ?
اما این بحث جذاب حالا حالا ها ادامه داره...
حالا اپ داره بزرگتر میشه و ما میخوایم حجم زیادی از محصولات و فروشگاه هایی که توسط کاربران ایجاد شده رو دسته بندی کنیم تا بتونیم راحت تر اون هارو مدیریت کنیم، این امکان رو به کاربران بدیم تا تگ یا دسته بندی ها متنوعی با تعداد نامحدود روی محصولات و فروشگاه هایی که دارن ایجاد کنن.
مثلا : رضا یه فروشگاه موبایل داره و میاد و تگ های #موبایل #لوازم_جانبی #فروش_موبایل میزاره و همچنین برای یه محصولی که مثلا گوشی اپل باشه تگ #apple یا #آیفون رو قرار میده.
رابطه های چند به چند معمولا سخت و گنگ به نظر میرسه اما با بیشتر کار کردن باهاشون این موضوع هم رفع میشه.
ما برای پیاده سازی این رویکرد نیاز به دو جدول داریم :
tags
taggables
جدول tags هر تگی که ایجاد میشه بدون در نظر گرفتن اینکه چه کسی اون رو ساخته در خودش ذخیره میکنه و جدول taggables اصطلاحا جدول junction ما هست و میاد و تگ هارو با جداولی که میخوان با tags رابطه داشته باشن لینک میکنه و رابطه چند به چند رو اجرا میکنه.
برای شروع من طبق دیاگرام جدول هام رو ایجاد میکنم :
اول با جدول و مدل Tags شروع میکنم و با یک دستور هر دو اونهارو ایجاد میکنم:
$ php artisan make:model Tag -m
دستور بالا دو کلاس Tag.php (مدل) و create_tags_table.php (مایگریشن) ایجاد میشن.
بعد میرم سراغ ایجاد جدول taggables :
$ php artisan make:migration create_taggables_table
با دستور بالا یک کلاس مایگریشن برای جدول tags ایجاد میشه تا بتونیم اون رو پیکر بندی کنیم.
خب حالا بریم برای ایجاد جدول ها در فایل مایگریشن create_tags_table :
نکته مهم : حواستون باشه زمانی که از فیلد ها updated_at و created_at استفاده نمیکنیم باید پراپرتی $timestamps رو false قرار بدیم که به اررور پیدا نکردن این فیلد ها نخوریم.
و بعد پیکر بندی جدول taggables :
حالا بریم سراغ مدل هایی که میخوایم از تگ استفاده کنن و با جدول tags ارتباط داشته باشن، اینجا بیشتر با قدرت polymorphic در پکیج ORM لاراول آشنا میشیم.
همونطور که ابتدا گفتم ما میخوایم فروشگاه ها و محصولات دارای تگ باشن تا بشه بهتر مدیریت و فیلترشون کرد، برای این منظور بجای ایجاد دو جدول shop_tags و product_tags ما میخوایم از یک جدول taggables برای هر جدولی که بخواد با tags ارتباط برقرار کنه خواهیم داشت که با اومدن فیلد های taggable_id و taggable_type این امکان پذیر میشه.
سوالی که ممکنه پیش بیاد primary کردن سه فیلد tag_id و taggable_id و taggable_type هست و این برای این هست که اجازه ندیم تا تگ های تکراری به یک ردیف در یک جدول لینک بشن و اومدیم از بوجود اومدن duplication entry جلوگیری کردیم. (در واقع اجازه نمیدیم که یک فروشگاه از دو تگ تکراری استفاده کنه)
بریم سراغ مدل Shop و یک متود جدید با نام tags بسازیم تا بتونیم تگ های هر فروشگاه رو براحتی بگیریم :
همونطور که در تصویری بالا میبینید ما با استفاده از تگ morphToMany میتونیم از رابطه چند به چند در polymorphic استفاده کنیم.
پارامتر هاش ممکنه یخورده گنگ به نظر برسه و با توجه به اینکه توو داکیومنت های لاراول این رو کامل توضیح نداده، من در عمل تستش کردم و کامنت هایی که میبینید جلوی هر پارامتر توسط خودم با بررسی هسته ORM لاراول نوشته شده.
مهم : پارامتر هایی که اختیاری هستن به شرطی درست توسط پکیج ORM لاراول حدس زده میشه که شما طبق اصول این پکیج که در داکیومنتاش اومده جلو برید!
select * from `tags` inner join `taggables` on `tags`.`id` = `taggables`.`tag_id` where `taggables`.`taggable_id` = ? and `taggables`.`taggable_type` = ?
حالا همه چی آماده اس تا ما هر تگی که یک فروشگاه نیاز داره رو به اون بدیم.
$shop = Shop::first() ; $tag = new Tag; $tag->name = 'Programming'; $tag->save(); $shop->tags()->attach($tag); $shop->tags;
و به همین راحتی میتونیم در مدل های دیگه ای که میخوان از تگ استفاده کنن هم این رو داشته باشیم :
همونطور که میبینید یه متود tags در مدل محصولات (Product) ایجاد کردم و با استفاده از متود morphToMany و پاس دادن فقط دو پارامتر میتونم از جدول tags در محصولات هم استفاده کنم.
نحوه کارکردن با آبجکتی که morphToMany بر میگردونه دقیقا مثل رابطه چند به چند ساده اس که در بخش اول مقاله استفاده میکردیم و در داکیومنت های لاراول گفته شده و میتونیم از متود های sync, attach and detach استفاده کنیم :
$product = Product::first(); $tags = Tag::insert([ ['name' => 'programming'], ['name' => 'games'], ]); $product->tags()->sync($tags); $product->tags()->detach(Tag::first()); $product->tags;
با استفاده از متود morphedByMany در مدل Tag ما میتونیم تگ هایی که محصولات و فروشگاه های مختلف استفاده کردن رو بگیریم :
متود morphedByMany این کوئری رو اجرا میکنه :
select * from `products` inner join `taggables` on `products`.`id` = `taggables`.`taggable_id` where `taggables`.`tag_id` = ? and `taggables`.`taggable_type` = ?"
حالا براحتی میتونیم از طریق آبجکت tag به محصولات یا فروشگاه هایی که از اون تگ استفاده کردن دسترسی داشته باشیم، برای مثال ما میخوایم بدونیم چه فروشگاه هایی در حوزه programming محصول دارن و اون هارو میفروشن :
$targetTag = Tag::whereName('programming')->first(); $targetTag->shops;
به همین راحتی میتونیم فروشگاه هایی که از این تگ استفاده کردن رو بگیریم.
این سری از آموزش درباره رابطه ORM لاراول رو همینجا در بخش دوم این سری تموم میکنم، قطعا خیلی مسائل و امکانات بیشتری هست که میتونید در داکیومنت های لاراول پیدا کنید، در این دو بخش من خواستم فراتر از داکیومنت های لاراول پیش برم و باهم رابطه های مختلف موجود در پکیج فوق العاده قدرتمند ORM لاراول رو یاد بگیریم و بیشتر از اتفاقاتی که پشت صحنه میوفته آگاه باشیم.
در پایان امیدوارم این مقاله براتون مفید بوده باشه و به احتمال زیاد بحث دیگه مربوط به همین سری رو در مقاله های مجزا مثل custom polymorphic types بنویسم.
پیشنهاد من به شما اینه که برید و بیشتر هسته این پکیج جذاب رو نگاه کنید و البته میتونید با استفاده از متود toSql اتفاقاتی که در اجرای کوئری میوفته رو بررسی کنید.
مرسی از همراهیتون،
اگه دوست دارید مطالب بیشتری از من مطالعه کنید ویرگول، توییتر و لینکدین منو دنبال کنید.
https://www.linkedin.com/in/mohamad-fazel-hesari