Laravel Migration İşlemleri ve Bazı Hatalar
Herkese merhaba;
Bu hafta Laravel ile “migration” işlemlerinden ve bazı sık karşılaşılan hatalardan bahsedeceğim.
Öncelikli olarak migration nedir? Neden kullanıyoruz ? gibi sorulara cevaplar arayalım.
Bu migration olayına .net geliştme yapan arkadaşlar Entity den dolayı bir kulak aşınalığı vardır azda olsa. Migrationlar biz yazılımcılar ile veritabanını birbirinden ayıran bir katmandır diyebilirim. Hem projenin bir başka ortamda terkar ayağa kalmasının kolaylaştırılması. Hem veritabanı mimarisinin versiyon kontrolünün sağlanması hemde SQL e gitmeden proje içerisinde dikkat dağıtmadan veritabanı işlemlerini yapabildiğimiz bir ara katman diyebiliriz. Temel anlamda veritabanımızı projemize uygun yönetmek ve versiyonlama için kullanıyoruz diyebiliriz. Migration lar Laravel projemizde
# database/migrations
klasörü altında bulunurlar. Laravel de ilk projenizi oluşturduğunuz da zaten
create_user_table
create_user_password_reset_table
Migration dosyaları çalıştırılmaya hazır hale geleceklerdir. Bunların içlerini inceleyebilirsiniz. Biz öncelikli olarak bir örnek üzerinden migration işlemlerine başlayalım bilmemiz gereken ilk komut
php artisan make:migration helloWorld
şeklinde burada make:migration’dan sonra tablonun ismi ve durumunu belirten bir isim kullanıyoruz standart olarak böyle bir kullanım geliştirilmiş ve bu isimlendirmeyi lower camel case kelinde yapıyoruz. Örnek ;
php artisan make:migration createPhoneTable
bu komutu çalıştırdırdığınızda çalıştırdığınız zaman damgası ile birlikte _create_phone_model_table isimli migration dosyanızın oluştuğunu göreceksiniz. Dosyanın içi şöyle görünüyor olmalı.
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreatePhoneModelTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('phone_model', function (Blueprint $table) {
$table->bigIncrements('id');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('phone');
}
}
burada bilmemiz gereken ilk kısım Schema detaylarını bir başka makalede anlatacağım ama temel iki tane kullanımı var aslında isminden de anlayacağınız gibi bizim veritabanı şemamızı oluşturduğumuz bir statik sınıf kendisi iki fonksiyonu var sık rastlayacağınız bir tanesi tablo oluşturmak için kullandığımız Create diğeri ise var olan tabloda değişiklik için kullandığımı Table burada biz telefon modellerini tutacağımız bir tablo oluşturmak istediğimiz için “create” metodundan yararlanacağız. Daha sonra veritabanından bildiğimiz türler ile devam ediyoruz tüm türlere buradan ulaşabilirsiniz. Ben burada birkaç tanesinden bahsedeceğim. Şöyle bir senaryomuz var. Telefon modelleri ve özelliklerini tutacağımız bir tablo yapıyoruz. Öncelikli olarak şu kolonlar olmalı diye düşünüyorum.
- Markası
- Modeli
- Üretim Yılı
- Sürümü
- Platform
gibi temel özellikleri baz alıyorum. Şimdi işlemlerimize devam edelim.Up fonksiyonumuz migration çalıştığında çalıştırılacak olan fonksiyonumuz. Son düzenlemeden sonra “up” şöyle görünüyor olmalı;
Schema::create('phone_model', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('brand')
->comment('Telefonun Markası');
$table->string('model')
->unique()
->comment('Telefonun modeli');
$table->date('year')
->comment('Telefonun Üretim Yılı');
$table->float('version')
->comment('Telefonun versiyonu');
$table->enum('platform',[1,2])
->comment('Telefonun Platformu: 1 Android , 2 IOS ');
$table->timestamps();
});
şimdi buraya birazcık açıklık getirelim. Id kolonumuz bigIncrements tipinde tanımlanmış aksi belirtilmediği durumlarda autoincrement=true olarak gelir arkadaşlar başka bir big integer tanımlamak istediğiniz de örneğin burada versiyonu bigIncrements tanımlamış olsaydık şöyle bir hata ile karşılaşırdık.
SQLSTATE[42000]: Syntax error or access violation: 1075 Incorrect table definition; there can be only one auto column and it must be defined as a key
bu ne demek bir tabloda birden fazla otomatik artan kolon bulunamaz. Eğer ikinci bir bigIncrements tanımlama yapacaksanız bunun iki yöntemi var
- Yöntem
$table->bigIncrements('version')->autoIncrement(false);
2. Yöntem
$table->unsignedBigInteger('version',false);
böylelikler bir hatadan ve o hataya ait çözümden de bahsetmiş olduk. Şimdi senaryomuza devam ederek açıklamaya devam edelim. String bildiğiniz gibi varchar’a denk geliyor SQL deki eğer uzunluk belirtmez veya başka bir ayar yapmazsanız başlangıçta 255 olarak geliyor. uzunluğu ise
$table->string('brand',100);
şeklinde belirtebilirsiniz. Diğer özelliğimiz “comment” bu ilgili kolona açıklama girdiğimiz kısım her ne kadar siz kullanıyor olsanız da hala sektörümüzde açıklama satırları ne yazıkki kullanılmıyor. Takım arkadaşlarımızdan anlayabilmesi açısından bir not bırakıyoruz. Diğer Özelliğimiz “unique” bu ismindende anlaşıldığı gibi bu kolonda ki her veriden sadece bir adet olabileceğini belirten kısım aynı isimde bir den fazla kayıt eklemeyi denerseniz. SQL hata verecektir. Date üzerinde çok açıklama gerekmeyen bir konu veritabanında tarih tutacağımız anlamına gelen tip aynı şekilde float da yine kesirli sayı tipleri için kullandığımız bir tip. Diğer tipimiz “Enum” burada dizi içerisinde ki değerlerden birinin seçilmesi gerektiğini belirtiyoruz. Yine kayıt esnasında burada tanımlanmamış bir değer girdiğinizde hata ile karşılaşırsınız. Laravel’in bize kazandırdığı bir diğer fonksiyon ise “timestamps” bu bizim tablomuza otomatik olarak bir created_at ve updated_at kolonlarının tanımlanmasını sağlar. Buraya kadar bir sorun yaşamadıysanız şimdi migration ı çalıştırabiliriz demektir. Şu kodu çalıştırın.
php artisan migrate
yaklaşık olarak şöyle bir çıktı görmelisiniz.
Migrating: 2020_03_28_201311_create_phone_model_table
Migrated: 2020_03_28_201311_create_phone_model_table (0.04 seconds)
bu çıktı ile karşılaştıysanız işlem başarılı demektir. Bir hata alıyorsanız yorumlarda sorabilirsiniz.
Senaryoya devam edecek olursak şuan telefon modelleri tablomuz hazır fakat aklıma sonradan yeni güncellemelerin geldiğini varsayalım örneğin telefon markalarını başka bir tablodan getirmek istedik ve bir anahtar ile işaretlemek istedik. Bu durum için öncelikle telefon markaları için bir migration daha oluşturmamız gerekiyor.
php artisan make:migration createPhoneBrands
ardından ek içeriklerimizi ekliyoruz. Markanın Adı , Logosu , Tanıtım yazısı gibi seçenekleri olsun. Şöyle görünüyor olmalı;
Schema::create('phone_brands', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('name',100);
$table->string('logo_url');
$table->text('description');
});
burada timestamp istemediğimiz için kaldırdık. Bu tablomuzuda oluşturuyoruz.
php artisan migrate
...
Migrating: 2020_03_28_201558_create_phone_brands
Migrated: 2020_03_28_201558_create_phone_brands (0.02 seconds)
evet buraya kadar çok başarılı fakat model tablosunda bir değişiklik yapmamız gerecek önceden String olan marka kolonunu artık burada ki id ile eşleyeceğiz. bunun için yeni migration oluşturup gerekli değişiklikleri yapıyoruz.
php artisan make:migration changeBrandColumnTypeAndAddForeignKey
isimlendirmeye takılmayın siz kendi anlayacağınız şekilde de isim verebilirsiniz.İsimlendirme standartına uyun yeter. Create olarak belirtmediğimiz için dosyamızın up fonksiyonu boş olarak geldi. Şimdi gerekli değişiklikleri yapalım. Up fonksiyonun içi
Schema::table('phone_model',static function(Blueprint $table){
$table->bigInteger('brand')->change();
$table->foreign('brand')
->references('id')->on('phone_brands');
});
ve ardından
php artisan migrate
ve bir başka hatamızı daha aldık.
Changing columns for table "phone_model" requires Doctrine DBAL; install "doctrine/dbal".
aslında çözüm basit zaten içerikte de yazıyor. Veritabanında değişiklik yapabilmek için doctrine/dbal paketini yüklemeliyiz.
composer require doctrine/dbal
ardından tekrar migrate işlemini yapalım. Eğer Mysql ≤ 5.7 kullanıyorsanız bu seferde şöyle bir hata almanız muhtemeldir.
Unknown database type enum requested, Doctrine\DBAL\Platforms\MySQL57Platform may not support it.
tabii ki bir çözümü var şöyle bir ekleme yapacağız. Schema sınıfımızın üstüne şunları ekliyoruz.
DB::connection()
->getDoctrineSchemaManager()
->getDatabasePlatform()
->registerDoctrineTypeMapping('enum', 'string');
bu son eklemeden sonra tekrar migrate ediyoruz. Haydaaa yine bir hata
SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax;
bu sefer neden olduğunu yorumlayalım. Yine sorun
$table->bigInteger('brand')->change();
tanımlamasında bigInteger autoincrement olarak tanımlanmasından kaynaklanıyor ayrıca belirtmek istediğim bir durum daha var ilişki kuracağımız tiplerinde aynı olması gerektiğini unutmayın. Şöyle bir durumda da hata verecekti.
Schema::table('phone_model', function(Blueprint $table){
$table->integer('brand',false)
->charset(null)
->change();
$table->foreign('brand')
->references('id')->on('phone_brands');
});
Hata ise
SQLSTATE[HY000]: General error: 1215 Cannot add foreign key constraint
Çünkü brands tablomuz da brand Big Integer olarak tanımlanmıştı. burada ise model tablomuzdaki brand integer olarak tanımlanmış. Türler uyuşmadığı için bu durumla karşılaştık. Gelelim asıl olması gereken çalışan versiyonuna son durum şu şekilde olmalı
public function up()
{
DB::connection()
->getDoctrineSchemaManager()
->getDatabasePlatform()
->registerDoctrineTypeMapping('enum','string'); Schema::table('phone_model', function(Blueprint $table){
$table->unsignedBigInteger('brand',false)
->charset(null)
->change();
$table->foreign('brand')
->references('id')
->on('phone_brands');
});
}
bu şekilde tekrar migrate ettiğimizde artık şu iletiyi görebiliyor olmalıyız.
Migrating: _change_brand_column_type_and_add_foreign_key
Migrated: _change_brand_column_type_and_add_foreign_key (0.09 seconds)
Artık tablolarımız bir birine bağlı ve istediğimiz değişiklikleri yapmış olduk. Tabloya bir kolon daha eklemek istediğimizde ise bu sefer change değilde after veya before metodunu kullanıyoruz. Bir örnekte bu konudan yapalım. Mesela modeli ön yüzde göstermek istemiyorsak bir bayrak koyabilir ve boolean tanımlayabiliriz. Böylelikle 1 olanlar ön tarafta gösterilebilir. Bir tane migration oluşturuyorum.
php artisan make:migration addStatusColumnToPhoneModel
ve şöyle düzenliyoruz.
public function up()
{
Schema::table('phone_model', static function (Blueprint $table) {
$table->boolean('status')
->after('platform')
->default(1);
});
}
Burada “Status” diye bir bayrak ekledik ve tipini “Boolean” olarak belirledik ve bu kolonun platform kolonundan sonra ekliyoruz varsayılan olarak da 1 olduğunu belirtiyoruz. Vee
Migrating: _add_status_column_to_phone_model
Migrated: _add_status_column_to_phone_model (0.05 seconds)
buda başarıyla çalıştı. Daha sonra geri alma ve diğer özelliklerden de bahsediyor olacağız. Konuyu burada daha fazla uzatmadan bitiriyorum.
Herkese İyi ve Hatasız Kodlamalar :)
Tolga Karabulut
tolga.karabulut@medianova.com
Medianova CDN | Senior PHP Developer