Kategoriler
Teknik

PHP’de Özyinelemeli Closure Kullanımı

Bazen bir çözümü gerçekleştirmek için özyinelemeli bir fonksiyon veya metot kullanma ihtiyacımız olabiliyor. Bu ihtiyaç, benim bir kategoriye ait öğeleri ve o kategorinin alt kategorisine ait öğeleri de içine alacak şekilde Eloquent ORM’de listelemem gerektiğinde ortaya çıktı.

Bunun için, kategori modeline alt kategorilerinin ID’sini getiren bir metot ekledim. İşte bu metodun örneği:

<?php

declare(strict_types=1);

namespace App\Models;

// things...

class Category
{
    public function categories()
    {
        return $this->hasMany(Category::class);
    }

    public function getChildCategoryIDs(): array
    {
        $ids = [];

        $extract = function (Category $category, array &$ids = []) use (&$extract): void {
            $ids[] = $category->id;

            if (isset($category->categories) && is_iterable($category->categories)) {
                foreach ($category->categories as $category) {
                    $extract($category, $ids);
                }
            }
        }

        $extract($this);

        return $ids;
    }
}

Closure Kullanımı: Neden?

Bu örnekte, özyinelemeyi gerçekleştirmek için sınıf içerisinde bir metot oluşturabilirdim. Ancak bu yaklaşımın, metot sayısını artıracağı ve kodun karmaşıklığını potansiyel olarak artırabileceği göz önünde bulundurulduğunda, tüm işlemleri tek bir metot içinde gerçekleştirmeye karar verdim ve Closure’u tercih ettim.

Kategori Yapısının Sebepleri

Bu kategori yapısını, ekstra bir Laravel/PHP paketi kullanmadan ve veritabanı tablo yapısını basit tutmak amacıyla oluşturdum. Alternatif olarak, Laravel’de hiyerarşik kategori paketi olan Baum’u kullanabilirdim, ancak bu durumda ek bir bağımlılığın getireceği potansiyel karmaşıklığı tercih etmedim.

Bu yaklaşımın kısa vadede hızlı ve basit bir çözüm sağladığını, ancak uzun vadede ve artan web trafiğiyle birlikte sorgu optimizasyonu ihtiyacını doğurabileceğini unutmamak önemli. Ancak, bu potansiyel sorun, önbellek kullanımıyla çözülebilir.

Çalışma Şekli:

<?php

public function show(Category $category)
{
    $category->load('categories.categories.categories');
    $ids = $category->getChildCategoryIDs();
    $products = Product::whereIn('category_id', $ids)->get();

    return view('products.index', compact('category', 'products'));
}

CategoryController içerisindeki show metodu, belirli bir kategoriyi ve o kategorinin ürünlerini listeleyen bir işlem gerçekleştirir. ‘Lazy load’ yöntemi kullanarak, hedef kategorinin alt kategorilerini (burada üç dal alıyoruz) elde ederiz ve model içinde tanımladığımız getChildCategoryIDs metodu ile bu alt kategorilerin ID’lerini alırız. Son olarak, bu ID’lere sahip tüm ürünlerin bir listesini WHERE IN sorgusu ile elde ederiz.

Kategoriler
Teknik

Spatie Async Paketiyle Laravel Eloquent ORM’i Kullanmak

Bir servisten yüklü miktarda veri çekip veritabanına işlemem gerekiyordu. Veriyi Guzzle’ın havuz özelliğiyle asenkron şekilde çekip veriyi işlemek içinse Spatie Async paketi kullanmam gerekti. Şu şekilde yol aldım.

Paketi composer ile kuralım:

composer require spatie/async

Kullanımı:

use SpatieAsyncPool;

$pool = Pool::create();

foreach ($things as $thing) {
    $pool->add(function () use ($thing) {
        // Do a thing
    })->then(function ($output) {
        // Handle success
    })->catch(function (Throwable $exception) {
        // Handle exception
    });
}

$pool->wait();

Yukarıdaki örnekte varsayılan ayarlamalar kullanıldı. Aşağıdaki örnekte havuza ekleyeceğimiz öğeleri Task sınıfını kullanarak ekleyip, ayarlamaları kendimiz yapacağız.

use SpatieAsyncPool;

$pool = Pool::create()
    ->concurrency(10)
    ->autoload(__DIR__ . '/vendor/autoload.php');

$pool->add(new FooTask(1));
$pool->add(new FooTask(2));
$pool->add(new FooTask(3));

$pool->wait();

Task örneği ise şöyle olacak:

class FooTask extends SpatieAsyncTask
{
    public function configure()
    {
        $app = require __DIR__ . '/../../bootstrap/app.php';
        $app->make(IlluminateContractsConsoleKernel::class)->bootstrap();
    }

    public function run()
    {
        return AppItem::create([
            'foo' => 'bar'
        ]);
    }
}

Burada önemli olan nokta configure() metodunda gereklilikleri ihtiyaçları çağırmak.

Kolay gelsin.

Kategoriler
Teknik

Laravel’de PHPUnit Testinde ‘Class env does not exist’ Hatası

Laravel (5.8)’de test yazarken

php artisan make:test FooTest

ile testi oluşturup

$response = $this->json('POST', '/api/v1/foo/bar', ['param1' => 'value1']);
$response->assertOk()->assertJsonCounts(1, 'reports');

şeklinde talep testi yapabiliyoruz.

Fakat eğer uygulamanızda Telescope kullanıyorsanız şu hatayı almanız muhtemel:

[message] => Class env does not exist
[exception] => ReflectionException
[file] => /.../vendor/laravel/framework/src/Illuminate/Container/Container.php
[line] => 794

Bu durumda phpunit.xml dosyanıza <env name="TELESCOPE_ENABLED" value="false"/> satırını eklerseniz, Telescope’u test sırasında pasif duruma getirmiş olursunuz. Böylelikle hatayı almayacaksınız.

Kolay gelsin.

Kategoriler
Teknik

Eloquent ORM’de Erişimci ve Mutatörler

Erişimciler (Accessors)

Erişimciler, veritabanından aldığımız bilgileri/verileri önceden tanımlı olarak biçimlendirmemize olanak tanır. İki tane örnek verelim. Öznitelikleri türkçe olarak örneklendireceğim (elbette ki veritabanını tasarlarken ingilizce kullanmak gerekiyor)

Veritabanımızdaki tabloda ad ve soyad adlı iki adet sütun olduğunu varsayalım. Normal şartlarda veritabanındaki ad soyad bilgisini görüntülemek istediğimizde

Ad Soyad: {{ mb_ucfirst($uye->ad) . ' ' . mb_ucfirst($uye->soyad) }}

şeklinde kullanırız. Ama aşağıdaki gibi bir metodu, modelimize tanımlarsak, işimizi, daha kullanışlı hale getirebiliriz.

public function getAdSoyadAttribute()
{
    return mb_ucfirst($this->ad) . ' ' . mb_ucfirst($this->soyad);
}

Artık şöyle kullanılabilir: Ad Soyad: {{ $uye->ad_soyad }}

Önceden tanımladığımız metod, bize veri işlemede yardımcı oluyor. Benzer bir örneği para birimi için verebiliriz. Veritabanımızda fiyat adlı bir sütun olsun ve içeriği ondalık sayı olarak tutsun. 1325.00 olan bir değeri normal şartlarda görüntülemek istediğimizde ondalık hali yerine şunu göreceğiz: 1325. Şimdi bunu biçimlendirelim.

/**
 * @return string
 */
public function getFiyatAttribute()
{
    return number_format((float) $this->fiyat, 2, ',', '.') . ' TL';
}

Şimdi, veritabanındaki fiyat değerini görüntülemek istediğimizde TL’ye uygun biçimde görünecektir. Yani 1.325,00 TL şeklinde.

 Mutatörler (Mutators)

Erişimcilerin aksine mutatörler gösterilen veriyi işlemek yerine gelen/alınan veriyi işlemekte bize yardımcı olurlar.

Aşağıdaki örnekte 1.325,00 TL olarak gelen veriyi veritabanına kaydetmeden önce 1325.00 haline çevireceğiz.

/**
 * @param string $deger
 * @return void
 */
public function setFiyatAttribute($deger)
{
    $this->attributes['fiyat'] = (float) str_replace(
        [' TL', '.', ','], 
        [null, null, '.'], 
        $deger
    );
}

Biraz daha basitleştirirsek, gelen şifre değerini veritabanına kaydetmeden evvel md5 ile şifrelenmiş haliyle kaydetmek isteyelim. (Ama tabii siz bunu yapmayın, bcrypt ile saklayın)

/**
 * @param string $deger
 * @return void
 */
public function setSifreAttribute($deger)
{
    $this->attributes['sifre'] = md5($deger);
}

Zaman Mutatörleri

Eloquent, varsayılan olarak created_at ve updated_at sütunlarını Carbon (tarih/saat sınıfı) örneğine dönüştürür. $dates sınıf değişkeni üzerine zaman özniteliği adını (aşağıdaki örnekte silinme_tarihi ve yorum_tarihi) ekleyerek geçerli kılabiliyoruz.

class Gonderi extends Model
{
    protected $dates = [
        'created_at',
        'updated_at',
        'silinme_tarihi', // deleted_at
        'yorum_tarihi', // commented_at
    ];
}

 Tarih Formatları

Varsayılan olarak tarih formatı Y-m-d H:i:s olan formatını değiştirmek için kullanılır. $dateFormat sınıf değişkeni aracılığıyla değiştirilir.

class Gonderi extends Model
{
    protected $dateFormat = 'U'; // Unix zaman damgası cinsine döndürür
}

Öznitelik Biçimlendirme

$casts sınıf değişkeni sayesinde model özniteliklerini biçimlendirebiliyoruz (cast). Bu özellik oldukça kullanışlı. Örneğin, vertabanında bir kullanıcının yönetici olup olmadığını yonetici_mi sütunu ile belirliyoruz. MySQL’de boolean veri türü olmadığı için işimizi görecek veri türü Tiny Integer oluyor. Eğer yönetici ise 1 değilse 0 değerini alacağını varsayalım. Veriyi elde etmek istediğimizde yonetici_mi‘in 1 değilde, true olarak biçimlendirmek istiyoruz. Şunu yapmak yeterli geliyor:

class Uye extends Model
{
  protected $casts = [
    'yonetici_mi' => 'boolean',
    'son_giris' => 'timestamp',
    'fiyat' => 'float',
    'yas' => 'integer'
  ]
}

$casts dizisinin ilk elemanında değeri boolean veri türüne biçimlendirmesini söyledik. Diğer elemanlarda da farklı işlemler yaptık. son_giris unix zaman damgasına biçimlenecek. fiyat ondalık sayı tipine biçimlenecek. yas sayı tipine biçimlenecek. Desteklenen biçimlerse şu şekilde: integer, real, float, double, string, boolean, object, array, collection, date, datetime, ve timestamp.

 Dizi ve JSON Biçimlenirme

Dizi biçimlendirme, JSON olarak depolanan veri tiplerinde oldukça kullanışlıdır (MySQL’in 5.7 sürümü JSON veri tipini destekliyor, önceki sürümler için TEXT kullanılabilir).

class Uye extends Model
{
  protected $casts = ['bilgiler' => 'array'];
}

$uye = Uye::findOrFail($id);

$bilgiler = $uye->bilgiler;
$bilgiler['yas'] = 25;
$bilgiler['sehir'] = 'İstanbul';
$uye->bilgiler = $bilgiler;

$uye->save();

Kolay gelsin.


Kaynakça

  1. https://laravel.com/docs/5.6/eloquent-mutators
Kategoriler
Teknik

Eloquent ORM’in Tek Başına Kullanımı

Evvela composer paket yöneticisiyle projemizi oluşturalım.

PHP 7 altındaki sürümler için kullanmanız gereken sürüm 5.4 olanı. 5.5 ve üstü olanlar PHP 7 gerektiriyor. Eğer PHP 7 ile çalışacaksanız sürüm belirtmeden gerekliliği indirebilirsiniz.

$ composer require illuminate/database:5.4

Öncelikle bir ‘kapsül’ oluşturacağız. Laravel yaratıcıları Eloquent’in ‘kapsül’ ile çatı dışında kullanılabilmesini amaçlamışlar.

index.php

require __DIR__ . '/vendor/autoload.php';

use Illuminate\Database\Capsule\Manager as Capsule;

$capsule = new Capsule();

$capsule->addConnection([
    'driver'    => 'mysql',
    'host'      => 'localhost',
    'database'  => 'database',
    'username'  => 'root',
    'password'  => 'password',
    'charset'   => 'utf8',
    'collation' => 'utf8_unicode_ci',
    'prefix'    => '',
]);

/*
SQLite bağlantısı için

$capsule->addConnection([
  'driver' => 'sqlite',
  'database' => '/hedef/dizin/database.sqlite',
  'prefix' => ''
]);

Dipnot: addConnection'ın ikinci parametresi bağlantı adını alır.
*/

// Kapsülü statik metodlarla global olarak erişilebilir hale getirir
$capsule->setAsGlobal();

// Eloquent ORM'i başlat
$capsule->bootEloquent();

Kurulumu tamamladık. Şimdi ilk örneğimizi sorgu oluşturucu ile yapalım:

$kullanicilar = Capsule::table('kullanicilar')->where('oylama', '>', 100)->get();

Doğrudan sorgu çalıştırmak istediğimizde select metodunu kullanıyoruz:

$sonuclar = Capsule::select('select * from kullanicilar where id = ?', [$id]);

Şema oluşturucusunu da kullanabiliyoruz. (Migrasyon/taşıma (Migration) için kullanılır)

Capsule::schema()->create('kullanicilar', function ($table) {
  $table->increments('id');
  $table->string('epota')->unique();
  $table->string('sifre');
  $table->unsignedTinyInteger('yas')->nullable();
  $table->boolean('aktif')->default(true);
  $table->timestamps();
});

Son olarak model sınıfımız ile kullanabiliriz. Kendimize has yazdığımız bir MVC iskeletinde kullanmak için oldukça kullanışlı oluyor.

class Kullanici extends \Illuminate\Database\Eloquent\Model {}

$kullanicilar = Kullanici::where('oylama', '>', 1)->get();

Kolay gelsin.


Kaynakça