PHP'de Düzenli İfadeler: preg_match, preg_replace ve Desen Kurma

PHP'de Düzenli İfadeler: preg_match, preg_replace ve Desen Kurma

6 dakika okuma

Düzenli ifadeler metin içinde kalıp aramak için kullanılır. Doğru yerde kullanıldığında onlarca satırlık ayrıştırma kodunun yerini tek bir ifade alır. Yanlış yerde kullanıldığında ise okunması güç, yavaş ve kırılgan bir yapı ortaya çıkar. Ayrım noktası şudur: aranan şey düzenli bir kalıpsa desen uygundur, yapısı olan bir belge biçimiyse ayrıştırıcı kullanılmalıdır.

PHP'de düzenli ifadeler PCRE kitaplığı üzerinden preg_ ön ekli fonksiyonlarla çalışır. Bu bölümde desen sözdizimi, gruplar, değiştirme ve bölme işlemleri ile Türkçe metinde karşılaşılan sorunlar ele alınıyor.

Desen yapısı ve sınırlayıcılar

Desen bir sınırlayıcı çift arasına yazılır. Geleneksel olarak eğik çizgi kullanılır, ancak desen içinde eğik çizgi geçiyorsa # veya ~ seçilerek kaçış karmaşası önlenir. Sınırlayıcıdan sonra gelen harfler bayraktır.

PHP9 satır
preg_match('/^\d{4}-\d{2}-\d{2}$/', '2026-07-27');   // tarih biçimi
preg_match('#^https?://#i', $adres);                   // eğik çizgi içeren desen

// sık kullanılan bayraklar
// i : büyük küçük harf ayrımı yok
// u : desen ve konu UTF-8 olarak işlenir
// m : ^ ve $ her satırın başına ve sonuna uyar
// s : nokta satır sonu karakterine de uyar
// x : desen içindeki boşluklar yok sayılır, yorum yazılabilir

Kullanıcıdan gelen bir değer desene gömülecekse mutlaka preg_quote ile kaçırılmalıdır. Aksi halde girdideki nokta ya da yıldız karakteri desenin anlamını değiştirir.

PHP2 satır
$aranan = preg_quote($_GET['q'] ?? '', '/');
preg_match("/{$aranan}/iu", $metin);

Türkçe metin ve u bayrağı

UTF-8 metinlerde u bayrağı olmadan desen bayt düzeyinde çalışır. Türkçe karakterler iki bayt tuttuğu için karakter sınıfları ve nicelik belirteçleri yanlış sonuç verir. Türkçe içerikle çalışan her desende bu bayrak bulunmalıdır.

PHP8 satır
$metin = 'çilek';

preg_match('/^.{5}$/', $metin);    // 0 - bayt sayısı 6 olduğu için uymaz
preg_match('/^.{5}$/u', $metin);   // 1 - karakter sayısı 5

// harf sınıfı da bayrağa bağlıdır
preg_match_all('/\p{L}+/u', 'Şehir: İstanbul', $bulunan);
// $bulunan[0] => ['Şehir', 'İstanbul']

\w kısayolu varsayılan olarak yalnızca ASCII harfleri kapsar. Türkçe harflerin de eşleşmesi için ya \p{L} kullanılır ya da sınıfa harfler açıkça eklenir.

Eşleşme yakalamak ve gruplar

preg_match ilk eşleşmeyi, preg_match_all tümünü bulur. Parantezle açılan gruplar sonuç dizisinde sırayla yer alır. Grup sayısı arttıkça sıra numarasını takip etmek zorlaşır; isimli gruplar bu sorunu ortadan kaldırır.

PHP9 satır
$desen = '/^(?<yil>\d{4})-(?<ay>\d{2})-(?<gun>\d{2})$/';

if (preg_match($desen, '2026-07-27', $p) === 1) {
    echo $p['yil'];   // 2026
    echo $p['ay'];    // 07
}

// yakalama gerekmiyorsa grup ?: ile işaretlenir, gereksiz bellek kullanılmaz
preg_match('/^(?:https?|ftp):\/\//', $adres);

preg_match_all sonucu varsayılan olarak grup sırasına göre dizilir: birinci boyut grup numarası, ikinci boyut eşleşme sırasıdır. Eşleşme başına gruplanmış bir sonuç isteniyorsa PREG_SET_ORDER bayrağı verilir. Bu bayrak, her eşleşmenin tüm gruplarını tek bir dizide topladığı için döngü içinde okumayı kolaylaştırır.

PHP5 satır
preg_match_all('/(?<ad>\w+)=(?<deger>\w+)/u', $ham, $p, PREG_SET_ORDER);

foreach ($p as $eslesme) {
    echo $eslesme['ad'], ' => ', $eslesme['deger'], PHP_EOL;
}

Dönüş değeri üç durumu ayırır: eşleşme sayısı, sıfır ve false. Sonuncusu desenin hatalı olduğunu gösterir ve false ile sıfır gevşek karşılaştırmada birbirine karışır. Bu yüzden karşılaştırma === ile yapılmalıdır.

Değiştirme ve geri çağırma

preg_replace eşleşen bölümleri verilen metinle değiştirir. Değiştirilecek metin bir hesaplama gerektiriyorsa preg_replace_callback kullanılır.

PHP12 satır
// birden fazla boşluğu teke indirmek
$temiz = preg_replace('/\s+/u', ' ', trim($metin));

// yakalanan grubu geri kullanmak
$html = preg_replace('/\*\*(.+?)\*\*/u', '<strong>$1</strong>', $metin);

// hesaplama gerektiren değişiklik
$sonuc = preg_replace_callback(
    '/\b\d+\b/',
    static fn (array $e): string => number_format((int) $e[0], 0, ',', '.'),
    $metin,
);

Metin içinde sabit bir dizgi değiştirilecekse düzenli ifade gereksizdir. str_replace hem daha hızlı çalışır hem de okunması kolaydır. Aynı şekilde bir metnin belirli bir ifadeyle başlayıp başlamadığı str_starts_with ile sorgulanır.

Bölme ve listeleme

Bölme işlemi tek bir ayırıcıyla sınırlıysa explode yeterlidir. Ayırıcı değişken sayıda boşluk içeriyorsa ya da birden fazla karakterden biri olabiliyorsa desen kullanılır.

PHP8 satır
// birden fazla ayırıcıya göre bölmek
$parcalar = preg_split('/[,;]\s*/u', 'elma, armut; kiraz');

// boş parçaları atmak
$etiketler = preg_split('/\s*,\s*/u', $ham, flags: PREG_SPLIT_NO_EMPTY);

// desene uyan dizi elemanlarını süzmek
$gorseller = preg_grep('/\.(jpe?g|png|webp)$/i', $dosyalar);

Uygulamada sık geçen desenler

Aşağıdaki desenler günlük işte en çok tekrarlanan ihtiyaçları karşılar. Her biri tek satırlık görünse de sınır koşulları düşünülerek yazılmıştır: başlangıç ve bitiş çıpaları, Türkçe karakter desteği ve gereksiz yakalama yapılmaması.

PHP14 satır
// bağlantı adresi üretmek için sadeleştirme
$slug = mb_strtolower(trim($baslik), 'UTF-8');
$slug = strtr($slug, ['ı' => 'i', 'ğ' => 'g', 'ü' => 'u', 'ş' => 's', 'ö' => 'o', 'ç' => 'c']);
$slug = preg_replace('/[^a-z0-9]+/u', '-', $slug);
$slug = trim($slug, '-');

// dosya uzantısı denetimi
$uygun = preg_match('/\.(jpe?g|png|webp)$/i', $ad) === 1;

// on bir haneli telefon numarasını normalleştirmek
$sayilar = preg_replace('/\D+/', '', $ham);

// birden fazla satır sonunu tek boşluğa indirmek
$tekSatir = preg_replace('/[\r\n]+/u', ' ', $metin);

Desen çalıştırıldıktan sonra hata durumu preg_last_error ile sorgulanabilir. Geri izleme sınırının aşılması bu yolla yakalanır ve sessiz başarısızlık önlenir.

PHP5 satır
$sonuc = preg_replace($desen, $yeni, $metin);

if ($sonuc === null && preg_last_error() !== PREG_NO_ERROR) {
    throw new RuntimeException('Desen çalıştırılamadı: ' . preg_last_error_msg());
}

Performans ve geri izleme tuzağı

İç içe geçmiş belirsiz nicelik belirteçleri, eşleşmeyen bir girdide denenecek yol sayısını katlanarak artırır. Bu durum katastrofik geri izleme olarak adlandırılır ve tek bir isteğin işlemciyi saniyelerce meşgul etmesine yol açar.

PHP8 satır
// riskli: (a+)+ yapısı eşleşmeyen girdide çok sayıda yol dener
$riskli = '/^(\w+\s?)+$/';

// güvenli karşılığı: belirsizliği kaldırmak
$guvenli = '/^\w+(?:\s\w+)*$/';

// sınır koymak da bir önlemdir
ini_set('pcre.backtrack_limit', '100000');

Desenler döngü içinde derlenmez; PCRE derlenmiş desenleri kendi önbelleğinde tutar. Yine de sıcak yollarda desen kullanmak yerine dizgi fonksiyonlarına geçmek ölçülebilir kazanç sağlar.

Uzun desenleri okunabilir tutmak

Bir desen tek satırda okunamayacak kadar uzadığında x bayrağı kullanılır. Bu bayrak desen içindeki boşlukları ve satır sonlarını yok sayar, yorum yazmaya izin verir. Boşluk karakterinin kendisi aranacaksa kaçış ile ya da karakter sınıfı içinde yazılır.

PHP12 satır
$desen = '/
    ^
    (?<protokol>https?)   # protokol
    :\/\/
    (?<alan>[\w.-]+)      # alan adı
    (?<yol>\/[^\s]*)?     # yol, isteğe bağlı
    $
/xu';

if (preg_match($desen, $adres, $p) === 1) {
    echo $p['alan'];
}

Deseni koda gömmek yerine adlandırılmış bir sabit olarak tanımlamak da okunabilirliği artırır. Aynı desen birden fazla yerde kullanılıyorsa bu yöntem tutarlılığı da korur; kural değiştiğinde tek bir yer güncellenir.

Düzenli ifadenin uygun olmadığı yerler

  • HTML ayrıştırmak. İç içe yapı düzenli ifadeyle güvenilir biçimde çözülmez; DOMDocument kullanılır.
  • E-posta doğrulamak. Standartla uyumlu desen aşırı karmaşıktır; filter_var ile FILTER_VALIDATE_EMAIL yeterlidir.
  • URL ayrıştırmak. parse_url bileşenleri hazır verir.
  • Sabit metin aramak. str_contains daha hızlı ve okunaklıdır.
  • JSON okumak. Biçimin kendi ayrıştırıcısı vardır.

Kontrol listesi

  • Türkçe metinle çalışan tüm desenlerde u bayrağı var.
  • Kullanıcı girdisi desene gömülmeden önce preg_quote ile kaçırılıyor.
  • Dönüş değeri === ile karşılaştırılıyor, false ayrı ele alınıyor.
  • Yakalanmayacak gruplar (?:...) biçiminde yazılıyor.
  • İç içe belirsiz nicelik belirteçleri bulunmuyor.
  • Sabit metin işlemleri için dizgi fonksiyonları tercih ediliyor.

İlgili yazılar

PHP eğitim serisi

Bu yazı, temel sözdiziminden üretim ortamına kadar ilerleyen 25 bölümlük serinin bir parçası. Bölümler sırayla okunacak biçimde hazırlandı, ancak her biri tek başına da kullanılabilir.

Temeller

  1. Kurulum ve temel sözdizimi
  2. Döngüler ve akış kontrolü
  3. Diziler
  4. Fonksiyonlar
  5. String fonksiyonları
  6. Süper globaller
  7. Form işleme
  8. Dosya işlemleri

Dilin ayrıntıları

  1. Tip sistemi ve strict_types
  2. Hata ve istisna yönetimi
  3. Düzenli ifadeler (bu yazı)
  4. Tarih ve saat işlemleri

Nesne yönelimli PHP

  1. Sınıf ve nesne
  2. Kalıtım, arayüz ve trait
  3. Statik üyeler, sabitler ve enum
  4. Namespace, Composer ve autoload

Veri ve güvenlik

  1. PDO ile veritabanı işlemleri
  2. Oturum yönetimi ve kimlik doğrulama
  3. Güvenli çıktı ve XSS
  4. JSON ile veri değişimi

Dış dünya ve üretim

  1. cURL ile dış API çağırma
  2. Kendi REST API'ni yazmak
  3. PHPUnit ile birim test
  4. Hata ayıklama ve performans ölçümü
  5. Üretime alma ve güvenlik

Yorumlar

Bu form reCAPTCHA ile korunur; Google Gizlilik Politikası ve Hizmet Şartları geçerlidir.

İlk yorumu sen yap.

İlgili Yazılar