13 Ekim 2026 - 09:27
PHP ve MySQL'de Türkçe Karakter Bozulması: utf8mb4 Teşhisi Görseli
Veritabanları

PHP ve MySQL'de Türkçe Karakter Bozulması: utf8mb4 Teşhisi

Yorumlar

Veritabanında “Çağrı” yazması gereken yerde farklı semboller görüyorsanız hemen bütün tablolara ALTER TABLE uygulamayın. Metin veritabanına yanlış kaydedilmiş olabilir; doğru kayıt, PHP bağlantısında veya tarayıcıya gönderilirken de yanlış yorumlanıyor olabilir. Görüntü aynı derecede bozuk olsa da yapılacak işlem farklıdır.

İşe tek bir kayıtla başlayın. Kaydın uygulamadaki görüntüsünü, veritabanından okunan baytlarını ve yeni bir deneme kaydının davranışını karşılaştırın. Eski kayıtlar bozuk, yeniler doğruysa geçmişteki bir aktarım sorunu olasıdır. Yalnızca bir PHP sayfası bozuksa önce o sayfanın bağlantı ve yanıt ayarlarına bakın.

Dosya, bağlantı, sütun ve HTTP birbirinden ayrıdır

  • Dosya veya veri kaynağı: PHP dosyası, CSV ya da SQL yedeği gerçekten hangi kodlamada?
  • Bağlantı: PHP ile MySQL, gönderilen baytları hangi karakter kümesi olarak yorumluyor?
  • Sütun: Metin hangi karakter kümesindeki alana yazılıyor?
  • HTTP yanıtı: Tarayıcıya gönderilen baytların kodlaması doğru bildiriliyor mu?

Bu katmanlardan birinde “UTF-8” yazması diğerlerini düzeltmez. Örneğin HTML'e meta etiketi eklemek, veritabanına daha önce soru işareti olarak kaydedilmiş harfleri geri getirmez. Aynı şekilde yalnızca veritabanının varsayılan karakter kümesini değiştirmek mevcut sütunları otomatik olarak dönüştürmez.

Ekran yerine HEX değerine bakın

Aşağıdaki sorgu bağlantıyı utf8mb4 olarak ayarlar ve bilinen bir metni ölçer. UTF-8 olarak kaydedilmiş dosyada veya doğru kodlama kullanan istemcide çalıştırın. “Çığ” üç karakterdir; UTF-8 gösterimi altı bayttır.

SQL
SET NAMES utf8mb4;
SELECT HEX('Çığ') AS baytlar,
       CHAR_LENGTH('Çığ') AS karakter,
       LENGTH('Çığ') AS bayt;

Beklenen değerler sırasıyla C387C4B1C49F, 3 ve 6 olur. CHAR_LENGTH karakter sayısını, LENGTH bayt sayısını verir. Bu iki sayının farklı olması hata değildir. Kendi kaydınız için örnek tablo ve alan adlarını uyarlayarak aynı ölçümü yapın:

SQL
SELECT id, ad, HEX(ad), CHAR_LENGTH(ad), LENGTH(ad)
FROM musteriler
WHERE id = 42;

HEX doğru olduğu hâlde yalnızca bir uygulamada görüntü bozuksa okuma veya sunum katmanına odaklanın. Kaydedilen baytlar da yanlışsa bağlantıyı düzeltmek gelecekteki yazımları iyileştirebilir ama eski kaydı kendiliğinden onarmaz. Tanı sırasında gerçek kişisel verileri ekran görüntüsü veya herkese açık hata günlüğüne taşımayın.

PHP'nin kullandığı bağlantıyı kontrol edin

phpMyAdmin kendi bağlantısını kurar. Orada görülen oturum ayarları, uygulamanızın PDO oturumunun ayarları değildir. Aşağıdaki sorguyu sorunu üreten PHP bağlantısından çalıştırmak bu yüzden daha anlamlıdır:

SQL
SELECT @@character_set_client,
       @@character_set_connection,
       @@character_set_results;
SHOW FULL COLUMNS FROM musteriler;

PDO DSN içinde karakter kümesini açıkça tanımlayın. Aşağıdaki örnek, ortam değişkenlerinin önceden güvenli biçimde sağlandığı bir test uygulaması içindir. Dosyayı UTF-8 kaydedin. Bağlantı hatalarının ayrıntılarını ziyaretçiye göstermeyin; üretimde hata günlüğü ve genel hata sayfası kullanın.

PHP
<?php
declare(strict_types=1);

header('Content-Type: text/html; charset=UTF-8');
$pdo = new PDO(
    'mysql:host=127.0.0.1;dbname=uygulama;charset=utf8mb4',
    getenv('DB_USER'),
    getenv('DB_PASSWORD'),
    [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_EMULATE_PREPARES => false,
    ]
);

$stmt = $pdo->prepare('SELECT ? AS metin');
$stmt->execute(['Çığ, İstanbul, şeker']);
$metin = $stmt->fetchColumn();
echo htmlspecialchars($metin, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');

Bu örnek tabloya yazmaz; PHP ile sunucu arasındaki gidiş dönüşü sınar. Başarılı olması, uygulamadaki bütün sütunların doğru tanımlandığını kanıtlamaz. Bir sonraki adımda ayrı test veritabanında gerçek bir INSERT ve SELECT denemesi yapın. Hazırlanmış sorgu kullanmak SQL değerlerini güvenli taşımaya yardımcı olur; bozuk kodlamayı onaran bir işlem değildir.

utf8mb4 ile yazma ve okuma testi

Aşağıdaki geçici tablo aynı oturum açık kaldığı sürece yaşar. Kullanıcının geçici tablo oluşturma yetkisi gerekir. Kalıcı uygulama tablolarına dokunmadan hem Türkçe hem dört bayt gerektiren bir Unicode karakteri deneyebilirsiniz.

SQL
SET NAMES utf8mb4;
CREATE TEMPORARY TABLE kodlama_testi (
  id INT PRIMARY KEY,
  metin VARCHAR(100) CHARACTER SET utf8mb4
) ENGINE=InnoDB;
INSERT INTO kodlama_testi VALUES (1, 'Çığ'), (2, '🐘');
SELECT id, metin, HEX(metin), CHAR_LENGTH(metin), LENGTH(metin)
FROM kodlama_testi ORDER BY id;

İkinci kayıtta karakter sayısı 1, bayt sayısı 4 olmalıdır. utf8mb3 bu karakteri temsil edemez. Bununla birlikte bir collation seçiminin yalnızca saklamayı değil karşılaştırmayı da etkilediğini unutmayın. Aktarımda collation adı reddediliyorsa Unknown Collation hatası ayrı ele alınmalıdır.

Bozuk eski kayıtlar için toplu dönüştürmeye ara verin

ALTER TABLE ... CONVERT TO CHARACTER SET, mevcut karakter tanımına göre dönüşüm yapar. Gerçekte başka kodlamadaki baytlar yanlış etiketlenmişse bu varsayım geçersiz olabilir. Karışık geçmişe sahip bir tabloda aynı işlem bazı satırları düzeltirken doğru satırları bozabilir. Önce yedek alın, sorunlu kayıtları sınıflandırın ve küçük bir kopyada beklenen baytlarla karşılaştırın.

“Her çıktıya bir kez daha decode uygulayayım” yaklaşımı da aynı nedenle risklidir. İki kez kodlanmış metinle gerçekten Latin-1 olan metin aynı şey değildir. Verinin yerine kalıcı olarak ? yazılmışsa orijinal harfi yalnızca mevcut baytlardan kesin olarak çıkarmak mümkün olmayabilir; sağlam yedek veya kaynak veri gerekir.

Düzeltmeyi bitirirken yalnızca liste sayfasına bakmayın. Formdan ekleme, düzenleme, arama, dışa aktarma ve yeniden içe aktarma akışlarında aynı örnek metni dolaştırın. Her adımda baytlar korunuyorsa sorunu tek ekranda gizlemek yerine veri yolunu düzeltmiş olursunuz.

Teknik başvuru: PDO_MYSQL DSN, MySQL bağlantı karakter kümeleri ve sütun karakter kümesi dönüşümü.

Benzer Yazılar

Yorumlar ()

Henüz yorum yok. İlk yorum yapan sen ol!

Yorum Yap