M. Niyazi Alpay
M. Niyazi Alpay
M. Niyazi Alpay

Çok küçük yaştan itibaren bilgisayar sistemleriyle ilgileniyorum ve 2005 yılından beri programlama ile uğraşıyorum, PHP, MySQL, Python, MongoDB ve Linux konularında bilgi sahibiyim

 

about.me/Cryptograph

Laravel ve MongoDB Entegrasyonu

Merhaba, iş yoğunluğum sebebiyle uzun zamandır blog yazamıyordum. Bu yazımda Laravel projenizde MongoDB veritabanını nasıl kullanırız onu anlatacağım.

Laravel varsayılanda MySQL desteği ile kuruluyor. Bunun yanı sıra config/database.php dosyasında PostgreSQL, SQLite ve SQL Server desteklerinin olduğunu görüyoruz. Veritabanı seçimi, projenin en başında, proje büyüklüğüne, veriye ne kadar sıklıkla ulaşmak isteyeceğimize ve ileride veri boyutu yükseldiğinde ihtiyaç halinde rahatça scaling yapabileceğimiz bir şekilde seçmek daha doğru olacaktır.

Yapmış olduğum projenin ilerleyen safhalarında veri boyutunun artacağını ön gördüğümüz için bu sebeple de veritabanı tarafında yükü daha da azaltmak ve problem yaşanması durumunda çözüm üretilene kadar kullanıcıların bunu en hafif şekilde hissedebilmesi için horizontal scaling yapısına uygun bir veritabanı seçmeye karar verdik.

Burada biraz araştırma yaptım, MongoDB, Cassandra vs hangisi Laravel ile daha uyumlu çalışır ve hangisinin resmi bir composer paketi var. Google’da “Laravel with nosql” şeklinde arama yapınca da ilk sırada MongoDB çıkıyor ve MongoDB’nin kendi hazırlamış olduğu composer paketi de var, bu sebeple projeye MongoDB ile ilerlemeye karar verdik.

MongoDB kurulumu ile ilgili olarak burada anlattığım konuyu inceleyebilirsiniz.

Eğer sunucuda (Local bilgisayarınız ya da uzak sunucu fark etmez, genel olarak sunucu diyeceğim) php-mongodb kurulu değilse bunun kurulumunu yapıyoruz. Bu işlem kullanmakta olduğunuz PHP sürümü ya da sunucuya göre değişebilir. Cpanel’de EasyApache ya da Pecl installer üzerinden, Plesk Panelde kullanacağınız PHP sürümüne terminalden pecl ile kurulabir. Ben Ubuntu sunucu üzerine kurulumdan bahsederek devam edeceğim.

apt install php-mongodb -y

Daha sonra Laravel projemizin olduğu dizinde terminalde aşağıdaki komut ile gerekli composer paketini kuruyoruz.

composer require mongodb/laravel-mongodb

Kurulum tamamlandıktan sonra config/database.php dosyasında connections tanımlarının olduğu yere aşağıdaki tanımları ekliyoruz.

'mongodb' => [
        'driver' => 'mongodb',
        'dsn' => env('MONGODB_URI', 'mongodb+srv://kullanıcı adı:parola@veritabanı adresi/veritabanı adı'),
        'database' => env('MONGODB_NAME'),
],

Bu tanımlama sonrasında .env dosyanıza MONGODB_URI tanımlamasına veritabanı adresinizi belirtebilirsiniz. Herhangi bir yetkilendirme yapmadıysanız doğrudan sunucu adresini girebilirsiniz,  MONGODB_NAME tanımlamasına da veritabanı adınızı yazabilirsiniz, .env dosyasında son olarak DB_CONNECTION tanımlamasını mongodb olarak değiştiriyoruz.

Son olarak config/app.php dosyasında providers alanına aşağıdaki tanımlamayı ekliyoruz.

MongoDBLaravelMongoDBServiceProvider::class,

Post Adında yeni bir model oluşturuyoruz.

php artisan make:model Post

Laravelde oluşturulan modeller varsayılanda “IlluminateDatabaseEloquentModel” sınıfından extend edilirler. Model oluşturduktan sonra dosyamızı düzenleyerek bu tanımlamayı değiştirmemiz gerekiyor. Oluşturduğumuz modelleri artık MongoDBLaravelEloquentModel bu sınıftan extend etmemiz gerekiyor. Yani oluşturulan modelin yeni hali aşağıdaki gibi olmalı.

namespace App\Models;
use MongoDB\Laravel\Eloquent\Model;

class Post extends Model { }

Laravelde veritabanı tablolarını oluşturmak için migrationlar ve tablolara varsayılan bir data eklemek için seederlar oluşturuyoruz. MongoDB’de ise tablo yapısı bulunmuyor, kaydedilen her veri döküman olarak adlandırılıyor ve her döküman json formatında tutuluyor. Daha önce ilişkisel veritabanlarıyla ilgilenenlerin bildiği table yapısını burada collection, row yapısını document, column yapısını ise field alır.

Yukarıdaki modeli çağırarak bir create işlemi yapıtğımızda post isimli bir collection yoksa oluşturulacak ve içerisine veri eklenmiş olacak. Ama biz farklı isimde bir collection için işlem yapmak istiyorsak daha önce “protected $table = 'tablo_adi';" şeklinde belirttiğimiz tanımlamayı aşağıdaki haliyle yapıyoruz.

protected $collection = 'blog_posts';

Yukarıda anlattığıma bir açıklık getirmek istiyroum; veritabanında post adında bir collection (SQL veritabanlarında tablo) bulunmuyor ve biz buraya veri eklemek istiyoruz. Migration oluşturmadan direkt olarak bu collection varmışçasına insert işlemi yapabiliriz. Bu işlem sonrasında collection oluşturulmuş olacaktır. Ancak yine de migration çalıştırabiliriz. Bu durumda migration bize collectiondaki alanların indexlenmesi için yardımcı olacaktır. Ancak bazı durumlarda veritabanına giderek manuel index oluşturmak gerekebiliyor.

Örnek olarak; category_id, attribute_id, name alanlarınız var. Aynı attribute_id değerine sahip farklı kategorilerde de veriniz var, bu sebeple select işlemini category_id ve attribute_id ile birlikte yapacaksınız. Veritabanında oluşturulan indexler ayrı ayrı oluşturulmuş durumda, bu durumda select işlemi yaptığınızda sorguda ilk bulunan şarta göre indexleri kontrol ediliyor.

db.attribute_list.find({ $and: [ {category_id: 15}, {attribute_id: 25} ] })

Burada her iki değeri de aynı indexte tanımlayarak sorguya hint eklememiz gerekiyor.

db.attribute_list.createIndex({category_id:1, attribute_id:1})

index oluşturuldu, indexin adını görmek için getIndexes() çalıştırabilirsiniz.

db.attribute_list.getIndexes()

category_id_1_attribute_id_1 adında yeni bir indeximiz var şimdi az önceki sorguyu aşağıdaki şekilde yeniden düzenlediğimizde öncekine göre daha hızlı sonuç almış olacağız.

db.attribute_list.find({ $and: [ {category_id: 15}, {attribute_id: 25} ] }).hint('category_id_1_attribute_id_1')

Şimdi tekrar konuya dönecek olursak Laravel tarafında daha farklı bir değişiklik yapmamıza gerek yoktur. Standart olarak yaptığınız her işlemi aynı şekilde yapmaya devam edebilirsiniz. Eğer sessionları veritabanında tutuyorsanız burada  ufak bir sıkıntı çıkıyor “1ff/laravel-mongodb-session” bu composer paketinin kurulumu bu sorunu çözse de ben resmi olmayan bir paketin kurulumunu desteklemiyorum. Dağınık bir sunucu yapısı kullanıyorsanız sessionların ortak bir yerde tutulması gerekir bu durumda da Memcache ya da Redis gibi bir yerde sessionları tutabilirsiniz.

Son olarak Laravel tarafında veritabanı içerisinde bir like sorgusu çalıştırdıysanız ve collection boyutu çok büyükse buradaki sorguya da yukarıda anlattığım hint tanımını yapmamız gerekiyor .

Standart sorgu:

Post::where('title', 'LIKE', '%'.request('search').'%')->get();

Hint tanımı yapılmış sorgu

Post::where('title', 'LIKE', '%'.request('search').'%')->hint('index_adı')->get();

 

Kullanıcı Kayıt ve Oturum Açma İşlemleri

Kayıt ve giriş işlemlerinin standart olarak User isimli model üzerinden yapıldığını varsayarak devam ediyorum.

MongoDB geçişi öncesinde yani standartta User modelinin yapısı aşağıdaki gibi

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use IlluminateNotificationsNotifiable;
use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable {
     use HasApiTokens, HasFactory, Notifiable;
     protected $guarded = 'users';
}

 

Görüldüğü üzere User sınıfı IlluminateFoundationAuthUser sınıfından extend ediliyor. Bu sınıf da IlluminateDatabaseEloquentModel sınıfından extend ediliyor. Dolayısıyla veritabanı bağlantısı doğru sağlanamayacağı için burada da bir değişiklik yapmamız gerekiyor. User sınıfını MongoDBLaravelAuthUser sınıfından extend etmemiz gerekiyor. Bu durumda sınıfın yeni hali aşağıdaki gibi olmalı.

namespace App\Models;

use Illuminate\Database\Eloquen\tFactoriesHasFactory;
use MongoDB\Laravel\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable {
     use HasApiTokens, HasFactory, Notifiable;
     protected $guarded = 'users';
}

Kullanıcı kaydı ve oturum açma işlemleri sorunsuzca sağlanacaktır. Ek olarak API tarafında sanctum ile yetkilendirme kısmında da bir değişiklik gerekiyor. API ile bir işiniz yoksa değişiklik yapmanız gerekmiyor ancak projenin ileriki aşamalarını düşünürsek mobil uygulama gibi bir şey lazım olduğunda API gerekli olacak. Sanctum LaravelSanctumPersonalAccessToken sınıfını kullanıyor ve bu sınıf da her zamanki gibi IlluminateDatabaseEloquentModel sınıfından extend edilmiş. Bu sınıf vendor/laravel/sanctum/src/ dizini altındaki PersonalAccessToken dosyasında tanımlı ancak bu dosyayı düzenleyemeyiz, bir composer paketi kurulumu ya da güncellemesinde burası eski haline geri gelir. Bu dosyayı düzenleyemeyiz ancak bu dosyanın kopyasını app/Models dizinine alarak gerekli değişiklikleri yapabiliriz.

app/Models/ altında PersonalAccessToken.php dosyasını kopyaladık ve diğer sınıflarda yaptığımız gibi extend edildiği Model sınıfını değiştirdik. Dosyanın son hali aşağıdaki gibi:

<?php

namespace App\Models;

use Laravel\Sanctum\Contracts\HasAbilities;
use MongoDB\Laravel\Eloquent\Model;

class PersonalAccessToken extends Model implements HasAbilities
{
    /**
     * The attributes that should be cast to native types.
     */
    protected function casts(): array
    {
        return [
            'abilities' => 'json',
            'last_used_at' => 'datetime',
            'expires_at' => 'datetime',
        ];
    }

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name',
        'token',
        'abilities',
        'expires_at',
    ];

    /**
     * The attributes that should be hidden for serialization.
     *
     * @var array
     */
    protected $hidden = [
        'token',
    ];

    /**
     * Get the tokenable model that the access token belongs to.
     *
     * @return \Illuminate\Database\Eloquent\Relations\MorphTo
     */
    public function tokenable()
    {
        return $this->morphTo('tokenable');
    }

    /**
     * Find the token instance matching the given token.
     *
     * @param  string  $token
     * @return static|null
     */
    public static function findToken($token)
    {
        if (strpos($token, '|') === false) {
            return static::where('token', hash('sha256', $token))->first();
        }

        [$id, $token] = explode('|', $token, 2);

        if ($instance = static::find($id)) {
            return hash_equals($instance->token, hash('sha256', $token)) ? $instance : null;
        }
    }

    /**
     * Determine if the token has a given ability.
     *
     * @param  string  $ability
     * @return bool
     */
    public function can($ability)
    {
        return in_array('*', $this->abilities) ||
            array_key_exists($ability, array_flip($this->abilities));
    }

    /**
     * Determine if the token is missing a given ability.
     *
     * @param  string  $ability
     * @return bool
     */
    public function cant($ability)
    {
        return ! $this->can($ability);
    }
}

PersonalAccessToken sınıfını yeniden oluşturduk ancak Laravel bu yeni oluşturduğumuz sınıfı tanımıyor, dolayısıyla API yetkilendirmemiz halen doğru çalışmıyor. AppServiceProvider içerisinde boot() fonksiyonuna PersonalAccessToken için alias tanımlaması yapacağız.

app/Providers/AppServiceProvider.php dosyasının son hali:

<?php
namespace App\Providers;

use Illuminate\Foundation\AliasLoader;
use Illuminate\Support\ServiceProvider; 

class AppServiceProvider extends ServiceProvider {
     /**
      * Register any application services.
      */
     public function register(): void
     {
         //
     }

      /**
      * Bootstrap any application services.
      */
     public function boot(): void
     {
          $loader = AliasLoader::getInstance();
          $loader->alias(Laravel\Sanctum\PersonalAccessToken::class, App\Models\PersonalAccessToken::class);
     }
}

Laravel projesi artık tamamen MongoDB ile eksiksiz çalışır duruma gelmiş oldu.

Muhammed Niyazi ALPAY - Cryptograph

Senior Software Developer & Senior Linux System Administrator

Meraklı

PHP MySQL MongoDB Python Linux Cyber Security

Bunları da okumak isteyebilirsiniz

Hiç yorum yok

Yorum Bırakın

E-posta adresiniz yayınlanmayacaktır. Zorunlu alanlar * ile işaretlenmiştir