فصل سوم: شروع واقعی با دیتابیس

طراحی دیتابیس با مایگرشن

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

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

متدهای بالا و پایین

همان طور که گفتیم، فایل‌های مایگرشن کلاس‌هایی معمولی در زبان پی‌اچ‌پی هستند که دو متد عمومی up و down را در خود جای داده‌اند و طراحی دیتابیس‌های ما در دل این دو متد صورت می‌گیرد.

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

هر کلاس مایگرشن، باید قادر به انجام هر دو کار باشد و به همین سبب هم دو متد، یکی به نام «بالا» و دیگری به نام «پایین» دارد.

  • متد up مسئول اجرای حرکت رو به جلوی هر مایگرشن است که چیزی را برپا می‌کند.
  • متد down مسئول اجرای حرکت رو به عقب هر مایگرشن است که بنایی را تخریب می‌کند.

به همین سادگی!

حرکت‌های جلو و عقب

بد نیست یک بار دیگر، مروری سریع بر فرمان‌های مایگرشن آرتیزان داشته باشیم.

در حرکت رو به جلو، باید همه‌ی مایگرشن‌های موجود را به خط کنیم و متدهای ‍up یکایک آن‌ها را به ترتیب اجرا نماییم. دستور زیر همین کار را می‌کند.

php artisan migrate

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

php artisan migrate:rollback

این همه‌ی کاری است که با مایگرشن می‌توانیم انجام بدهیم. دستورهای بعدی، کم و بیش ترکیبی از همین دو دستور هستند.

برای آن که همه چیز را خراب کنیم (یعنی آن قدر migrate:rollback کنیم که چیزی باقی نماند):

php artisan migrate:reset

برای آن که همه چیز را خراب کنیم و از نو بسازیم (یعنی migrate:reset کنیم و دوباره ‍migrate نماییم):

php artisan mirgate:refresh

کتابخانه‌ای به نام Schema

هر کاری که در مایگرشن انجام می‌شود، چه در متد up قرار بگیرد و در پی بنا کردن چیزی باشد، و چه در متد down قرار بگیرد و در پی تخریب چیزی باشد، در واقع کاری است که روی یکی (و فقط یکی) از جدول‌های بانک اطلاعاتی ما انجام می‌شود.

  • گاهی می‌خواهیم جدول جدیدی بسازیم و فیلدهایش را مرتب کنیم؛
  • گاهی می‌خواهیم جدولی که داشته‌ایم را خراب کنیم؛
  • و گاهی هم می‌خواهیم جدولی را تغییر دهیم.
در مایگرشن، هزار و یک کار دیگر هم می‌توانیم انجام بدهیم، مثلا یک خط برنامه بنویسیم که «سلام دنیا»ی معروف را به ما بگوید و یا فرمی را جلوی کاربر بگذاریم که پر کند. بله می‌توانیم و احتمالاً کار هم می‌کند. اما هر سخن جایی و هر نکته مکانی دارد. مایگرشن جای این جور کارها که گفتیم نیست.

حالا که هرچه در متدهای up و ‍down کلاس‌های مایگرشن می‌نویسم از جنس کار با جداول دیتابیس است، خوب است کتابخانه‌ای داشته باشیم و تمام این کارها را با آن انجام بدهیم؛ کتابخانه‌ای که از تمام انواع دیتابیس تحت حمایت لاراول، پشتیبانی کند و ما لازم نباشد برای هر کدام خط جداگانه‌ای بنویسیم.

چنین کتابخانه‌ای در لاراول وجود دارد و نامش ‍Schema است.

به دلیل محدودیت زبان فارسی، چاره‌ای جز نوشتن این کلمه به صورت «اسکیما» نداریم. اما شما هنگام خواندن، الف ابتدای آن را تا جای ممکن نادیده بگیرید و روی حرف سین، یک سکون بگذارید: «سْکیما»

اسکیما را از کجا بیاوریم؟

نگران نباشید.

کارها در لاراول راحت‌تر از چیزی است که تصور می‌شود.

همان موقع که آرتیزان عزیز، فایل مایگرشن را برای ما می‌سازد و متدهای up و down را در آن جاسازی می‌کند، کلاس Schema را هم در بخش وابستگی‌های مایگرشن فراخوانی می‌کند.

Migration Schema

اجازه بدهید معرفی متدهای اسکیما را از ساده‌ترین‌های آن‌ها آغاز کنیم.

حذف جدول

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

در اولین متد، می‌توانیم جدولی را ‍drop کنیم:

Schema::drop('missions');

و در دومین متد، هم همین طور:

Schema::dropIfExists('missions');

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

تغییر نام جدول

متدی که برای تغییر نام در اختیار ما گذاشته شده، از متدهای حذف هم ساده‌تر هستند.

Schema::rename($previous_name , $new_name)

لازم است توضیح دهم که آرگومان اول نام قدیمی یک جدول است و آرگومان دوم نام جدیدی است که می‌خواهیم بر آن بگذاریم؟

بعید می‌دانم لازم باشد.

ساخت جدول‌

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

Schema::create('missions' , function (Blueprint $table) {});

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

تغییرات درون جدول

گاهی فقط می‌خواهیم جدولی را انتخاب کنیم و ساختار درونی آن را تغییر دهیم. متد ()table برای همین روزهاست.

Schema:table('missions' , function (Blueprint $table) {});

آرگومان‌های ورودی این متد نیز درست مانند متد ()create است که اولی نام جدول را در بر می‌گیرد و دومی بستار (Closure)حاوی دستورات تعیین ساختار جدول را.

به عنوان تمرین

این کتاب [مثلاً] قرار است بار آموزشی داشته باشد و بنا نیست چیزهایی را بگوییم و عبور کنیم. بنابراین اجازه بدهید همین جا توقفی کنیم و یک مرور کلی داشته باشیم.

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

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

دیدیم که آرتیزان فایل‌های مایگرشن را به درخواست ما ایجاد کرد و متدهای up و ‍‍‍down را در آن‌ها گذاشت و دستوراتی را نیز که حدس می‌زد لازم داریم در دلشان نهاد.

به عنوان تمرین، نگاهی به همه‌ی فایل‌های مایگرشنی که در اختیار دارید بیاندازید و کاربرد دستورات اسکیما را در آن‌ها بیابید و سعی کنید توضیح دهید که چرا برخی از آن‌ها در متد up جای گرفته‌اند و برخی در متد down‍.

کتابخانه‌ای به نام Blueprint

دیدیم که چطور می‌توانیم به کمک اسکیما، جدولی را بسازیم یا برای تغییرات فراخوانی کنیم.

دو متد ()create و ()table را عمداً در انتهای عنوان پیش قرار دادم که بتوانید با چند پاراگراف عقب رفتن در متن، نگاه دوباره‌ای به الگوی اجرای آن‌ها بیاندازید و ببینید که چطور یک بستار به عنوان آرگومان دوم این متدها مورد استفاده قرار گرفته و شیئی از نوع Blueprint به آن پاس داده شده است.

واژه‌ی بلوپرینت، به معنی «نقشه‌ی ساخت»، عبارتی است که از صنعت معماری وام گرفته شده است. بلوپرینت، که در قرن ۱۹ اختراع شد، این امکان را فراهم می‌کرد که از یک نقشه معماری با سرعت و دقت زیادی به تعداد انبوه تکثیر شود.

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

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

Migration Blueprint

فقط لازم است که از متدهای شیء table$ که از روی کلاس Blueprint ساخته شده، به شرحی که اینجا به آن خواهیم پرداخت، استفاده کنیم.

تعریف ستون‌ها با بلوپرینت

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

ستون افزایش خودکار

مرسوم است که نخستین ستون هر جدول را i‍d می‌نامند و در حالت افزایش خودکار قرار می‌دهند و primary index جدول را روی آن تنظیم می‌کنند. متدهای زیر همین کار را به روش‌های مختلف انجام می‌دهند.

$table->increments('id');
$table->bigIncrements('id');
$table->mediumIncrements('id')
$table->smallIncrements('id');
$table->tinyIncrements('id');

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

ستون‌های عددی

متدهای زیر برای تعریف ستون‌های عدد صحیح به کار می‌روند که هر کدام محدوده و بازه‌ای از اعداد را تحت پوشش قرار می‌دهند که موضوع بحث این کتاب نیست.

$table->integer('age');
$table->smallInteger('age');
$table->mediumInteger('age');
$table->bigInteger('age');
$table->unsignedInteger('age');
$table->unsignedSmallInteger('age');
$table->unsignedMediumInteger('age');
$table->unsignedTinyInteger('age');

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

$table->decimal('amount', 8, 2);
$table->double('amount', 8, 2);
$table->float('amount', 8, 2);

ستون‌های منطقی

اگرچه نوع داده‌ی BOOLEAN در بیشتر انواع رایج دیتابیس تعریف شده، در mySql، چنانچه مرسوم است از TINYINT برای ذخیره‌ی مقدارهای منطقی استفاده می‌کنند. بلوپرینت، متد ()boolean را برای این نوع از داده‌ها معرفی کرده، اما همچنان برای ساخت جدول در mySql، از TINYINT بهره می‌گیرد.

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

$table->boolean('active')

رشته و متن

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

$table->char('name', 100);
$table->string('name', 100);

سه متد زیر که برای ذخیره‌سازی متن به کار می‌روند، به ترتیب منطبق بر نوع داده‌های TEXT، MEDIUMTEXT و LONGTEXT در دیتابیس‌ها هستند که بنا بر مستندات mySql، اولی ۶۴ کیلوبایت، دومی ۱۶ مگابایت، و سومی ۴ گیگابایت ظرفیت دارد.

$table->text('description');
$table->mediumText('description');
$table->longText('description');

نگهداری زمان

هر کدام از متدهای زیر،‌ معادل نوع داده‌ای متناظر با همان نام در دیتابیس هستند که برای نگهداری زمان، یا تاریخ و زمان به کار می‌روند.

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

$table->time('sunrise');
$table->timeTz('sunrise');
$table->timestamp('added_on');
$table->timestampTz('added_on');
$table->year('birth_year');
$table->dateTime('created_at');
$table->dateTimeTz('created_at');

زمان‌های مهم

سیستم مدیریت مدل‌های لاراول (موسوم به الیکوئنت)، زمان اضافه شدن و آخرین به‌روزرسانی رکوردها را به صورت پیش‌فرض ثبت می‌کند و برای این کار به دو ستون created_at و updated_at نیاز دارد.

اکنون می‌دانیم که این دو ستون با دستورهای زیر ساخته می‌شوند.

$table->timestamp('created_at')->nullable();
$table->timestamp('updated_at')->nullable();

اما بلوپرینت راه بهتری هم برای این کار تکراری درست کرده و ما را از درج این دو خط طولانی، آن هم با به‌کارگیری متد عجیب و غریب ()nullable که تاکنون سخنی از آن به میان نیاورده‌ایم، بی‌نیاز ساخته است:

$table->timestamps();

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

ستون زباله‌دان

سیستم‌عامل‌های متداول دسکتاپ، فایل‌هایی که پاک می‌کنیم را به زباله‌دانی منتقل می‌کنند که ممکن است نامش Recycle Bin یا Trash یا هر چیز دیگری باشد. این سیستم‌های عامل، تا وقتی فایل‌های حذف‌شده را از زباله‌دان هم پاک نکنیم، کاری به کارشان ندارند. در واقع فرمان پاک کردن فایل‌ها، آن‌ها را موقتاً از دسترس ما دور می‌کنند و بس.

در برنامه‌های وبی که ما می‌نویسیم نیز این قابلیت به کار می‌آید.

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

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

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

$table->softDeletes();

با این کار، فیلدی از جنس زمان، به نام deleted_at در جدول اضافه می‌شود. در درس‌های بعدی می‌آموزیم که چطور این ستون جالب را در عمل به کار بگیریم.

زنجیره‌ی جزئیات

برای آن که مشخص کنیم ستون مورد نظر ما می‌تواند مقدار null را بپذیرد، از متد ()nullable در انتهای همان دستور ایجاد ستون استفاده می‌کنیم:

$table->string('email')->nullable();

با استفاده از متد ()default، مقداری پیش‌فرض برای ستون خود تعیین می‌کنیم:

$table->boolean('active')->default(true);

برای درج کامنت، از متد ()comment کمک می‌گیریم:

$table->string('email')->comment('This is Email');

برای آن که زمان فعلی را به عنوان پیش‌فرض یک ستون زمانی تعیین کنیم:

$table->string('created_at')->useCurrent();

برای آن که کدبندی نویسه را به چیزی غیر از پیش‌فرض دیتابیس تغییر دهیم:

$table->charset('utf8')

و یا برای تغییر collation:

$table->collation('utf8_persian_ci')

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

$table->string('email')->nullable()->comment('folan')->charset('utf8')

ایندکس‌ها در بلوپرینت

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

ایندکس معمولی

در همان خطی که ستون را تعریف کردید، می‌توانید متد ()index را نیز به صورت زنجیره‌ای فرا بخوانید و با این کار از لاراول بخواهید ایندکسی را برای آن ستون تعریف کند.

$table->string('name')->index();

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

$table->index('name')

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

$table->index(['last_name','first_name']);

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

$table->index(['last_name','first_name'] , 'index_name');

ایندکس یکتا

ایندکس‌های یکتا با متد ()unique تعریف می‌شوند و هر چه برای ایندکس‌های معمولی گفتیم، در ایندکس‌های یکتا نیز به کار می‌آیند.

می‌توانیم آن‌ها را به صورت زنجیره‌ای در انتهای همان خط تعریف ستون، تعریف نماییم:

$table->string('username')->unique();

می‌توانیم پس از تعریف ستون، در خطی مستقل آن‌ها را تعریف کنیم:

$table->unique('username');

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

$table->unique('username','unique_username');

ایندکس اصلی

پیش از این، روش ثبت primary index را آموختیم و گفتیم که بهتر است به همان سنت مألوف چنگ بیاندازیم و اولین فیلد را به این شکل (دقیقاً به این شکل) تعریف کنیم:

$table->increments('id');

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

می‌توانیم از روش زنجیره‌ای برای تعریف آن استفاده کنیم:

$table->unsignedInteger('id')->autoIncrement();

می‌توانیم در خط جداگانه‌ای تعریفش کنیم:

$table->unsignedInteger('id');
$table->primary('id');

و می‌توانیم چند فیلد را با یکدیگر ترکیب نماییم:

$table->primary(['id','parent_id']);

کلیدهای خارجی

فرض کنید جدولی از دیدگاه‌ها داریم که هر دیدگاه توسط یکی از کاربران ارسال شده است. بیایید برای این کار یک ستون user_id بسازیم و به id در جدول users متصلش کنیم.

$table->integer('user_id')->unsigned();
$table->foreign('user_id')->references('id')->on('users');

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

$table->foreign('user_id')
    ->refrences('id')
    ->on('users')
    ->onDelete('cascade'); // <-- That Easy!

ویرایش یک جدول با بلوپرینت

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

جزئیاتی که ویرایش را از زمان ساخت متمایز می‌کنند، زیاد نیستند و با هم مرورشان می‌کنیم.

تعیین مکان ستون جدید

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

تعیین مکان نیز به سبک و سیاق بلوپرینت، با سادگی هرچه تمام‌تر و با زبانی نزدیک به زبان آدمیزاد صورت می‌گیرد.

ممکن است بخواهیم ستون جدید را «بعد از» ستونی از ستون‌های موجود قرار دهیم:

$table->tinyText('description')->after('text');

ممکن هم هست بخواهیم ستون جدید را در ابتدای جدول بگذاریم:

$table->tinyText('description')->first();

هرچند بلوپرینت متد ()first را سخاوتمندانه در اختیار ما گذاشته، اما استفاده از آن کار عاقلانه‌ای نیست. چرا که بنا بر پاره‌ای روایات، اولین ستون هر جدول را ستونی به نام id تشکیل می‌دهد که ایندکس اولیه ما نیز بر آن استوار است.

تغییر نام ستون

متد ()renameColumn، با الگوی ساده‌ی زیر، می‌تواند نام یک ستون را تغییر دهد.

$table->renameColumn('from','to');

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

برای این کار به بسته‌ی doctrine/dbal نیاز دارید. نحوه‌ی نصب بسته‌ها را در همین کتاب، در درسی که به کامپوزر و مدیریت بسته‌ها اختصای داشت، مرور کردیم و در درس‌های بعدی نیز بیشتر به آن خواهیم پرداخت.

حذف ستون

متد ()dropColumn می‌تواند یک یا چند ستون موجود از جدول را حذف کند.

$table->dropColumn('active')

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

$table->dropColumn(['age' , 'active'])

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

تغییر در ایندکس‌ها

متدهای اضافه کردن ایندکس را مرور کردیم و همان‌ها اینجا هم کار می‌کنند. شاید به چند متدی که برای حذف ایندکس‌ها استفاده می‌شوند نیاز داشته باشید.

لاراول متدهای زیر را ارائه می‌کند که از نامشان پیداست چه می‌کنند و به چه کار می‌آیند:

$table->dropPrimary('users_id_primary');
$table->dropUnique('users_email_unique');
$table->dropIndex('geo_state_index');
$table->dropForeign('posts_user_id_foreign');

همه‌ی این متدها از ورودی آرایه نیز پشتیبانی می‌کنند تا چندین ایندکس را هم‌زمان حذف کنند.

متد down را جدی بگیرید!

نوشتن متد down برای بسیاری از تازه‌واردان به دنیای مایگرشن مبهم به نظر می‌رسد، اما منطق کار ساده‌تر از این حرف‌هاست.

داستان از این قرار است که می‌بایست هر کار در متد up کردید را در متد down برعکس کنید.

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

دستور migrate در کنسول آرتیزان، فایل‌های مایگرشن را یکی یکی و به ترتیب نامشان برمی‌گیرد و متدهای up را در آن‌ها اجرا می‌کند و چیزهایی را برپا می‌سازد. هنگام حرکت بازگشتی هم که همچون دستور آشنای undo عمل می‌کند، فایل‌های مایگرشن صف می‌شوند و این بار متدهای down موجود در آن‌ها، با ترتیب معکوس، اجرا می‌شوند.

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

دستپاچه و هراسان نشوید. همه‌ی کاری که قرار است بکنید همین است:

هرچه در متد up نوشتید را در متد down برعکس کنید.

هرچه در متد up ساختید را در متد down ویران کنید و هرچه در متد up ویراش کردید را در متد down به حال نخست بازگردانید.

اگر در متد up جدولی ساخته‌اید، باید در متد down آن را از بین ببرید و این درست کاری است که استفاده از دستور ساخت مایگرشن به کمک آرتیزان، (به آن نحو که در درس پیش توضیح دادیم)، خودبه‌خود انجام می‌دهد.

public function down()
{
    Schema::dropIfExists('missions');
}

اگر در متد up ویرایشی را روی جدولی انجام دادید، یا ستون‌های جدیدی ساختید، در متد down آن ویرایش‌ها را به حال نخست بازگردانید و ستون‌های جدیدی که ساخته‌اید را دور بریزید.

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

یک مثال گام به گام...

فرض کنید می‌خواهیم قابلیت انقضای سفر را به جدول مأموریت‌های خود اضافه کنیم.

گام اول، آرتیزان

ابتدا با یک دستور آرتیزان، فایل مایگرشن مورد نیاز خود را می‌سازیم.

php artisan make:migration add_expiry_to_missions_table --table=missions

آرتیزان، با اجرای این دستور، فایلی که لازم داریم را برایمان می‌سازد و در جای درست قرار می‌دهد. Migration Edit Example

و سپس کدهایی که می‌داند لازم داریم را در آن قرار می‌دهد. دقیقاً به این صورت:

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class AddExpiryToMissionsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('missions', function (Blueprint $table) {
            //
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table('missions', function (Blueprint $table) {
            //
        });
    }
}

ملاحظه می‌فرمایید که متدهای ‍up و down‍ سر جایشان گذاشته شده اند، اسکیما در آن‌ها به کار رفته و نام جدول را نیز که در دستور آرتیزان گنجانده بودیم، برایمان گذاشته و بستار (کلوژر) حاوی بلوپرینت را نیز ساخته است.

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

گام دوم، متد بالارونده

حالا وقت آن است که ستون‌هایی که می‌خواهیم اضافه کنیم را در متد up بنویسیم.

public function up()
{
    Schema::table('missions', function (Blueprint $table) {
        $table->timestamp('expired_at')->nullable()->after('description');
        $table->boolean('is_expired')->default(0)->after('expired_at');
    });
}

در این مثال، فرض می‌کنیم که برای افزودن قابلیت انقضا به جدول مأموریت‌ها، به دو ستون نیاز داریم.

  • ستون اول، تاریخ انقضا را در ذخیره می‌کند؛
  • و ستون دوم، که از نوع BOOLEAN است، وضعیت انقضای سفر را در خود نگاه می‌دارد.

گام سوم، متد پایین‌رونده

متد down که قرار است همچون عملگر undo به کار آید، کافی‌ست هرچه در متد up ساخته شده را تخریب کند.

در متد up دو ستون به جدول اضافه کردیم و حالا تنها کاری که در متد down باید انجام دهیم، آن است که آن دو ستون اضافه را از بین ببریم.

public function down()
{
    Schema::table('missions', function (Blueprint $table) {
        $table->dropColumn(['expired_at', 'is_expired']);
    });
}

همین!

واقعیت آن است که نوشتن متد down‍ از متد ‍up هم ساده‌تر است!

این طور نیست؟

تکرار یک هشدار

باز هم دقت کنید.

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

لاراول نمی‌تواند حدس بزند که واقعاً آن داده‌ها را لازم دارید یا نه، اما می‌تواند مراقب باشد که در فاز بهره‌برداری از دستور migrate استفاده نکنید.

اگر چنین کنید، اخطاری می‌گیرید.

Laravel Migration Warning

امیدوارم قرار دادن این اسکرین‌شات‌ها، باعث نشوند که خودتان آزمایش نکنید. برای آزمایش آنچه گفتم، کافی‌ست ابتدا محیط برنامه‌ی خود را در حالت production قرار دهید (به فایل ‍env. بروید و APP_ENV را از ‍local به ‍production تغییر دهید) و سپس با اجرای دستور مایگرشن در کنسول آرتیزان، تصویر بالا را در کنسول خودتان مشاهده کنید.

جان کلام

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