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

“Mikro” Ufak PHP Framework

composer require yidemir/mikro

komutuyla ilgili dizine projeyi dahil ettiğiniz taktirde fonksiyonlar otomatik olarak işlenecektir.

 Özellikler

  • Basit şifreleme/şifre çözme
  • Veritabanı fonksiyonelliği (oluştur, oku, güncelle, sil, sorgula)
  • Sayfalama (dizi sayfalama/veritabanı ilintili sayfalama)
  • Basit talep işleme (request)
  • Yanıt (response) işlemleri (html, json, yönlendirme)
  • Rotacı (rota metodları, gruplama, kaynak işleme)
  • Doğrulama (basit validasyon işlemleri, form validasyonu)
  • Görünüm (view) işleme (Basit ama kullanışlı gövde ve blok işlemleri)

 Neden ihtiyaç duyayım?

Orta çaplı ya da büyük bir projede, projeden bağımsız servis geliştirmek için idealdir.

 Nasıl kullanırım?

Evvela bir index.php sayfası oluşturalım ve yazmaya başlayalım:

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

view\path(__DIR__ . '/views'); // görünüm dosyalarının bulunacağı dizin
crypt\secret('foobar'); // şifreleme kullanılacaksa belirlenmesi gereken gizli anahtar

route\get('/', function() {
    $posts = db\table('posts')->get('where comment_count=? and is_published=1', [5]);
    return response\json($posts);
});

route\get('/show/:id', function($id) {
    $post = db\table('posts')->find($id);
    return response\json($post);
});

route\post('/', function() {
    $values = request\all();
    $validator = validator\validate($values, [
        'title' => 'required|maxlen:255',
        'body' => 'required|minlen:5',
        'created_at' => 'required|time',
        'is_published' => 'nullable'
    ]);

    if ($validator->fails) {
        return response\json([
            'message' => 'Form verileri geçersiz',
            'errors' => $validator->errors,
            'status' => 422
        ], 422);
    }

    db\table('posts')->insert($validator->values);
    return response\json([
        'message' => 'Gönderi başarıyla eklendi',
        'code' => 200
    ]);
});

Veya daha karmaşık bir uygulama yazacaksanız rota gruplama ve rota kaynağı oluşturma özelliğini kullanabilirsiniz.

route\get('/', 'AppControllersHomeController@index');

route\group([
    'path' => '/admin',
    'namespace' => 'AppControllersAdmin\',
    'middleware' => ['check_admin_callback']
], function() {
    route\get('/', 'DashboardController@index');
    route\resource('/posts', 'PostController');
    route\resource('/categories', 'CategoryController');
});

Daha detaylı bilgiler için kaynak kodlarına göz atabilir ve examples dizinini inceleyebilirsiniz. Dökümantasyon için henüz hazır değil.

İyi çalışmalar

Kategoriler
Teknik

PHP’de Geriçağırım İşlevleri (Fonksiyon ve Yöntemler) (1)

Geriçağırım (callback) işlevleri (functions) aslında PHP’de bildiğimiz fonksiyonları ifade eder. PHP’nin 5.4 sürümünden itibarent bir callable türü ile kendilerini belli ederler.

PHP’de iki çeşit işlev (fonksiyon) vardır. Birincisi adı sanı belli olan işlevler, ikincisi anonim işlevler. Adı sanı belli dediğimiz işlevler, şu ana kadar bildiğimiz kullandığımız fonksiyonlardır. Bunları çağırmanın/kullanmanın birden fazla yöntemi vardır. Birazdan bunları işleyeceğiz. İkinci olan anonim işlevler ise, fonksiyonlarla hemen hemen aynı olup, fonksiyonlar gibi tanımlanmaz. Fark olarak anonim işlevler bir değişkene atanabilir. (en basit örneğiyle, bunun dışında farklı şeylere de yarar, göreceğiz).

PHP’de bir değerin çağırılabilir olup olmadığını is_callable işleviyle kontrol edebiliyoruz. İki tane örnek verelim:

function selam_ver(string $isim): string
{
    return "Selamlar sevgili $isim";
}

var_dump(is_callable('selam_ver')); // true

$selamVer = function(string $isim): string {
    return "Selamlar sevgili $isim";
};

var_dump(is_callable($selamVer)); // true
var_dump(is_callable('isset')); // true çünkü böyle bir fonksiyon zaten var, PHP'de öntanımlı
var_dump(is_callable('herhangi_fonksiyon')); // false, çünkü herhangi_fonksiyon adında bir fonksiyon tanımlamadık

Buradan doğru, bir işlevin (fonksiyonun) tanımlı olup olmadığını veya bir değişkenin işlev olup olmadığını öğrenebiliyoruz. Aynı zamanda bir sınıf içerisindeki statik bir işlevin var olup olmadığını da öğrenebiliriz:

class Selamlayici
{
    public static function selamla(string $isim): string
    {
        return "Selamlar sevgili $isim";
    }
}

var_dump(is_callable('Selamlayici::selamla')); // true

Peki sınıf dinamikse nasıl öğreneceğiz? Şöyle:

class Selamlayici
{
    public function selamla(string $isim): string
    {
        return "Selamlar sevgili $isim";
    }
}

var_dump(is_callable([new Selamlayici, 'selamla'])); // true

Dinamik bir sınıfta dize içerisindeki ilk eleman sınıfın örneğini, ikinci eleman ise yöntemi (metodu) bulundurur.

Şimdi, buraya kadar bazı şeylere tamam dedik. Neyin ne olduğunu nasıl öğreneceğimizi anladık. Peki bunları nasıl kullanacağız. İşlevleri kullanmanın en basit yolu, herkesin bildiği gibi selam_ver('Ali') kodu gibidir. Ama PHP’de işlevleri kullanıp çağırabilmek için birden fazla yöntem vardır. Hemen görelim:

selam_ver('Ali'); // "Selamlar sevgili Ali"
$selamVer('Renas'); // "Selamlar sevgili Renas"
Selamlayici::selamla('Deniz'); // "Selamlar sevgili Deniz"
$selamlayici = new Selamlayici;
$selamlayici->selamla('Nar'); // "Selamlar sevgili Nar"

Bu gördüğümüz klasik bir biçimde işlevleri çağırmamıza yarar, birçoğumuz bilir. Başka hangi yöntemler var işlevleri çağırmak için? İlk iki tanesi call_user_func ve call_user_func_array işlevidir. Bunlar tanımlı bir işlevi bir işlev aracılığı ile çağırır. Görelim:

function parametresiz_selamlama(): string
{
    return 'Selamlar sevgili Django!';
}

var_dump(call_user_func('parametresiz_selamlama')); // "Selamlar sevgili Django!"

Yukarıdaki örnekte, parametresi olmayan bir işlevi parametresiz_selamlama() kodu ile değil, farklı bir yöntemle çağırdık. Peki neden böyle bir şeye ihtiyaç duyduk? Pek çok sebebi olabilir. Bunun normal bir fonksiyon değilde, bir sınıf yöntemi olduğunu varsaydığımızda bunu bir MVC uygulaması geliştirirken kullanabilirdik.

Peki ilk başlarda tanımladığımız parametreli işlevleri nasıl çağıracağız:

// artık var_dump işlevini kullanmadan yazacağım, görsel olarak karışık görünmesin diye

call_user_func_array('selam_ver', ['Ali']); // "Selamlar sevgili Ali"
call_user_func_array($selamVer, ['Renas']); // "Selamlar sevgili Renas"
call_user_func_array('Selamlayici::selamla', ['Deniz']); // "Selamlar sevgili Deniz"
call_user_func_array([new Selamlayici, 'selamla'], ['Nar']); // "Selamlar sevgili Nar"
call_user_func_array('parametresiz_selamlama'); // "Selamlar sevgili Django!"

Yukarıda daha normal biçimde çağırdığımız işlevleri, şimdi başka (call_user_func_array) bir işlev aracılığıyla çağırdık. Anonim bir işlevi şu şekilde de çağırabiliriz:

call_user_func(function(){
    echo 'Hoş geldiniz';
});

Peki, anonim işlevleri gerçek hayatta nasıl kullanacağız? En basit haliyle şunu yazalım:

function ozel_selamlama(string $isim, callable $ozelSelamla): string
{
    return call_user_func_array($ozelSelamla, [$isim]);
    // veya $ozelSelamla($isim); bu ikisi aynı şey
}

ozel_selamla('Ali', function($isim): string {
    return "Bu farklı bir selamlama biçimi sevgili {$isim}!";
}); // "Bu farklı bir selamlama biçimi sevgili Ali!"

Yukarıdaki örnekte, özel bir selamlama mesajı vermek için iki parametreli bir işlev/fonksiyon oluşturduk. İlk parametre selamlanacak kişinin adını alıyor, ikinci parametre ise nasıl selamlanacağını ayarlayan anonim bir işlevi alıyor.

Başka bir örnek daha verelim. Bu sefer daha spesifik bir örnek verelim:

function eklemeli_selamla(string $isim, ?callable $ek = null): string
{
    $selamlama = "Selamlar sevgili $isim";

    if (!is_null($ek)) {
        return $selamlama . $ek($isim);
        // veya:
        // return $selamlama . call_user_func_array($ek, [$isim]);
    }

    return $selamlama;
}

eklemeli_selamlama('Deniz'); // "Selamlar sevgili Deniz"
eklemeli_selamlama('Deniz', function($isim): string {
    return ". Adının $isim olduğunu biliyorum!";
}); // "Selamlar sevgili Deniz. Adının Deniz olduğunu biliyorum!"

Şimdilik bahsedeceklerimiz bunlar. Bu yazının bir devamı daha olacak. Devamı niteliğindeki yazıda ise Closure sınıfı, sınıfların bir işlev olarak çağırılabilmesi ve Reflection sınıfından bahsetmeyi planlıyorum.

İyi çalışmalar.