Takip et

JavaScript, PHP ve MySQL Arasında Gizli Tuzaklar: Vekil Çiftlerin Ötesinde Karakter Sayımı

JavaScript, stringleri dahili olarak UTF-16 kod birimleri olarak saklar. Çoğu emoji, Unicode’un “Supplementary Plane” bölgesinde yer alır ve UTF-16’da ik…

JavaScript, PHP ve MySQL Arasında Gizli Tuzaklar: Vekil Çiftlerin Ötesinde Karakter Sayımı

Günümüz web uygulamalarında kullanıcı deneyimi ve veri bütünlüğü kritik öneme sahiptir. Bu bağlamda, kullanıcı girdilerinin doğru bir şekilde işlenmesi ve depolanması, beklenmedik hataların önüne geçmek için elzemdir. Özellikle metin verileriyle çalışırken, “karakter sayımı” gibi basit görünen bir işlem bile JavaScript, PHP ve MySQL gibi farklı teknolojiler arasında gizli tuzaklara yol açabilir. Bu makalede, vekil çiftlerin (surrogate pairs) ötesine geçerek, bu platformlar arasındaki karakter sayımı farklılıklarını, nedenlerini ve bu tuzaklardan nasıl kaçınacağımızı detaylıca inceleyeceğiz.

Karakter Kodlamalarına Derin Bir Bakış: UTF-8 ve UTF-16

Metin işleme dünyasında karakter sayımının karmaşıklığını anlamak için, öncelikle karakter kodlamalarının temellerini kavramak gerekir. Özellikle Unicode standardı ve onun popüler uygulamaları olan UTF-8 ve UTF-16, bu konunun merkezinde yer alır.

Unicode ve Kod Noktaları

Unicode, dünya üzerindeki tüm yazım sistemlerini kapsayan evrensel bir karakter kodlama standardıdır. Her karaktere benzersiz bir sayısal değer atar; bu değerlere “kod noktası” (code point) denir. Örneğin, ‘A’ karakterinin kod noktası U+0041, ‘€’ sembolünün U+20AC, bir emoji olan ‘😂’ karakterinin ise U+1F602’dir. Kod noktaları genellikle onaltılık (hexadecimal) olarak ifade edilir.

UTF-8: Web’in Tercihi

UTF-8 (Unicode Transformation Format – 8-bit), web üzerinde en yaygın kullanılan karakter kodlamasıdır. Değişken uzunluklu bir kodlama şemasıdır; yani bir karakteri temsil etmek için 1 ila 4 bayt arasında değişen bir uzunluk kullanabilir. ASCII karakterleri (U+0000’dan U+007F’e kadar) tek bayt ile temsil edilirken, diğer karakterler 2, 3 veya 4 bayt kullanır. Bu değişken uzunluklu yapısı, hem geriye dönük uyumluluk (ASCII ile) hem de alan verimliliği (özellikle Batı dillerinde) sağlar.

UTF-16: JavaScript’in Dahili Standardı

UTF-16 (Unicode Transformation Format – 16-bit), JavaScript’in dahili olarak stringleri temsil etmek için kullandığı kodlamadır. Bu kodlamada, çoğu karakter (Basic Multilingual Plane – BMP olarak adlandırılan U+0000’dan U+FFFF’e kadar olan aralıktaki karakterler) 2 bayt (bir “kod birimi” – code unit) ile temsil edilir. Ancak, BMP dışındaki karakterler (supplementary characters, örneğin çoğu emoji ve nadir Çince karakterler) iki adet 16-bitlik kod birimi kullanılarak temsil edilir; bunlara “vekil çiftler” (surrogate pairs) denir. Bu, JavaScript’in length özelliğinin neden bazen yanıltıcı sonuçlar verdiğinin ana nedenidir.

JavaScript’in Karakter Sayma Tuzağı: UTF-16 ve Vekil Çiftler

JavaScript’te string uzunluğunu ölçmek için genellikle String.prototype.length özelliğini kullanırız. Ancak bu özellik, beklediğimiz gibi “görsel karakter” sayısını değil, stringin UTF-16 kod birimi sayısını döndürür. Bu durum, özellikle vekil çiftlerle karşılaştığımızda önemli sorunlara yol açar.

String.prototype.length‘in Yanıltıcı Davranışı

JavaScript’te bir stringin length özelliği, o stringi oluşturan 16-bitlik kod birimlerinin sayısını döndürür. BMP içindeki karakterler için bu genellikle görsel karakter sayısıyla eşleşir. Ancak, bir vekil çift (iki adet 16-bitlik kod birimi) ile temsil edilen bir karakter için length özelliği 2 döndürürken, biz tek bir karakter görmeyi bekleriz.

// BMP karakteri: Tek bir kod birimi
const str1 = "Merhaba";
console.log(str1.length); // Çıktı: 7 (Doğru)

// Emoji: Vekil çift (iki kod birimi)
const str2 = "👋"; // El sallayan emoji U+1F44B
console.log(str2.length); // Çıktı: 2 (Yanlış, tek bir karakter bekleriz)

// Birden fazla emoji ve BMP karakteri
const str3 = "Merhaba dünya! 👋🌍";
console.log(str3.length); // Çıktı: 19 (Beklenen: 17)


Yukarıdaki örnekte 👋 ve 🌍 emojileri ikişer kod birimi olarak sayıldığı için toplam uzunluk yanlış hesaplanmıştır.

Doğru Karakter Sayımı: Kod Noktası Yaklaşımı

JavaScript'te doğru karakter sayımı için, stringi kod noktalarına ayırmamız gerekir. Bunu yapmanın modern ve güvenilir yolları vardır:

1. Spread Operatörü (...) ile Array.from: Bu yöntem, stringi Unicode kod noktalarına göre ayrıştırır ve her bir kod noktasını bir dizi elemanı olarak döndürür. Dizinin uzunluğu, gerçek karakter sayısını verir.

const str = "Merhaba dünya! 👋🌍";
    const charArray = [...str]; // ['M', 'e', 'r', 'h', 'a', 'b', 'a', ' ', 'd', 'ü', 'n', 'y', 'a', '!', ' ', '👋', '🌍']
    console.log(charArray.length); // Çıktı: 17 (Doğru)

    // Fonksiyon olarak
    function getTrueLength(str) {
        return [...str].length;
    }
    console.log(getTrueLength("👋")); // Çıktı: 1
    console.log(getTrueLength("Merhaba dünya! 👋🌍")); // Çıktı: 17

2. Intl.Segmenter (Daha Gelişmiş): Bu API, dilbilimsel olarak doğru "grapheme cluster" (kullanıcı tarafından tek bir karakter olarak algılanan birim) sayımı için tasarlanmıştır. Özellikle birleşik karakterler (örneğin, 'é' veya '👨‍👩‍👧‍👦' gibi sıfır genişlikli birleştiricilerle oluşan emojiler) için daha doğru sonuçlar verebilir.

const str4 = "👨‍👩‍👧‍👦"; // Aile emojisi, birden fazla kod noktası ve ZWJ (Zero Width Joiner) içerir
    console.log(str4.length); // Çıktı: 11 (Yanlış)
    console.log([...str4].length); // Çıktı: 4 (Hala yanlış, tek bir karakter gibi görünür)

    const segmenter = new Intl.Segmenter('tr', { granularity: 'grapheme' });
    const segments = [...segmenter.segment(str4)];
    console.log(segments.length); // Çıktı: 1 (Doğru)

    // Fonksiyon olarak
    function getGraphemeLength(str) {
        const segmenter = new Intl.Segmenter('tr', { granularity: 'grapheme' });
        return [...segmenter.segment(str)].length;
    }
    console.log(getGraphemeLength("👨‍👩‍👧‍👦")); // Çıktı: 1
    console.log(getGraphemeLength("Merhaba dünya! 👋🌍")); // Çıktı: 17


Intl.Segmenter karmaşık durumlar için en doğru yaklaşımı sunar.

PHP'de Karakter Sayımı ve UTF-8'in Nüansları

PHP, stringleri dahili olarak bayt dizileri olarak ele alır. Bu durum, özellikle çok baytlı UTF-8 karakterleriyle çalışırken dikkatli olunması gerektiği anlamına gelir. PHP'nin standart string fonksiyonları genellikle bayt tabanlı çalışır.

strlen() vs. mb_strlen()

PHP'de string uzunluğunu ölçmek için iki ana fonksiyon bulunur:

1. strlen(): Bu fonksiyon, verilen stringin *bayt cinsinden* uzunluğunu döndürür. UTF-8 gibi çok baytlı kodlamalarda, bu fonksiyonun döndürdüğü değer gerçek karakter sayısıyla eşleşmez.

$str1 = "Merhaba";
    echo strlen($str1); // Çıktı: 7 (Doğru)

    $str2 = "şifre"; // 'ş' ve 'ç' gibi Türkçe karakterler UTF-8'de 2 bayttır
    echo strlen($str2); // Çıktı: 7 (Yanlış, 5 karakter bekleriz)

    $str3 = "👋"; // Emoji, UTF-8'de 4 bayttır
    echo strlen($str3); // Çıktı: 4 (Yanlış, 1 karakter bekleriz)

2. mb_strlen(): "Multibyte String" (çok baytlı string) fonksiyonlarının bir parçası olan mb_strlen(), karakter setini göz önünde bulundurarak stringin *karakter cinsinden* uzunluğunu döndürür. Bu, UTF-8 gibi kodlamalarla çalışırken doğru sonuçları elde etmek için kullanılması gereken fonksiyondur.

// Varsayılan karakter setini ayarlamak önemlidir
    mb_internal_encoding("UTF-8");

    $str1 = "Merhaba";
    echo mb_strlen($str1); // Çıktı: 7 (Doğru)

    $str2 = "şifre";
    echo mb_strlen($str2); // Çıktı: 5 (Doğru)

    $str3 = "👋";
    echo mb_strlen($str3); // Çıktı: 1 (Doğru)

    $str4 = "Merhaba dünya! 👋🌍";
    echo mb_strlen($str4); // Çıktı: 17 (Doğru)


mb_strlen() fonksiyonuna ikinci bir parametre olarak karakter seti de verilebilir: mb_strlen($string, 'UTF-8'). Bu, mb_internal_encoding() ayarından bağımsız olarak belirli bir kodlama ile çalışmayı sağlar ve daha güvenli bir yaklaşımdır.

PHP'de Karakter Seti Ayarları

PHP'nin çok baytlı string fonksiyonlarının doğru çalışması için, kullanılan karakter setinin doğru bir şekilde ayarlanması çok önemlidir. Bu genellikle mb_internal_encoding("UTF-8"); ile yapılır ve uygulamanın başlangıcında bir kez çağrılmalıdır. Ayrıca, php.ini dosyasında default_charset = "UTF-8" ayarının yapıldığından emin olunmalıdır.

MySQL ve Karakter Setleri: Collation'ların Rolü

Veritabanları, özellikle MySQL, metin verilerini depolama ve işleme konusunda kendi kurallarına sahiptir. Karakter setleri ve collation'lar (sıralama kuralları), string uzunluklarını ve karşılaştırmalarını doğrudan etkiler.

LENGTH() vs. CHAR_LENGTH()

MySQL'de string uzunluğunu ölçmek için iki ana fonksiyon bulunur:

1. LENGTH(): Bu fonksiyon, stringin *bayt cinsinden* uzunluğunu döndürür. PHP'deki strlen() gibi, çok baytlı karakter setlerinde gerçek karakter sayısını vermez.

2. CHAR_LENGTH(): Bu fonksiyon, stringin *karakter cinsinden* uzunluğunu döndürür. Bu, PHP'deki mb_strlen() gibi, çok baytlı karakter setleriyle çalışırken doğru karakter sayısını elde etmek için kullanılması gereken fonksiyondur.

-- Varsayılan karakter seti utf8mb4 olan bir tablo varsayalım
SELECT
    LENGTH('Merhaba') AS byte_len_merhaba,
    CHAR_LENGTH('Merhaba') AS char_len_merhaba,
    LENGTH('şifre') AS byte_len_sifre,
    CHAR_LENGTH('şifre') AS char_len_sifre,
    LENGTH('👋') AS byte_len_emoji,
    CHAR_LENGTH('👋') AS char_len_emoji,
    LENGTH('Merhaba dünya! 👋🌍') AS byte_len_full,
    CHAR_LENGTH('Merhaba dünya! 👋🌍') AS char_len_full;

Çıktı Tablosu:

| byte_len_merhaba | char_len_merhaba | byte_len_sifre | char_len_sifre | byte_len_emoji | char_len_emoji | byte_len_full | char_len_full |
| :--------------- | :--------------- | :------------- | :------------- | :------------- | :------------- | :------------ | :------------ |
| 7 | 7 | 7 | 5 | 4 | 1 | 72 | 17 |

Görüldüğü gibi, CHAR_LENGTH() doğru karakter sayısını verirken, LENGTH() bayt sayısını döndürür. Özellikle Türkçe karakterler ve emojilerde fark belirginleşir.

utf8 vs. utf8mb4: Neden utf8mb4 Kullanmalıyız?

MySQL'in eski utf8 karakter seti, aslında tam bir UTF-8 uygulaması değildir. Sadece 1 ila 3 bayt uzunluğundaki karakterleri destekler. Bu, 4 bayt uzunluğundaki karakterleri (örneğin, çoğu emoji ve bazı nadir Çince/Japonca karakterler) depolayamadığı anlamına gelir. Bu karakterler depolanmaya çalışıldığında ya hata verir ya da '?' ile değiştirilir.

utf8mb4 ise, tam UTF-8 desteği sunar ve 1 ila 4 bayt uzunluğundaki tüm Unicode karakterlerini doğru bir şekilde depolayabilir. Modern uygulamalarda, veritabanı, tablo ve sütun düzeyinde mutlaka utf8mb4 karakter seti kullanılmalıdır.

-- Veritabanı oluştururken
CREATE DATABASE my_database CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- Tablo oluştururken
CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
    bio TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci
);

-- Mevcut tabloyu ve sütunu değiştirmek için
ALTER DATABASE my_database CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE users CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE users MODIFY username VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;


Collation (utf8mb4_unicode_ci gibi), karakterlerin nasıl sıralanacağını ve karşılaştırılacağını belirler. _ci (case insensitive) büyük/küçük harf duyarsızlığı anlamına gelir.

Senkronizasyon Zorlukları ve Çözüm Stratejileri

Frontend (JavaScript), Backend (PHP) ve Veritabanı (MySQL) arasında karakter sayımı konusunda tutarsızlıklar yaşanması, veri bütünlüğü sorunlarına, kullanıcı deneyimi hatalarına ve güvenlik açıklarına yol açabilir.

Frontend-Backend Uyumsuzlukları

Bir kullanıcı JavaScript ile bir metin alanına 10 karakterlik bir limit olduğunu düşünerek giriş yapabilir. Ancak bu metin PHP'ye ulaştığında mb_strlen ile kontrol edildiğinde 10 karakterden fazla çıkabilir (örneğin, JavaScript length 10 iken, metin 5 emoji içeriyorsa mb_strlen 5 döndürür). Veya tam tersi, JavaScript length 10 iken, PHP mb_strlen 10'dan az döndürebilir (eğer metin sadece BMP karakterler içeriyorsa). Bu durum, kullanıcıya yanlış geri bildirim verilmesine veya sunucu tarafında beklenmedik kesilmelere neden olabilir.

Veri Bütünlüğü ve Maksimum Uzunluk Kısıtlamaları

Veritabanında bir VARCHAR(255) sütunu tanımladığınızda, bu 255 *karakter* anlamına gelir (eğer utf8mb4 kullanılıyorsa). Ancak frontend'de JavaScript length ile kontrol ettiğinizde, vekil çiftler nedeniyle 255 karakterden daha az metin girilmesine izin verebilirsiniz. Ya da tam tersi, JavaScript'in yanlış sayımı nedeniyle veritabanına 255 karakterden *daha fazla* karakter göndermeye çalışabilirsiniz, bu da veri kesilmesine veya hata mesajlarına yol açar.

Tutarlı Bir Karakter Sayma Stratejisi

Bu sorunları aşmak için, tüm katmanlarda tutarlı bir karakter sayma stratejisi benimsemek esastır:

1. Frontend (JavaScript): Kullanıcıya gösterilen karakter sayısını ve maksimum uzunluk kontrollerini yaparken, [...str].length veya Intl.Segmenter kullanarak doğru Unicode karakter sayısını elde edin.

// Kullanıcı girdisi için maksimum 10 karakter
    const maxChars = 10;
    const userInput = "Hello👋"; // length 7, gerçek 6
    if ([...userInput].length > maxChars) {
        // Hata mesajı göster
    }

2. Backend (PHP): Frontend'den gelen verileri işlerken ve veritabanına kaydetmeden önce, mb_strlen($string, 'UTF-8') kullanarak karakter sayısını doğrulayın. Bu, veritabanı kısıtlamalarıyla uyumlu olmasını sağlar.

// Frontend'den gelen veri
    $userInput = $_POST['text_input'];
    $maxChars = 10;

    mb_internal_encoding("UTF-8"); // Veya mb_strlen'e ikinci parametre olarak geçin

    if (mb_strlen($userInput) > $maxChars) {
        // Hata döndür, veri çok uzun
    }
    // Veritabanına kaydet

3. Veritabanı (MySQL): Tüm veritabanı, tablo ve sütunlarınızın utf8mb4 karakter setini kullandığından emin olun. CHAR_LENGTH() fonksiyonunu kullanarak veritabanı tarafında da karakter uzunluklarını kontrol edebilirsiniz, ancak genellikle backend kontrolü yeterlidir.

-- Sütun tanımı
    user_comment VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL

Sonuç

JavaScript, PHP ve MySQL arasındaki karakter sayımı farklılıkları, yüzeyde basit görünse de, derinlemesine incelendiğinde karmaşık ve yanıltıcı tuzaklar barındırır. Vekil çiftler, çok baytlı karakterler ve farklı kodlama standartları, bu platformların string uzunluklarını farklı yorumlamasına neden olur. Bu makalede ele aldığımız gibi, JavaScript'te String.prototype.length yerine [...str].length veya Intl.Segmenter kullanmak, PHP'de strlen() yerine mb_strlen() kullanmak ve MySQL'de utf8 yerine utf8mb4 karakter setini benimsemek ve CHAR_LENGTH() fonksiyonunu kullanmak, bu gizli tuzaklardan kaçınmanın anahtarıdır. Tüm uygulama katmanlarında tutarlı bir Unicode karakter sayımı stratejisi benimseyerek, veri bütünlüğünü sağlayabilir, kullanıcı deneyimini iyileştirebilir ve beklenmedik hataların önüne geçebilirsiniz. Bu konudaki farkındalık ve doğru araçların kullanımı, modern web geliştirmenin vazgeçilmez bir parçasıdır.

SSS (Sıkça Sorulan Sorular)

1. Neden JavaScript'in length özelliği emojileri yanlış sayıyor?

JavaScript, stringleri dahili olarak UTF-16 kod birimleri olarak saklar. Çoğu emoji, Unicode'un "Supplementary Plane" bölgesinde yer alır ve UTF-16'da iki adet 16-bitlik kod birimi (vekil çift) ile temsil edilir. String.prototype.length ise bu kod birimlerinin sayısını döndürdüğü için, tek bir emoji için 2 döndürür.

2. PHP'de strlen() yerine neden mb_strlen() kullanmalıyım?

strlen() fonksiyonu, stringin bayt cinsinden uzunluğunu döndürür. UTF-8 gibi çok baytlı karakter kodlamalarında, bir karakter birden fazla bayt ile temsil edilebilir (örneğin, Türkçe 'ş' veya emojiler). Bu durumda strlen() gerçek karakter sayısını değil, bayt sayısını döndürür. mb_strlen() ise belirtilen karakter setine göre karakter sayısını döndürerek doğru sonucu verir.

3. MySQL'de utf8 ve utf8mb4 arasındaki fark nedir? Hangisini kullanmalıyım?

MySQL'in eski utf8 karakter seti, sadece 1 ila 3 bayt uzunluğundaki UTF-8 karakterlerini destekler. Bu, çoğu emojinin ve bazı nadir Unicode karakterlerinin (4 bayt uzunluğunda olanlar) depolanamaması anlamına gelir. utf8mb4 ise tam UTF-8 desteği sunar ve 1 ila 4 bayt uzunluğundaki tüm Unicode karakterlerini doğru bir şekilde depulayabilir. Modern uygulamalar için her zaman utf8mb4 kullanmalısınız.

4. Frontend, Backend ve Veritabanı arasında karakter sayımı tutarsızlıkları nasıl önlenir?

Her katmanda Unicode karakter sayımını doğru bir şekilde yapacak yöntemleri kullanmalısınız: JavaScript'te [...str].length veya Intl.Segmenter, PHP'de mb_strlen($string, 'UTF-8') ve MySQL'de utf8mb4 karakter seti ile CHAR_LENGTH() fonksiyonu. Ayrıca, tüm katmanlarda aynı maksimum karakter uzunluğu limitlerini uygulayarak tutarlılığı sağlamalısınız.

5. Sadece İngilizce karakterlerle çalışıyorsam bu sorunlar beni etkiler mi?

Eğer uygulamanız sadece temel ASCII karakterlerle (İngilizce harfler, sayılar, temel semboller) sınırlıysa, bu sorunlar sizi doğrudan etkilemeyebilir. Ancak, kullanıcıların emoji kullanma veya farklı dillerde metin girme potansiyeli varsa (ki çoğu modern web uygulamasında bu geçerlidir), bu konuları göz önünde bulundurmanız şiddetle tavsiye edilir. Gelecekteki genişlemeler veya uluslararasılaşma durumlarında sorun yaşamamak için baştan doğru yaklaşımları benimsemek en iyisidir.

Yorumlar
İçeriği beğendiniz mi? Bir tartışma başlatın veya görüşlerinizi paylaşın.
Yorum Yaz

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

E-posta Bülteni
Yazılım Topluluğuna Katılın
En son güncellemeleri, yaratıcı ipuçlarını ve özel kaynakları doğrudan e-posta kutunuza alın. Tasarım ve inovasyonun geleceğini birlikte keşfedelim.