PHPPamokos.lt


7. Duomenų bazės migracijos

Kad suvaldytume programinį kodą ir jo versijas, naudojame versijų kontrolės sistemas - tokias kaip Git, SVN, Mercurial. O kaip su duomenų baze? Juk projekto metu tenka keisti DB struktūrą ar kurti naujas lenteles. Ir jei turite kelias darbo aplinkas (serverius), dirbate iš kelių kompiuterių ar paprasčiausiai dirbate komandoje su kitais programuotojais, tuos DB pakeitimus reikia atlikti visur. Ir kas tada - darome Export SQL skriptus ir juos siunčiame visur? Laravel siūlo patogesnį būdą - migracijas.

Principas toks: kiekvieną kartą kai darote kažkokį pakeitimą duomenų bazėje, reikia tai daryti per Laravel įrankius. Jokių tiesioginių Create Table ar phpMyAdmin. Sukuriate migracijos failus, kuriuos vėliau galima įvykdyti arba atstatyti ankstesnę versiją su Artisan pagalba iš komandinės eilutės.

Tiesą pasakius, ir migracijų kūrimas vyksta per Artisan.
Pavyzdys - sakykime, kad norime sukurti naują lentelę pavadinimu Pages.

1. Iš komandinės eilutės paleidžiame komandą migracijos failo sukūrimui:
php artisan make:migration create_pages
2. Einame į katalogą database/migrations ir atidarome naujausią ką tik sukurtą failą:
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreatePages extends Migration {

  /**
  * Run the migrations.
  *
  * @return void
  */
  public function up()
  {
  }

  /**
  * Reverse the migrations.
  *
  * @return void
  */
  public function down()
  {
  }

}
Kaip matome, tai yra PHP klasė, turinti dvi funkcijas: up() ir down(). Pirmoje aprašome sukūrimo veiksmą, o pastarojoje - atstatome tai kas buvo, t.y. triname lentelę.

Ir čia mums nereikės SQL kodo - tiek lentelę, tiek jos laukus kursime Laravel funkcijų pagalba, štai taip:
public function up()
{
  Schema::create('pages', function($table){
    $table->increments('id');
    $table->string('title', 100);
    $table->text('page_text');
  });
}
Šiame pavyzdyje funkcijoje up() sukuriama DB lentelė pages su tokiais laukais kaip:
  • id (int auto_increment)
  • title (varchar 100)
  • page_text (text)
Iš pirmo žvilgsnio gali atrodyti - kam kurti DB lentelę su PHP funkcijomis, kai galima tai vizualiai padaryti per phpMyAdmin ar kitas panašias priemones? Toks klausimas turi pagrindo kai prie projekto dirbate vienas ir nereikės dirbti su kitais žmonėmis, keičiant DB struktūrą. Priešingu atveju bus galvos skausmas, norint pakeisti kokį lauką ar sukurti kokią lentelę - ir po to perduoti pakeitimus kolegoms. Kurti SQL failus su export/import veiksmais ir siuntinėti juos el.paštu ar per kokį GitHub baisiai nepatogu.

Bet net jei ir dirbate vienas, dažniausiai juk dirbate keliose aplinkose - namų/ofiso kompiuteryje lokaliai, "gyvame" serveryje, o gali būti dar ir tarpinių aplinkų - testavimui ir pan. Tai šiuo atveju galioja ta pati logika - be migracijų darant DB pakeitimus, reikės praleidinėti SQL importo užklausas kiekvienoje aplinkoje.

Dabar grįžkime prie up() funkcijos ir aptarkime įvairių laukų kūrimo sintaksę. Kaip matote pavyzdyje, Laravel klasė Schema turi funkciją create(), kurios antrasis parametras yra atskira funkcija, kur ir yra surašomi visi laukai su parametrais. Iš pagrindinių laukų tipų galima paminėti šiuos:
  • $table->increments('xxx'): INT laukas xxx su auto_increment ir Primary indeksu
  • $table->string('xxx', 123): VARCHAR(123) tipo laukas xxx
  • $table->text('xxx'): TEXT tipo laukas xxx
  • $table->integer('xxx'): INT(11) tipo laukas xxx
  • $table->bigInteger('xxx'): INT(18) tipo laukas xxx
  • $table->float('xxx'): FLOAT tipo laukas xxx
  • $table->boolean('xxx'): BOOLEAN tipo laukas xxx
  • $table->date('xxx'): DATE tipo laukas xxx
Maža to, galima nurodyti ir atskirų laukų savybes. Pvz, jei norite pasakyti, kad laukas gali būti NULL, tai atrodys taip:
$table->string('title', 100)->nullable();
Kad būtų uždėtas UNIQUE raktas:
$table->string('email', 100)->unique();
Kad būtų uždėtas INDEX indeksas:
$table->integer('user_id')->index();
 

Kaip panaudoti migraciją?

Ok, sukūrėme migracijos failą su funkcija up() (funkcija down() bus aptarta žemiau), kaip dabar jį paleisti ir panaudoti? Komandinėje eilutėje įrašome štai ką:
php artisan migrate
Paleidus šią komandą, vykdomas toks veiksmas - Laravel pagalbininkas Artisan pažiūri į katalogą databases/migrations ir paleidžia visų ten esančių failų (jų vėliau gali būti daugiau nei vienas) funkcijas up() vieną po kito. Taigi, po mūsų sukurto failo migracijos paleidimo bus sukurta lentelė pages.

Taip pat komanda php artisan migrate į duomenų bazės lentelę migrations įrašo faktą, kad tokie ir tokie failai buvo apdoroti, tad antrą kartą leidžiant migraciją - jie jau nebus liečiami.

Svarbus dalykas - kuriant migracijos failą, yra ne tik funkcija up(), bet ir funkcija down(). Ji skirta atšaukti paskutiniam veiksmui.
 

Daugiau nei viena migracija

Sakykime, kad projekto eigoje norime sukurti antrą lentelę ar padaryti kitokius duomenų bazės pakeitimus. Tam sukuriame dar vieną migraciją - lygiai taip pat kaip ir pirmame pavyzdyje, skirsis tik naujas pavadinimas. Payzdžiui, norime sukurti lentelę "users":
  • Komanda: php artisan make:migration create_users
  • Kataloge database/migrations sukurtame faile užpildome funkciją up()
  • Komanda: php artisan migrate
Taip pat vienoje funkcijoje up() gali būti ir daugiau veiksmų - vienoje migracijoje galima kurti kelias lenteles, pridėti laukus, keisti lentelių struktūrą ir t.t.
 

Migracijos atšaukimas

Pats laikas pakalbėti apie funkciją down(). Ji skirta atšaukti šios migracijos veiksmą. Kam to reikia? Sakykime, pamatėte klaidą migracijoje ir norite ištrinti ką tik sukurtą lentelę. Arba su naujais pakeitimais neveikia jūsų funkcijos ir norite atstatyti duomenų bazę į prieš tai buvusią būklę. Taigi, tam nurodome funkcijoje down() visus veiksmus, kurie atstatytų funkcijoje up() padarytus pakeitimus į pradinę padėtį.

Sakykime, jei migracija sukuria lentelę pages, tai funkcijoje down() ji turi būti šalinama:
public function up()
{
  Schema::create('pages', function($table){
    // table fields ...
  });
}

public function down()
{
  Schema::drop('pages');
}
Ir jeigu norite praleisti tą atšaukimo veiksmą, komanda būtų tokia:
php artisan migrate:rollback
Kas įvyksta, ją paleidus? Artisan pažiūri į DB lentelę migrations, paima naujausią eilutę ir suranda jos atitinkamą failą kataloge database/migrations bei praleidžia jo funkciją down().

Svarbu: Komanda "php artisan migrate:rollback" atstato tik vieną paskutinį migracijos veiksmą. Skirtingai nuo komandos "php artisan migrate", kuri apdoroja visas dar neapdorotas migracijas. Jeigu norite atšaukti visas migracijas iki pat pirmos, komanda yra kitokia:
php artisan migrate:reset
 

Timestamps laukai

Labai naudinga Laravel smulkmena - du TIMESTAMP tipo laukai: created_at ir updated_at. Jie yra automatiškai užpildomi ir atnaujinami, kviečiant Eloquent funkciją save(). Kad jie būtų sukurti migracijos metu, yra speciali paprasta funkcija timestamps(), kurią reikia iškviesti prie lentelės laukų pildymo sąrašo:
public function up()
{
  Schema::create('pages', function($table){
    $table->increments('id');
    $table->string('title', 100);
    $table->text('page_text');
    $table->timestamps();
  });
}
Vėliau jų pildyti nereikia, Laravel jų duomenimis rūpinsis automatiškai.

Tiesa, reikia pridurti, kad jei naudosite Eloquent, jūsų lentelėse privalo būti šie timestamps laukai - kitaip saugant įrašą mėtys klaidas. Jei dirbate su lentele, kuri jų neturi ir tikrai žinote, kad jums jų nereikia - tada Modelio faile kaip kintamąjį nurodykite var $timestamps = false:
class Page extends Model
{
  var $timestamps = false;
}
 

Kiti migracijos veiksmai su DB lentelėmis

Be abejo, migracijose galima ne tik kurti ir šalinti lenteles. Schema klasė siūlo gan didelį spektrą funkcijų, čia paminėsiu dažniausiai naudojamas iš jų: Kaip pakeisti lentelę - bendra struktūra:
Schema::table('users', function($table)
{
  // Visi lentelės pakeitimai rašomi čia
});
Kaip pridėti lauką prie jau esamos lentelės:
Schema::table('users', function($table)
{
  $table->string('surname')->after('name');
});
Kaip pašalinti lauką iš jau esamos lentelės:
Schema::table('users', function($table)
{
  $table->dropColumn('surname');
});
Pakeisti lauko pavadinimą:
Schema::table('users', function($table)
{
  $table->renameColumn('surname', 'last_name');
});
Pašalinti visą lentelę:
Schema::drop('users');
 

Seeding: pradinių duomenų užpildymas

Be pradinės duomenų bazės struktūros sukūrimo, Laravel siūlo ir mechanizmą pradiniams duomenims užpildyti. Sakykime, norite užpildyti kokių miestų lentelę, sukurti pirminių vartotojų slaptažodžius, įvesti testinių duomenų. Visam tam galima panaudoti seeds mechanizmą. Kataloge database/seeds yra failas DatabaseSeeder.php, kuriame yra funkcija run() - joje galime įrašyti reikalingų duomenų sukūrimo funkcijas arba kviesti kitus Seeds failus.

Pavyzdys:

class DatabaseSeeder extends Seeder {

  /**
  * Run the database seeds.
  *
  * @return void
  */
  public function run()
  {
    User::create(
      array(
        'email' => 'povilas@phppamokos.lt',
        'password' => Hash::make('saugusslaptazodis'),
      )
    );

  }

}
Ir tada, surašę tuos pradinius duomenis, galime įkelti failą į kokį GitHub ar kitokią sistemą, ir jei reikės projektą perkelti į kokį kitą serverį ar perduoti naujam programuotojui, jis galės tuos duomenis turėti vienos komandos pagalba:
php artisan migrate --seed

Atributas --seed nurodo, kad kartu su migracija reikia praleisti ir seeds kataloge esančius failus. Kodėl sakau failus daugiskaita? Nes, kaip minėjau, iš DatabaseSeeder.php failo galima iškviesti ir kitus failus - tokiu būdu pagal prasmę suskirstant pradinius duomenis:

database/seeds/DatabaseSeeder.php:

class DatabaseSeeder extends Seeder {

  public function run()
  {
    $this->call('UsersTableSeeder');
    $this->call('CategoriesTableSeeder');
  }

}

database/seeds/UsersTableSeeder.php:

class UsersTableSeeder extends Seeder {

  public function run()
  {
    User::create( ...
  }

}
Taip pat kartais patogi komanda, kuri sukuria visą duomenų bazės struktūrą iš naujo, trindama visas senas lenteles ir duomenis, t.y. migracijos įvykdomos iš naujo:
php artisan migrate:refresh --seed
Arba galite iškviesti vien tik Seeds, be migracijų:
php artisan db:seed
 

Praktika: krepšinio aikštelių duomenų bazės sukūrimas ir užpildymas

Praktikos skyrelyje šiandien pabandykime perkelti visą mūsų nedidelę duomenų bazę į migrations ir seeds struktūras. Tiksliau, čia dirbsime su lentelėmis types, cities ir courts, dar bus lentelė users bet apie ją kalbėsime vėliau, kai mokysimės prisijungimo ir autentifikacijos mechanizmo kitoje pamokoje.

Taigi, pradėkime - sukuriame migracijas ir pradinius duomenis lentelei types:

php artisan make:migration create_types
class CreateTypes extends Migration {

  public function up()
  {
    Schema::create('types', function($table){
      $table->increments('id');
      $table->string('title', 100);
    });
  }

  public function down()
  {
    Schema::drop('types');
  }

}
Ir dabar - seeding. Užpildykime lentelę reikalingais aikštelių tipais: "lauko aikštelė", "sporto salė", "privati aikštelė":
class DatabaseSeeder extends Seeder {

  public function run()
  {
    Eloquent::unguard();

    Type::create(
      array(
        'title' => 'lauko aikštelė',
      )
    );
    Type::create(
      array(
        'title' => 'sporto salė',
      )
    );
    Type::create(
      array(
        'title' => 'privati aikštelė',
      )
    );

  }

}
Beje, eilutė Eloquent::unguard() laikinai išjungia fillable laukų apsaugą ir leidžia sukurti eilutes su create() funkcija vietoje save().
 

Namų darbai

Tradiciškai, namų darbams paliekame tai, ko neužbaigėme skyrelyje "praktika" - taigi, sukurkite migracijas lentelėms cities ir courts. Norėsite pasitikrinti - parsisiųskite migracijų failus iš čia.


(c) 2015-2018. Visais klausimais kreipkitės povilas@laraveldaily.com