tarihinde yayınlandı Yorum yapın

Data Transfer Objects Nedir? Güzellikleri Nelerdir? Gerçek Bir Uygulama Örneği Üzerinden

Bir önceki yazımın üzerinden 24 saat geçmeden ikinci yazıyı paylaşıyorum, normalde çok nadirdir bu durum.

Öncelikle Data Transfer Objects veya nam-ı diğer DTO nedir? Hemen Claude’a yazdıralım:

Data Transfer Objects (DTO), farklı yazılım katmanları veya sistemler arasında veri taşımak için kullanılan, genellikle sadece veri içeren ve iş mantığından arındırılmış basit nesnelerdir. DTO’lar, özellikle istemci-sunucu mimarisinde veya mikroservis yapılarında, veritabanı varlıklarının (entity) doğrudan dış dünyaya açılmasını önleyerek güvenliği artırır ve gereksiz veri transferini azaltır. Örneğin, bir kullanıcı nesnesi veritabanında şifre, e-posta doğrulama token’ı gibi birçok alan içerebilirken, DTO sadece isim, soyisim ve profil fotoğrafı gibi API üzerinden paylaşılması gereken alanları taşır. Bu sayede hem performans iyileşir (gereksiz veri taşınmaz), hem güvenlik artar (hassas veriler gizlenir), hem de farklı katmanlar arasında gevşek bağlılık (loose coupling) sağlanarak kod daha sürdürülebilir hale gelir.

CLAUDE AI

Bu Claude’un yaptığı tanım fakat biz Laravel geliştiricileri için Model’de tanımlanan $hidden ve Resource bu açıklamadaki bir çok hizmeti bize sağlıyor. Gelelim benim ihtiyacımın nerede oluştuğuna, örnek kodlarımı biraz sadeleştiriyorum elbette.

Senaryom çok basit, kullanıcı e-posta adresini değiştirdiğinde yeni e-posta adresi ve rastgele bir string ifadeyi array’de saklayıp Cache’e kaydetmek istiyorum.

PHP
$email_change_data = [
    'hash' => Str::random(64),
    'new_email' => $email,
    'created_at' => now(),
];

Cache::set('email_change', $email_change_data, 60 * 60 * 3);

Buradaki problem dizi anahtarlarımı asla unutmamam gerekiyor. Diğer türlü farklı bir yerde email_change önbelleğini sorgularsam elde edeceğim dizide kullandığım anahtarları hatırlamak için tekrar gerisin geri buraya dönmem gerekiyor. İşte tam bu noktada imdadımıza DTO’lar yetişiyor.

Laravel’de aslında istediğimiz varlıklarla bir gün Collection oluşturabilirsek bu DTO mitio falan hiç gerekmeyecek muhtemelen. php artisan make:collection EmailChangeCollection komutu ile bir nevi DTO yapabilir oluyoruz. Sanırım.

Şimdi app dizini içerisinde DTOs diye bir klasör oluşturalım. Ben böyle yapmayı seviyorum en azından. Yukarıdaki problemi çözen DTOmuzu hazırlayalım:

PHP
<?php

namespace App\DTOs;

use Illuminate\Support\Carbon;
use Illuminate\Support\Str;

readonly class EmailVerificationData
{
    public function __construct(
        public string $hash,
        public ?string $new_email,
        public Carbon $created_at,
    ) {}

    public static function create(?string $email = null): self
    {
        return new self(
            hash: Str::random(64),
            new_email: $email,
            created_at: now(),
        );
    }

    public function toArray(): array
    {
        return [
            'hash' => $this->hash,
            'new_email' => $this->new_email,
            'created_at' => $this->created_at,
        ];
    }

    public static function fromArray(array $data): self
    {
        return new self(
            hash: $data['hash'],
            new_email: $data['new_email'],
            created_at: $data['created_at'],
        );
    }
}

Yukarıdaki örnekte de görebileceğiniz üzere dizide tanımladığımız öğelerin veri tiplerini de tanımlamış olduk. Bu sayede hem bu verinin içeriğini hatırlamamıza gerek kalmayacak, IDE’miz bize hatırlatacak aynı zamanda created_at ile Carbon nesnesini rahatlıkla kullanabilir olacağız. create() metodu ile aşağıdaki örnektede görebileceğiniz gibi kaydedilecek verimizi hazırlıyorum. toArray() ve fromArray() metodlarını kullanmayı tercih ediyorum. Bu dönüşümlerle beraber verimin hangi DTO olduğunu IDE ile paylaşıyorum kabaca.

Şimdi ilk hazırladığımız kodu güncelleyelim:

PHP
$email_verification_data = EmailVerificationData::create($email);
Cache::set('email_change', $email_verification_data->toArray(), 60 * 60 * 3);

Çok harika değil mi? Artık verimi DTO ile oluşturdum, diziye çevirip cache’e attık. Şimdi cache’den veriyi alıp kontrol işlemlerine geçelim:

PHP
$email_verification_data = EmailVerificationData::fromArray(Cache::get('email_change'));

İşte cachedeki veri ile DTOmuzu oluşturduk yeniden. Bu örnekte cacheten verinin direkt geleceği varsayılışmıştır, cache’in varlığını öncesinde kontrol etmeniz gerekir veyahut Cache::get('email_change', ['hash' => null, 'new_email' => null, 'created_at' => now()]) gibi bir fallback ekleyebilirsiniz ki cache varlığını önceden kontrol etmenizi tavsiye ederim.

Artık email_verification_data değişkenini isteğimiz doğrultusunda değerlendirebiliriz.

PHP
if($email_verification_data->hash === $incoming_hash) {
  echo $email_verification_data->created_at->toDateTimeString();
}

Bu sayede hem projeniz daha sürdürülebilir bir hale geliyor, hem daha okunabilir hale geliyor hem de type safe olduğundan bir kat daha güvenli hale geliyor.

Böyle veli nimetlerden faydalanmak gerekiyor, lazım olduğunda. Bir başka yazıda görüşünceye dek, selametle.

tarihinde yayınlandı 1 Yorum

Brew ile PHP Memcached Kurulumu

Merhabalar arkadaşlar,

Aslında bu yazı bir nevi kendime bir not. Brew ile PHP kurulumu yaptıktan sonra memcached eklentisini kurarken ne yazık ki bir hata alıyoruz.

Bash
pecl install memcached

Bu kurulum sırasında bir kaç soru promptu ile karşılaşıyoruz. Hepsine enter enter tıklamak ne yazık ki bu kurulumun hata vermesine sebep oluyor. Dip not, ben m3 işlemcili macbook kullanıyorum, dizinler farklı olabiliyor intel işlemcili macbook’larda. Bu prompt’larda aşağıdaki promptta duralım:

Bash
zlib directory [no] :

Bunu gördüğümüz anda zlib dizinimizi elle yazdırmamız gerekiyor:

Bash
zlib directory [no] : /opt/homebrew/opt/zlib

Artık kurulum sağlıklı bir şekilde tamamlanacak. Eğer bu dizini elle girmezseniz ne yazık ki kurulum sırasında aşağıdaki hata alınıyor:

Bash
checking whether to use system zstd library... no
checking for ZLIB... yes, shared
checking for pkg-config... /opt/homebrew/bin/pkg-config
checking for zlib location... configure: error: memcached support requires ZLIB. Use --with-zlib-dir=<DIR> to specify the prefix where ZLIB headers and library are located
ERROR: `/private/tmp/pear/temp/memcached/configure --with-php-config=/opt/homebrew/opt/php/bin/php-config --with-libmemcached-dir=no --with-zlib-dir=no --with-system-fastlz=no --enable-memcached-igbinary=no --enable-memcached-msgpack=no --enable-memcached-json=no --enable-memcached-protocol=no --enable-memcached-sasl=yes --enable-memcached-session=yes' failed

Faydası olur mu bilmem, kendime not alıyorum 🙂

tarihinde yayınlandı Yorum yapın

Scribe Laravel API Dökümantasyonu İçin Geliştirdiğim Auth Middleware Kütüphanesi

Merhaba arkadaşlar,

İlk API servisi geliştirmeye başladığım zaman, tabii tecrübesizim de, neyi nasıl yapmalıyım diye ciddi bir çalışma içerisindeyim. Her şeyi inceliyorum, standartları nelerdir, HTTP 201 yanıtını nedir, ne zaman dönülmeli gib gibi ciddi bir çalışma içerisindeyim. Laravel zaten bu konunda işin standartlarını benim yerime yapıyor ki bu harika.

Fakat bu end-pointleri front-end tarafında geliştirme yapan arkadaşla nasıl paylaşacağım sorunuyla karşılaştım ve adeta duvara çarptım. İlk başta slack üzerinden paylaşıyorum mesajla ama bu çok uzun sürmedi. Çünkü mesajlaşarak olacak gibi değil. Arkasından ben kendim end-pointleri, hangisinin ne işe yaradığını, hangi body ve query parametrelerinin gerekli olduğunu vs. elle yazmaya başladım. Fakat bu da gerçekten olacak gibi değildi. O kadar çok sıkıldım ki iki kelime yazmaya üşenir oldum.

Sonunda bunun için bir çözüm üretilmiştir eminim ki diyerek API dökümantasyonu için paket araştırmaya niyetlendim. Yukarıdaki süreç çok uzun sürmüş gibi hissettirebilir fakat 3-5 gün sürmedi paket arayışına geçmem.

O dönem bir kaç paket inceledim ve içime en çok sinen paket Scribe olmuştur. Kullanmaya başlar başlamaz da iyikim oldu. Bir kaç yıldır kullanıyorum ve %95 tüm ihtiyaçlarımı çok iyi bir şekilde karşıladı. Geçen sene geliştiricilerden dökümantasyon end-pointim için bir password protection istemiştim. Yani dökümantasyon end-pointim herkes tarafından erişilebilir olmasın, bu durum beni ufaktan rahatsız etmeye başlamıştı. Bunu kendin implement edebilirsin demişti.

Aradan geçen bir yılın sonunda bunun için bir yardımcı kütüphane paketledim. Bu kütüphaneyi Github üzerinden inceleyebilirsiniz: oralunal/scribe-auth

Kütüphane kısaca scribe dökümantasyonu için bir middleware oluşturuyor ve bu middleware’i scribe.php config dosyasında tanım, dökümantasyona erişmeye çalıştığınız zaman sizden bir şifre talep ediyor. İşleyişi basit, kullanımı basit ve entegrasyonu basit. Umarım bu paketle dökümantasyon end-pointinizin kısmi güvenliğini sağlayabiliriz.

Şu anda daha fazla geliştirir miyim emin değilim fakat geliştirirsem yetkilendirme yöntemine statik bir şifre ile yapmak yerine buraya alternatif olarak User model’ini de dahil etmeye niyetleniyorum. Bir ara sanırım hazırlayacağım.

Eğer talep olursa Laravel projelerinizin API end-pointleri için nasıl dökümantasyonlar hazırlayacağınızla ilgili bir video hazırlayabilirim. Bu yazıya 150.000 like gelirse, hahahha 😀


tarihinde yayınlandı Yorum yapın

PHPStorm’un Route::prefix Metodu İçin Protected Uyarısı

Bu artık var olan bir sorun değil.

Bir kaç ay önce çıkmaya başladı, sanırım bir güncellemeden sonra bozdular ya da olması gerektiği gibi yaptılar diyebilirim. Bilemedim. Laravel’de Route’larınızı bir prefix altında gruplamak istediğimizde karşılaşıyoruz bu hatayla. Senaryomuz şu şekilde:

PHP
    Route::prefix('/me')->name('me.')->middleware('auth:sanctum')->controller(MeController::class)->group(function () {
        Route::get('', 'show')->name('show');
    });

Bu şekilde kullanımda hiç bir problem yok fakat PHPStorm inatla prefix’in altını çiziyor ve şu uyarıyı veriyor:

Member has protected visibility but can be accessed via ‘__callStatic’ magic method

Bu şekilde warning’leri görmek tabii benim tansiyonu oldukça düşürüyor. Ne zaman hayatımıza dahil oldu tam bilmiyorum fakat korkulacak bir şey yok diyebilirim. Uyarının sebebi ise Router sınıfında prefix metodunun protected olarak yer alıyor olması. Hal böyle olunca dışarıdan prefix metodunu çağırdığımızda Router sınıfındaki __call metodu bu görevi üstleniyor ve RouteRegistrar sınıfında attribute metodu ile hallediyor işini. Laravel Idea‘nın bu konuya en kısa sürede bir çözüm üreteceğine inancım sonsuz, gerçekten bu uyarıları görmeyi sevmiyorum, takıntılıyım diyim, siz anlayın.

Güncelleme (12 Mayıs 2025): Laravel Idea 10.2 güncellemesi ile bu sorun giderildi.

tarihinde yayınlandı Yorum yapın

MySQL Kullanılan Projede ‘schema:dump –prune’ Sonrasında Test’lerin SQLite İle Çalışmaması

Merhaba arkadaşlar,

Bugün Laravel ile geliştirdiğim backend api servisinde ilginç bir durum ile karşılaştım. Bu hatayı tanımlamam gerekirse:

  • Üzerinde çalıştığım projede yüzlerce migration dosyası oluşmaya başladı. Her yeni migration oluşturduğumda database dizini altında yeni oluşturduğum migration dosyasına kaydırmaktan iyice bıkkınlık gelmişti.
  • Bunun için buradan görebileceğiniz üzere migrationları silip bir dump dosyası oluşturdum. Projemde MySQL kullanıyorum.
Bash
# Mevcut veritabanının bir dumpını oluşturur ve migration'ların hepsini temizler.
php artisan schema:dump --prune
  • Bu işlem sonrası migrations dizini uçuyor ve yerime schema isimli yeni bir dizin geliyor. İçerisinde de mysql kullandığım için mysql-schema.sql isimli bir dosya oluşuyor. Buraya kadar her şey harika.

Buraya kadar her şey güzel. Artık daha sade ve temiz bir database dizinim var. Fakat problem testlerimin çalışmasında başlıyor. Testlerimde her zaman veritabanını sıfırlıyorum:

PHP
<?php

use Illuminate\Foundation\Testing\RefreshDatabase;

uses(RefreshDatabase::class);

Evet, ben de Pest kullanıyorum. Test içinse veritabanı olarak olarak SQLite kullanıyorum. Bağlantımda :memory: olarak ayarlı. Problem burada başlıyor. Migration’larımızı sildiğimiz için ve veritabanı dumpımız mysql olduğu sqlite ile veritabanı oluşturulamıyor. Test sonuçlarımda düzenli olarak users tablosu bulunamadı hatası alıyorum. Ufak bir çakallık deneyip mysql-schema.sql dosyamın adını sqlite-schema.sql olarak değiştirsem yedirir miyim diye bir düşündüm, düşükte olsa umut fakirin ekmeğiydi ama tabii ki olmadı 🙂

Bunun için çözümler üretmeye çalışırken mysql dump dosyasını sqlite dumpa dönüştürmek aklıma geldi. Bunun için github’da dumblob/mysql2sqlite reposunu keşfettim. Bu toolu kullanarak mevcut mysql dumpımdan sqlite dump oluşturdum. Bu repodan mysql2sqlite dosyasını projemin ana dizininde oluşturdum ve sonrasında şu komutu çalıştırdım:

Bash
./mysql2sqlite database/schema/mysql-schema.sql > database/schema/sqlite-schema.sql

Voila! sqlite-schema.sql dosyam schema klasörünün altında oluşmuştu. Tabii şimdi test etmeye geldi.

Bash
php artisan test

Artık sağlıklı bir şekilde sqlite-schema.sql dosyasını sağlıklı bir şekilde kullanarak veritabanımı sıfırlayabiliyorum.

Umarım birinize ilaç olabilecek bir yaklaşım sunmuşumdur. Bu yazıyı yazarken neden şu konuları da açıklamam gerektiğini hissettim, bir ara bunlara da değineceğim, kendime not olması açısından:

  • Test nedir? PHPUnit ve Pest nedir?
  • Neden test yazmalıyız?
  • Neden testlerimizde sqlite kullanmalıyız ya da kullanmak zorunda mıyız?

Ölmez sağ kalır isek bir ara yazarız 🙂 Kalın sağlıcakla.

tarihinde yayınlandı Yorum yapın

Laravel’de URL Parametrelerinin Doğrulanması

Laravel, form verilerinin doğrulanmasını kolaylaştıran güçlü ve esnek bir doğrulama sistemine sahiptir. Ancak, bazen URL parametrelerini veya “route” parametrelerini doğrulamanız gerekebilir. Laravel, FormRequest sınıfları ile bu tür senaryoları kolaylıkla işleyebilir.

FormRequest Sınıflarının Kullanılması

Laravel’de HTTP requestlerinin doğrulanması genellikle bir FormRequest sınıfı ile yapılır. Bu, tüm doğrulama kurallarını ve yetkilendirme mantığını tek bir yerde toplamanıza izin verir.

Bir FormRequest sınıfı oluşturmak için Laravel’in artisan komutunu kullanabilirsiniz:

Bash
php artisan make:request VerifyUserRequest

Bu komut, app/Http/Requests/VerifyUserRequest.php adlı yeni bir dosya oluşturur. Bu dosyada iki ana metod bulunur: authorize ve rules.

  • authorize metodunda, isteği yapan kullanıcının bu isteği yapma yetkisi olup olmadığı kontrol edilir.
  • rules metodunda, request verilerinin karşılaması gereken doğrulama kuralları belirlenir.

URL Parametrelerini Doğrulama

Eğer bir URL şu şekildeyse: https://example.com/verify/{id}/{hash}, ve istemcinin bu endpoint’e id ve hash parametrelerini sağlaması gerekiyorsa, bu parametreleri FormRequest sınıfı ile doğrulayabiliriz.

Öncelikle, route tanımımızı belirtelim:

PHP
Route::get('/verify/{id}/{hash}', 'VerificationController@verify')->name('verify')->where('id', '[0-9]+');

Ardından, VerifyUserRequest sınıfımızda rules metodunu aşağıdaki gibi tanımlayabiliriz:

PHP
public function rules()
{
    return [
        'id' => 'required|integer|exists:users,id',
        'hash' => 'required|string',
    ];
}

Bununla birlikte, Laravel bu parametreleri doğrulayabilmek için URL’den gelen parametrelerin ve request verilerinin birleşimini kullanmalıdır. Laravel, varsayılan olarak yalnızca request verilerini doğrular. Bu nedenle, URL’den gelen parametrelerin doğrulanmasını sağlamak için, validationData metodunu FormRequest sınıfımıza eklemeliyiz:

PHP
public function validationData()
{
    return array_merge($this->route()->parameters(), $this->all());
}

Bu metod, route parametrelerini ve request verilerini birleştirir. Artık Laravel, id ve hash parametrelerini URL’den çeker ve belirlediğimiz doğrulama kurallarını uygular. Sonuç olarak, FormRequest sınıfı, route parametrelerini doğrulama ihtiyacımız olduğunda da oldukça kullanışlıdır. Doğru şekilde kullanıldığında, form verilerini ve URL parametrelerini doğrulama işlemlerimizi tek bir yerde organize edebiliriz.

Umuyorum ki bu rehber, Laravel ile URL parametrelerini nasıl doğrulayacağınıza dair size yardımcı olmuştur.

tarihinde yayınlandı Yorum yapın

Laravel’da Foreign Key’ler İçeren Bileşik İndeksler Nasıl Kaldırılır?

Bir Laravel geliştiricisi olarak, veritabanı tabloları arasındaki ilişkileri yönetirken foreign key’ler ve indekslerle sıklıkla karşılaşıyoruzdur. Ancak, bileşik bir indeks oluştururken foreign key’leri içermesi durumunda bazı zorluklar yaşanabilir. Bu yazıda, Laravel’da foreign key’ler içeren bir bileşik indeksi nasıl kaldıracağımızı göreceğiz.

Bileşik indeksler birden çok kolonda oluşturulan indekslerdir ve genellikle veritabanı sorgularının performansını artırmak için kullanılırlar. Bununla birlikte, foreign key’lerle birlikte kullanıldıklarında bazen karışık durumlar ortaya çıkabilir.

Örneğin, contact_tags adlı bir tablomuz olsun ve bu tabloda tag_id ve contact_id adında iki foreign key’imiz olsun. Bu iki sütunu içeren bir bileşik indeks oluşturduk (tag_contact_unique adlı). Ancak, bu indeksi kaldırmak istediğimizde bir problemle karşılaşıyoruz.

İşte bu problemi çözmenin bir yolunu aşağıda bulabilirsiniz:

PHP
public function down() {
    Schema::table('contact_tags', function (Blueprint $table) {
        // Bu biraz garip gelebilir, ancak öncelikle eksik indeksleri eklememiz gerekiyor:
        $table->index('tag_id', 'tag_id_foreign');
        $table->index('contact_id', 'contact_id_foreign');
        // Şimdi bu "down" komutunun ana kısmına geçerek benzersiz indeksi kaldırabiliriz:
        $table->dropUnique('tag_contact_unique');
    });
}

Bu kodda neler olduğuna bir göz atalım:

  1. Öncelikle, tag_id ve contact_id sütunlarına indeks ekliyoruz. Burada dikkat edilmesi gereken önemli nokta, indeks isimlerinin foreign key isimleriyle aynı olması gerektiğidir. Bu, Laravel’in foreign key’leri ve indeksleri birbirinden ayırt etmesine yardımcı olur.
  2. Ardından, tag_contact_unique adındaki bileşik indeksi kaldırıyoruz. Bu, tag_id ve contact_id sütunlarında oluşturduğumuz benzersiz bileşik indeksi kaldırır.

Sonuç olarak, foreign key’ler içeren bir bileşik indeksi kaldırmak için bu yöntemi kullanabiliriz.