فصل چهارم: لایه مدل

آشنایی با الیکوئنت

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

درس نخست از این فصل، به آشنایی با فلسفه‌ی مدل‌ها، نحوه‌ی ساخت آن‌ها، و چند نکته‌ی ریز در این رابطه اختصاص دارد.

هرچند این سطرها را در نخستین ساعت‌های سال ۱۳۹۷ می‌نویسم، اما سال نو را تبریک نمی‌گویم، چون معلوم نیست شما که آن را می‌خوانید در چه زمانی به سر می‌برید.

لایه مدل در لاراول

در فصل نخست این کتاب، کمی در مورد معماری سه‌لایه‌ی «مدل ـ نمایش ـ کنترلر»، موسوم به MVC توضیح داده شد (+) که بنا بر تکرار آن ندارم.

پردازش داده‌ها، چه پیش از ذخیره‌ی آن‌ها و چه پس از آن در هنگام استفاده، بر عهده‌ی لایه‌ی مدل است.

همان جا گفتم با این که لاراول یک فریمورک مبتنی بر MVC نیست و چنین ادعایی هم ندارد، اما به جداسازی لایه‌ها که روح این معماری‌ست وفادار مانده و همین برای ما کافی‌ست که مدل را به عنوان یک لایه‌ی مستقل در این کتاب مورد بررسی قرار دهیم و به یاد داشته باشیم که:

مدل‌ها نسبت به آنچه در بیرونشان روی می‌دهد‌، نابینا هستند. این که داده‌هایی که ذخیره می‌شوند از کجا و به چه طریقی به دست آمده‌اند و این که داده‌هایی که استخراج می‌شوند قرار است به چه کار آیند، به مدل ارتباطی ندارد. لایه‌ی مدل نه این چیزها را می‌داند و نه در طلب دانستن آن‌ها، ارتباطی با دیگر لایه‌ها برقرار می‌کند.

قانون طلایی مدل‌ها در لاراول

در لاراول، به ازای هر جدول اطلاعات، یک مدل داریم که مسئول پردازش اطلاعات همان جدول است و «الیکوئنت» خوانده می‌شود.

لاراول پر از اسم‌های جذاب و رنگارنگ است که «الیکوئنت» (Eloquent) یکی از آن‌هاست. این اسامی را بیاموزید و به کار ببرید، اما زیاد خودتان را درگیرشان نسازید و تصور نکنید که مفاهیم ناشناخته‌ای هستند که قبلاً در پی‌اچ‌پی نمی‌شناختید.

خاطرتان هست که گفتیم نام جدول‌ها در لاراول از اسامی جمع در زبان انگلیسی ساخته می‌شوند؟

ایده آن است که کلمه‌ی «مدل»‌ به یک رکورد از جدول اشاره کند و به همین دلیل، نام کلاس مدل متناظر با هر جدول، صورت مفرد نام همان جدول است.

بر این اساس، حالا که برای آپولو هوا کردن، دو جدول به نام‌های users و missions ساختیم، دو کلاس الیکوئنت، به ترتیب با نام‌های User و Mission برای آن‌ها لازم داریم.

دسترسی به مدل‌ها

اگرچه در معماری استاندارد MVC دسترسی به لایه‌ی مدل تنها از طریق لایه‌ی کنترل میسر است، لاراول چنین محدودیتی را قائل نمی‌شود و از هر کجا که بخواهید، حتی در لایه‌ی نمایش، می‌توانید سراغ مدل‌ها بروید و از متدهای آن استفاده کنید.

ساخت مدل‌ها

مدل‌های الیکوئنت کلاس‌هایی هستند که به صورت پیش‌فرض در پوشه‌ی app جای داده می‌شوند و نیم‌اسپیس متناسب با همان پوشه را بر خود می‌گیرند و از کلاس دیگری به نام Model مشتق می‌شوند.

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

چون در درس پیش (+) جدولی به نام missions ساختیم، به مدلی با نام Mission هم نیاز داریم. پس این مدل را همزمان با من بسازید تا همین طور که در دنیای لاراول پیش می‌رویم، از کار اصلی خودمان که آپولو هوا کردن است، باز نمانیم.

php artisan make:model Mission

اجرای دستور بالا در خط فرمان پروژه، کلاسی با نام مورد علاقه‌ی ما می‌سازد و در جای درست قرار می‌دهد و نیم‌اسپیس متناسب را بر آن می‌نهد و از کلاس درست مشتق می‌نماید.

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

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Mission extends Model
{
    //
}

پوشهٔ مدل‌ها

آنجا که سخن از ساختار پوشه‌های لاراول بود، اشاره کردم که خالق این فریمورک، با دلایلی که برای خودش دارد، به صورت پیش‌فرض جایی برای مدل‌ها در نظر نگرفته و آن‌ها را کف پوشه‌ی app پخش می‌کند که ظاهر ناخوش‌آیندی دارد، اما قابل اصلاح است.

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

تنها کاری که هنگام ساخت مدل‌های الیکوئنت باید انجام دهیم، آن است که نامی که برای پوشه‌ی مورد نظر خود در نظر گرفته‌ایم را در دستور آرتیزان ذکر کنیم.

php artisan make:model Models/Mission

اجرای دستور بالا در خط فرمان پروژه، کلاسی با نام مورد علاقه‌ی ما، و این بار در پوشه‌ی مورد علاقه‌ی ما می‌سازد و نیم‌اسپیس متناسب را بر آن می‌نهد و همچنان از کلاس درست مشتق می‌نماید.

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Mission extends Model
{
    //
}

مدل کاربر

هنگامی که به اتفاق هم مایگرشن‌هایی برای آپولو هوا کردن می‌ساختیم (+)، دیدیم که مایگرشن مربوط به ایجاد جدولی برای اطلاعات کاربران از قبل وجود داشت و ما فقط تغییراتی در آن ایجاد کردیم که نیازهای خاص پروژه‌ی ما را برآورده سازد.

از آنجا که تقریباً تمام پروژه‌های وب در عالم امکان، کاربرانی نیز دارند، لاراول جدول و مدل مورد نیاز برای مدیریت کاربران را به صورت پیش‌فرض در جعبه‌ی خود گذاشته که در بدو نصب قابل استفاده هستند.

شما حالا می‌دانید که نام مدل مربوط به کاربران، User است و طبیعتاً باید در پوشه‌ی app که محل پیش‌فرض لاراول برای نگهداری مدل‌هاست دنبالش بگردید.

اگر هم مثل من دوست دارید که مدل‌ها در جای مشخصی مخصوص به خودشان، مثلاً در پوشه‌ای به نام Models نگهداری شوند، باید زحمت انتقال این کلاس پیش‌فرض را بکشید. من قول می‌دم که ارزشش را داشته باشد.

از شما می‌خواهم این مدل پیش‌فرض مربوط به کاربران را باز کنید و با مدل مربوط به مأموریت‌ها که به اتفاق هم ساختیم، مقایسه نمایید.

قاعدتاً اولین تفاوتی که چشم شما را می‌گیرد، آن است که در مدل User چیزهایی نوشته شده، اما مدل Mission که خودمان ساختیم، خالی است. فعلاً نگران خالی بودن کلاس خودمان نباشید و به دنبال دومین تفاوت بگردید.

آیا می‌توانید پیش از اشاره‌ی من آن را بیابید؟

<?php

namespace App;

use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
  ...

قابل اعتبارسنجی!

مدل User، به جای آن که از کلاس Model مشتق شود، کلاس دیگری را با نام مستعار Authenticatable به عنوان والد خود برگزیده است!

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

نگاهی به داخل این کلاس بیاندازید و ببینید این دست به دست کردن، چه چیز یا چیزهایی را به مدل ما ا‍ضافه کرده است.

<?php

namespace Illuminate\Foundation\Auth;

use Illuminate\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Auth\Passwords\CanResetPassword;
use Illuminate\Foundation\Auth\Access\Authorizable;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;

class User extends Model implements
    AuthenticatableContract,
    AuthorizableContract,
    CanResetPasswordContract
{
    use Authenticatable, Authorizable, CanResetPassword;
}
مراقب باشید نام User در این کلاس واسط، شما را به اشتباه نیاندازد و آن را با مدل User اشتباه نگیرید. در واقع Authenticatable نام مستعاری‌ست که برای پرهیز از هم‌نامی دو کلاس، در مدل User به این کلاس داده شده است. از آنجا که پیش‌فرض کتاب آن است که شما پی‌اچ‌پی را می‌دانید و با مفاهیم شیءگرایی در این زبان آشنا هستید، به همین اشاره بسنده می‌کنم.

از نیم‌اسپیس و فهرست وابستگی‌های استفاده‌شده گذر کنید و به خط هجدهم بروید، آنجا که سه Trait مورد استفاده قرار گرفته‌اند.

معرکه نیست؟

لاراول، با همان منطق که «تقریباً تمام پروژه‌های وب در عالم امکان، کاربرانی نیز دارند»، مایگرشن و مدلی برای مدیریت کاربران ساخته و جلوتر هم رفته است:

  • تریت Authenticatable، هر آنچه برای اعتبارسنجی کاربران لازم دارید را به مدل کاربر اضافه می‌کند.
  • تریت Authorizable، هر آنچه برای بررسی سطح دسترسی کاربران لازم دارید را به مدل کاربر اضافه می‌کند.
  • و تریت CanResetPasswordContract، متدهای لازم برای بازنشانی گذرواژه را به مدل کاربر اضافه می‌کند.
قضیه به این‌جا ختم نمی‌شود. لاراول کنترلرهای مورد نیاز برای ورود و خروج کاربران و فراموشی رمز عبور را نیز تدارک دیده و برای آن که لطف را تمام کرده باشد، حتی نماهای لازم در لایه‌ی نمایش را نیز آماده کرده است. از همه بهتر آن که تمام این سیستم، تقریباً بدون دخالت شما، قابل استفاده و عملیاتی است! فعلاً عجله نکنید. تک‌تک آن‌ها را بررسی خواهیم کرد.

ما چه کنیم؟

مدل کاربر، با مشتق شدن از کلاس Authenticatable، قابلیت‌هایی که متناسب با کاربران است را بدون جنگ و خون‌ریزی به خود اضافه می‌کند و جز علم به موضوع، کار خاصی لازم نیست انجام دهید.

حالا اگر با هر دلیلی که خودتان می‌دانید (و در حال حاضر برای من بسیار دور از انتظار است)، ترجیح می‌دادید که از این امکانات استفاده نکنید، یا فقط از چندتای آن‌ها استفاده کنید، مختارید مدل User را به میل خود تغییر دهید.

مثلاً اگر می‌خواهید مدل User هم مدلی مثل سایر مدل‌ها باشد و هیچ امکانات اضافه‌ای نداشته باشد، کافی‌ست مثل سایر مدل‌ها، آن را فرزندی از کلاس ‍Model تعریف کنید.

class User extends Model 
{
    //
}

یا اگر ابزار بهتری برای تعیین سطح دسترسی‌ها نوشته‌اید و نمی‌خواهید مدل User از ابزار پیش‌فرض لاراول در این راه استفاده کنید، کافی‌ست کلاس خود را از همان کلاس Model مشتق کنید و سپس همان traitها (و البته implementationها) که مورد نیازتان هست را به آن اضافه نمایید.

class User extends Model implements
    AuthenticatableContract,
    CanResetPasswordContract
{
    use Authenticatable, CanResetPassword;
}

یا مثلاً ممکن است با دلایلی عجیب و غریب، بخواهید جدول ادمین‌های پروژه‌ی خود را از جدول کاربران جدا کنید و در این صورت باید برخی از این امکانات را در مدل مربوط به آن جدول هم لحاظ کنید.

پیشنهاد می‌کنم از این کارها نکنید و اجازه دهید داده‌های تمام کاربران پروژه‌ی شما، اعم از ادمین و غیرادمین، مشتری و مدیرفروش، کارجو و کارآفرین، و به طور کلی هر موجودی از جنس آدمیزاد، در یک جدول متمرکز باشد. هیچ چیز ناخوشایندتر از آن نیست که به شما بگویند برای خرید از فروشگاه آنلاینی که ادمینش هستید، ابتدا باید از حساب کاربری ادمین خود خارج شوید، سپس با شناسه و گذرواژه‌ی دیگرتان به عنوان مشتری وارد شوید و خرید کنید. این برنامه‌ی شماست که باید بتواند میان کاربران و نقش‌های مختلف آن‌ها تفاوت قائل شود. زحمتش را به دوش استفاده‌کنندگان نیاندازید.

باز هم پوشهٔ مدل‌ها

گفتم که به اعتقاد من، و بسیاری از لاراول‌نویسان دیگر، بهتر است مدل‌ها پوشه‌ای برای خود داشته باشند و مثل بی‌خانمان‌ها کف پوشه‌ی app قرار نگیرند.

برای مدل‌های جدید، کافی است نام یک پوشه را (مثلا Models) در دستور آرتیزان قید کنیم. اما برای مدل User‍ که از قبل وجود داشت، باید خودمان دست به کار شویم.

اگر می‌خواهید کدهایی که برای آپولو هوا کردن می‌نویسم به کار شما نیز بیایند، با من همراه شوید و این دو تغییر کوچک را اعمال کنید:

  • ابتدا فایل User.php را از پوشه‌ی app بردارید و در پوشه‌ی app/Modles‍‍ قرار دهید.
  • بعد نیم‌اسپیس آن را به شکل App\Models‍ اصلاح کنید.

همین!

مدل مایگرشن

از یک سو...

در تقریباً تمام درس‌های فصل سوم، که بیشتر به مفهوم مایگرشن و نحوه‌ی کار با آن می‌پرداختند، از جدولی به نام migrations سخن گفتیم و دانستیم که لاراول، برای نگهداری نام مایگرشن‌های اجراشده و ترتیب برپایی هر یک از آن‌ها، به چنان جدولی نیازمند است.

از سوی دیگر...

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

در لاراول، به ازای هر جدول اطلاعات، یک مدل داریم که مسئول پردازش اطلاعات همان جدول است.

پس...

لابد باید مدلی به نام Migration داشته باشیم و حتماً انتظار داریم لاراول خودش ترتیب ساخت آن را داده باشد.

اما این طور نیست!

چنین مدلی در کار نیست و ما هم آن را نخواهیم ساخت، چون اساساً این ما نیستیم که به آن نیاز داریم!

اجازه بدهید لاراول را در نحوه‌ی ارتباط برقرار کردن با جدولی که خودش برای کارهای خودش ساخته تنها بگذاریم.

مطمئنم که از پس آن برمی‌آید.

جان کلام

در این درس مهم و کوتاه، اولین گام‌ها را بر لایه‌ی مدل در لاراول برداشتیم و آموختیم که هر جدول به یک مدل نیاز دارد که نام مفرد همان جدول را بر خود دارد و Eloquent Model خوانده می‌شود. در ادامه، یک مدل برای خودمان ساختیم و دانستیم که مدل مورد نیاز برای مدیریت کاربران ما از پیش ساخته شده و با تفاوت‌های اندکی که با مدل معمولی دارد آشنا شدیم.

مدل‌هایی را که در این درس دیدیم، چه ‍Mission که خودمان ساختیم و چه User که از قبل وجود داشت، بدون یک خط «چیز» اضافه، به حال خود رها کردیم. در درس بعد خواهیم دید که چگونه همین مدل‌های بدوی و صفرکیلومتر، می‌توانند کوئری‌های اولیه‌ی ساخت و ویرایش و حذف رکورد را برای ما بسازند.

پروژه‌ی آپولوی شما در پایان این درس باید چیزی شبیه این باشد.