PHP'de düzenli ifadelerle (regex) çalışmak için PCRE eklentisinin preg_ önekli fonksiyonları kullanılır. Bir metnin desene uyup uymadığını preg_match(), bütün eşleşmeleri preg_match_all(), değiştirmeyi preg_replace() ve preg_replace_callback(), desene göre bölmeyi preg_split() yapar. Desen '/^\d{4}$/' gibi iki sınırlayıcı (delimiter) arasına yazılır ve kapanış sınırlayıcısından sonra i, u, m gibi değiştiriciler eklenir.
Bu yazı düzenli ifade sözdizimini sıfırdan öğretmez; karakter sınıfları, nicelik belirteçleri ve gruplar gibi temel kavramlar için düzenli ifadeler başlangıç rehberi yazısına bakabilirsiniz. Burada PHP'ye özgü konulara odaklanıyoruz: fonksiyonların dönüş değerleri ve false tuzağı, eşleşme dizisinin yapısı, adlandırılmış gruplar, değiştiriciler, Türkçe metin için u değiştiricisinin önemi ve Türkçe i/I harflerinde büyük/küçük harf duyarsız eşleşmenin sınırları, preg_quote() ile kullanıcı girdisini güvenle desene eklemek ve hata ayıklama.
Sınırlayıcılar ve değiştiriciler

PCRE fonksiyonları deseni sınırlayıcılar arasında ister. Kılavuza göre sınırlayıcı, harf, rakam, ters bölü, boşluk veya NUL olmayan herhangi bir tek baytlık karakter olabilir; en sık /, # ve ~ kullanılır. Parantez çiftleri ((), {}, [], <>) de sınırlayıcı olabilir. Desen içinde sınırlayıcı karakter geçiyorsa ters bölüyle kaçırmanız gerekir; URL veya dosya yolu gibi çok / içeren desenlerde # gibi başka bir sınırlayıcı seçmek okunabilirliği artırır. Çok baytlı karakterler (örneğin §) sınırlayıcı olamaz.
| Değiştirici | Etkisi |
|---|---|
| i | büyük/küçük harf duyarsız eşleşme |
| m | ^ ve $ her satırın başı ve sonunda da eşleşir |
| s | nokta (.) satır sonu karakterini de kapsar |
| x | desendeki boşluklar ve # ile başlayan yorumlar yok sayılır |
| u | desen ve metin UTF-8 olarak işlenir |
| U | nicelik belirteçleri varsayılan olarak açgözlü olmaz |
| D | $ yalnızca metnin en sonunda eşleşir |
| A | eşleşme metnin başına sabitlenir |
| n | numaralı gruplar yakalama yapmaz, yalnızca adlandırılmışlar (PHP 8.2) |
| r | i ve u ile birlikte ASCII ve ASCII dışı harflerin eşleşmesini engeller (PHP 8.4) |
preg_match(): eşleşme var mı?
preg_match() metinde desenin ilk eşleşmesini arar. Eşleşme varsa 1, yoksa 0, bir hata oluşursa false döndürür. Üçüncü parametre olarak bir değişken verirseniz eşleşmeler o diziye yazılır: $m[0] desenin tamamıyla eşleşen metin, $m[1] ilk parantezli grup, $m[2] ikinci grup ve böyle devam eder. Kılavuz, dönüş değerinin hem 0 hem false olabildiği için sonucu === ile test etmenizi önerir; özellikle "eşleşmedi" ile "desen bozuk" durumlarını ayırmak istiyorsanız bu şarttır.
<?php
$tarih = '2026-11-07';
if (preg_match('/^(\d{4})-(\d{2})-(\d{2})$/', $tarih, $m)) {
echo "yıl $m[1], ay $m[2], gün $m[3]\n"; // çıktı: yıl 2026, ay 11, gün 07
}
var_dump(preg_match('/^\d{4}$/', 'abcd')); // çıktı: int(0)Grupları numarayla izlemek, desen büyüdükçe zorlaşır. (?<ad>...) yazımıyla gruba bir ad verirseniz eşleşme dizisinde hem numarası hem adıyla yer alır. PHP 8.2'den beri gelen n değiştiricisi ise adı olmayan gruplarda yakalamayı kapatır; böylece dizide yalnızca adlandırılmış gruplar ve tam eşleşme kalır. Kılavuza göre PHP 8.4'ten beri grup adları 32 yerine 128 karaktere kadar uzun olabilir.
<?php
$desen = '/^(?<il>\d{2})\s(?<harf>[A-Z]{1,3})\s(?<no>\d{2,4})$/';
preg_match($desen, '34 ABC 123', $m);
echo $m['il'], ' / ', $m['harf'], ' / ', $m['no'], "\n"; // çıktı: 34 / ABC / 123
echo count($m), "\n"; // çıktı: 7 (adlar ve numaralar)
preg_match('/(\d+)-(?<son>\d+)/n', '10-20', $m);
echo json_encode($m), "\n"; // çıktı: {"0":"10-20","son":"20","1":"20"}Son satırda adsız (\d+) grubu yakalanmadı; numara 1 artık adlandırılmış son grubuna ait. Eşleşmeyen isteğe bağlı gruplar için varsayılan davranış, ortadakileri boş metin olarak yazmak, sondakileri ise diziye hiç eklememektir. PREG_UNMATCHED_AS_NULL bayrağı bu grupları null olarak ve her zaman diziye ekler; böylece dizinin boyutu sabit kalır.
u değiştiricisi ve Türkçe metin
PCRE, u değiştiricisi olmadan metni bayt dizisi olarak görür. Nokta tek bir baytla eşleşir, {5} beş bayt sayar ve [çğ] gibi bir karakter sınıfı aslında dört ayrı bayttan oluşan bir sınıf olur. Türkçe veya başka bir dilde UTF-8 metinle çalışırken u değiştiricisini her desene eklemek iyi bir varsayılandır. Kılavuza göre u ile birlikte geçersiz bir UTF-8 metin hiçbir şeyle eşleşmez, geçersiz bir desen ise uyarı üretir.
Harfleri yakalamak için [a-zA-Z] yazmak Türkçe harfleri dışarıda bırakır. Unicode özellikleri u ile birlikte kullanılabilir: \p{L} herhangi bir dildeki harfi, \p{Lu} büyük harfi, \p{Ll} küçük harfi yakalar. Kılavuz \w için "harf, rakam veya alt çizgi" tanımını verir ve hangi baytların harf sayıldığının karakter tablolarına bağlı olduğunu söyler. PHP 8.5.12 ile yaptığımız denemede \w, u ile Türkçe harfleri kapsadı, u olmadan kapsamadı. Niyetinizi açık göstermek için harf gerektiğinde \p{L} yazmak daha iyidir.
<?php
var_dump(preg_match('/^.{5}$/', 'Şişli')); // çıktı: int(0) (7 bayt)
var_dump(preg_match('/^.{5}$/u', 'Şişli')); // çıktı: int(1)
var_dump(preg_match('/^[a-z]+$/iu', 'çay')); // çıktı: int(0)
var_dump(preg_match('/^\p{L}+$/u', 'çığır')); // çıktı: int(1)
var_dump(preg_match('/^\w+$/', 'çığır')); // çıktı: int(0)
var_dump(preg_match('/^\w+$/u', 'çığır')); // çıktı: int(1)Büyük/küçük harf duyarsız eşleşmede Türkçe i/I harfleri yine sorun çıkarır. i değiştiricisi Unicode'un dilden bağımsız harf eşleme kurallarını kullanır: ş ile Ş eşleşir, ama i ile İ, ı ile I eşleşmez; çünkü bu kurallarda i harfinin karşılığı I'dır. Kullanıcının büyük harfle yazdığı İSTANBUL araması bu yüzden istanbul içeren kayıtları bulamaz. Güvenli yol, hem deseni hem metni önce Türkçe kurallarla küçültmektir; yardımcı fonksiyonları PHP mbstring ve Türkçe karakterler yazısında verdik.
<?php
var_dump(preg_match('/şeker/iu', 'ŞEKER')); // çıktı: int(1)
var_dump(preg_match('/istanbul/iu', 'İSTANBUL')); // çıktı: int(0)
var_dump(preg_match('/ışık/iu', 'IŞIK')); // çıktı: int(0)preg_match_all(): bütün eşleşmeler

preg_match_all() ilk eşleşmede durmaz, metnin sonuna kadar arar ve tam eşleşme sayısını döndürür. Eşleşme dizisinin yapısını dördüncü parametredeki bayrak belirler. Varsayılan PREG_PATTERN_ORDER'da $m[0] bütün tam eşleşmelerin, $m[1] bütün birinci grupların listesidir. PREG_SET_ORDER'da ise her eşleşme kendi alt dizisini alır; satır satır işlemek için genellikle bu daha rahattır.
<?php
$metin = 'Kalem 12,50 TL, defter 30 TL, silgi 7,25 TL';
$desen = '/(\p{L}+) (\d+(?:,\d+)?) TL/u';
$adet = preg_match_all($desen, $metin, $m);
echo $adet, "\n"; // çıktı: 3
echo implode(' | ', $m[2]), "\n"; // çıktı: 12,50 | 30 | 7,25
preg_match_all($desen, $metin, $satirlar, PREG_SET_ORDER);
foreach ($satirlar as [, $urun, $fiyat]) {
echo "$urun = $fiyat\n";
}
/* çıktı:
Kalem = 12,50
defter = 30
silgi = 7,25
*/PREG_OFFSET_CAPTURE bayrağı her eşleşmenin metindeki konumunu da verir. Bu konum u değiştiricisiyle bile bayt cinsindendir; mb_substr() gibi karakter tabanlı fonksiyonlara vermeden önce dönüştürmeniz gerekir. Kadıköy içinde köy eşleşmesinin konumu 5 olarak döner, oysa karakter konumu 4'tür.
preg_replace(): desene göre değiştirmek
preg_replace() desene uyan her parçayı değiştirme metniyle değiştirir. Değiştirme metninde $1, $2 gibi referanslarla yakalanan grupları geri yazabilirsiniz; $0 tam eşleşmedir. Referansın hemen arkasından bir rakam geliyorsa ${1}0 yazımıyla grubu ayırırsınız, aksi hâlde PHP $10'u onuncu grup sanar. Desen ve değiştirme dizi olarak da verilebilir; her desen kendi sırasındaki değiştirmeyle eşlenir. Dördüncü parametre her desen için en fazla kaç değişiklik yapılacağını sınırlar, beşinci parametre ise yapılan değişiklik sayısını bir değişkene yazar. Sabit bir metni değiştirmek için desene gerek yoktur; kılavuz bu durumda str_replace() kullanmanızı önerir.
<?php
echo preg_replace('/\s+/', ' ', " çok fazla \n boşluk "), "|\n"; // çıktı: çok fazla boşluk |
echo preg_replace('/(\d{4})-(\d{2})-(\d{2})/', '$3.$2.$1', '2026-11-07'), "\n"; // çıktı: 07.11.2026
echo preg_replace('/(\d)/', '${1}0', 'a1b2'), "\n"; // çıktı: a10b20
$tel = '0532 123 45 67';
echo preg_replace('/\d(?=(?:\D*\d){2})/', '*', $tel, -1, $sayi), "\n"; // çıktı: **** *** ** 67
echo $sayi, "\n"; // çıktı: 9
echo preg_replace(['/ğ/u', '/ş/u', '/ı/u'], ['g', 's', 'i'], 'dağ başı'), "\n"; // çıktı: dag basipreg_replace_callback(): değiştirmeyi kodla hesaplamak
Değiştirme metni sabit değilse, örneğin yakalanan sayıyla hesap yapmanız veya bir sözlükten değer bulmanız gerekiyorsa preg_replace_callback() kullanılır. Callback her eşleşme için çağrılır, eşleşme dizisini parametre olarak alır ve yerine konacak metni döndürür. Kılavuz, callback yalnızca bir yerde kullanılıyorsa onu anonim fonksiyon olarak doğrudan çağrının içinde yazmayı önerir. PHP 7.4'ten beri altıncı parametre olarak PREG_OFFSET_CAPTURE ve PREG_UNMATCHED_AS_NULL bayrakları da verilebilir.
<?php
$sablon = 'Merhaba {ad}, {sehir} şubemize hoş geldiniz. {bilinmeyen}';
$veri = ['ad' => 'Ece', 'sehir' => 'İzmir'];
echo preg_replace_callback(
'/\{(\w+)\}/',
fn(array $m) => $veri[$m[1]] ?? $m[0],
$sablon
), "\n";
// çıktı: Merhaba Ece, İzmir şubemize hoş geldiniz. {bilinmeyen}
echo preg_replace_callback('/\d+(?:\.\d+)?/', fn($m) => $m[0] * 1.2, 'Kalem 10, defter 25.5'), "\n";
// çıktı: Kalem 12, defter 30.6preg_split() ve preg_grep()
explode() tek bir sabit ayırıcıyla böler. Ayırıcı değişkense, örneğin virgül, noktalı virgül ve boşlukların karışımıysa preg_split() kullanılır. PREG_SPLIT_NO_EMPTY bayrağı boş parçaları atar, PREG_SPLIT_DELIM_CAPTURE desendeki parantezli ayırıcıları da sonuca ekler. Üçüncü parametre parça sayısını sınırlar; -1 veya 0 sınırsız demektir. Bayrakları birleştirmek için | operatörü kullanılır. preg_grep() ise bir dizinin desene uyan elemanlarını, anahtarlarını koruyarak döndürür; PREG_GREP_INVERT bayrağıyla uymayanları verir.
<?php
$etiketler = preg_split('/[\s,;]+/', 'php, regex; türkçe ,unicode', -1, PREG_SPLIT_NO_EMPTY);
echo json_encode($etiketler, JSON_UNESCAPED_UNICODE), "\n";
// çıktı: ["php","regex","türkçe","unicode"]
echo json_encode(preg_split('/([+-])/', '10+5-3', -1, PREG_SPLIT_DELIM_CAPTURE)), "\n";
// çıktı: ["10","+","5","-","3"]
$kodlar = ['a' => '123', 'b' => '12x', 'c' => '9'];
echo json_encode(preg_grep('/^\d+$/', $kodlar)), "\n"; // çıktı: {"a":"123","c":"9"}
echo json_encode(preg_grep('/^\d+$/', $kodlar, PREG_GREP_INVERT)), "\n"; // çıktı: {"b":"12x"}preg_quote(): kullanıcı girdisini desene eklemek
Kullanıcının yazdığı bir arama kelimesini desenin içine olduğu gibi koyarsanız, kelimedeki nokta, parantez, artı veya soru işareti gibi karakterler düzenli ifade anlamı kazanır. Sonuç yanlış eşleşmeler, bozuk desen uyarıları veya çok yavaş çalışan desenler olabilir. preg_quote() bu özel karakterlerin önüne ters bölü koyar. Kılavuza göre / özel bir karakter değildir; bu yüzden desende kullandığınız sınırlayıcıyı ikinci parametre olarak vermeniz gerekir. PHP 7.3'ten beri # karakteri de kaçırılır. Kaçırma yalnızca desen içindir; değiştirme metnine uygulanmaz.
<?php
$aranan = 'PHP 8.5 (yeni)';
$metin = 'Bu yazı PHP 8.5 (yeni) sürümünü anlatıyor.';
var_dump(preg_match('/' . $aranan . '/', 'PHP 805 yeni')); // çıktı: int(1) (yanlış eşleşme)
echo preg_quote($aranan, '/'), "\n"; // çıktı: PHP 8\.5 \(yeni\)
var_dump(preg_match('/' . preg_quote($aranan, '/') . '/', 'PHP 805 yeni')); // çıktı: int(0)
var_dump(preg_match('/' . preg_quote($aranan, '/') . '/', $metin)); // çıktı: int(1)Uygulamalı örnek: Türkçe başlıktan URL kısaltması (slug)
Blog yazısı veya ürün başlığından php-dizi-siralama gibi bir adres parçası üretmek, düzenli ifadelerin tipik bir kullanımıdır. Adımları sırayla izleyelim: önce başlığı Türkçe kurallarla küçültüyoruz, sonra Türkçe harfleri ASCII karşılıklarına çeviriyoruz. Bu çeviri sabit bir eşleme olduğu için regex yerine strtr() yeterlidir. Ardından harf ve rakam dışındaki her karakter dizisini tek bir tireye indiriyor, son olarak baştaki ve sondaki tireleri temizliyoruz. Tire, desende karakter sınıfının dışında kaldığı için kaçırmaya gerek yoktur.
<?php
function slug(string $baslik): string
{
$s = mb_strtolower(str_replace(['İ', 'I'], ['i', 'ı'], $baslik));
$s = strtr($s, ['ç' => 'c', 'ğ' => 'g', 'ı' => 'i', 'ö' => 'o', 'ş' => 's', 'ü' => 'u']);
$s = preg_replace('/[^a-z0-9]+/', '-', $s);
return trim($s, '-');
}
echo slug('PHP Dizi Sıralama: sort() ve usort()'), "\n"; // çıktı: php-dizi-siralama-sort-ve-usort
echo slug(" İstanbul'da Işık Gösterisi! "), "\n"; // çıktı: istanbul-da-isik-gosterisiDesen bilerek u değiştiricisi olmadan yazıldı: bu noktada metinde yalnızca ASCII harfler kalmasını bekliyoruz ve [^a-z0-9]+, geriye kalan her baytı, örneğin emojileri veya başka dillerin harflerini, tireye çevirir. Kesme işareti, iki nokta ve parantez gibi işaretler de tireye dönüşür, art arda gelen tireler tek tireye iner. Farklı bir dil desteği gerekiyorsa çeviri tablosunu genişletmeniz gerekir.
Hatalar: bozuk desen ve geçersiz UTF-8
Derlenemeyen bir desen verdiğinizde fonksiyon E_WARNING uyarısı üretir ve false döndürür; uyarıda PCRE'nin derleme hatası ve hatanın desendeki konumu yazar. Desen geçerli olsa da eşleşme sırasında bir sorun çıkabilir: u değiştiricisiyle geçersiz UTF-8 metin işlendiğinde veya pcre.backtrack_limit gibi sınırlar aşıldığında fonksiyon yine false döndürür ama uyarı vermez. Bu durumda sebebi preg_last_error() (hata kodu) ve PHP 8.0 ile gelen preg_last_error_msg() (okunabilir mesaj) ile öğrenirsiniz. preg_replace() hata durumunda null döndürür; sonucu doğrudan veritabanına yazmadan önce bu değeri kontrol edin.
<?php
var_dump(preg_match('/(abc/', 'abc'));
/* çıktı:
Warning: preg_match(): Compilation failed: missing closing parenthesis at offset 4 in /yol/ornek.php on line 2
bool(false)
*/
$bozuk = "Merhaba \xC3"; // yarım kalmış bir UTF-8 karakteri
var_dump(preg_match('/a/u', $bozuk)); // çıktı: bool(false)
echo preg_last_error_msg(), "\n"; // çıktı: Malformed UTF-8 characters, possibly incorrectly encoded
var_dump(preg_last_error() === PREG_BAD_UTF8_ERROR); // çıktı: bool(true)Kılavuz ayrıca PHP 8.4'te gelen PCRE2 10.44 güncellemesinin bazı eski desenlerin anlamını değiştirdiğini not eder: örneğin {,3} artık düz metin değil, "en fazla üç" anlamında bir nicelik belirteci olarak tanınıyor. Eski bir kod tabanını 8.4 veya üstüne taşırken desenlerinizi testlerle yeniden doğrulayın.
Sık yapılan hatalar
![Sık yapılan hatalar: preg_match() sonucunu yalnızca doğruluk değeri olarak kullanmak, Türkçe metinde u değiştiricisini unutmak, [a-z] ile harf aramak, Kullanıcı girdisini...](/assets/blog/php-preg-match-regex-b11.webp)
preg_match()sonucunu yalnızca doğruluk değeri olarak kullanmak:0vefalsefarklı anlamlar taşır; hatayı ayırt etmek için=== falsekontrolü yapın.- Türkçe metinde
udeğiştiricisini unutmak: nokta ve nicelik belirteçleri baytları sayar, karakter sınıfları harfleri böler. [a-z]ile harf aramak: Türkçe harfleri kaçırır;\p{L}kullanın.- Kullanıcı girdisini kaçırmadan desene eklemek: her zaman
preg_quote($girdi, '/')kullanın. - Çift tırnaklı PHP metninde ters bölü kullanmak:
"\d"gibi diziler PHP tarafından da yorumlanabilir. Desenleri tek tırnakla yazın; ters bölünün kendisini eşleştirmek için desende\\\\gerekir. - Basit işler için regex kullanmak: sabit metin aramak için
str_contains(), değiştirmek içinstr_replace(), e-posta veya URL doğrulamak için filter_var daha basit ve okunaklıdır.
Sık Sorulan Sorular
preg_match() neden yalnızca ilk eşleşmeyi veriyor?
Tasarımı böyledir: preg_match() ilk eşleşmeyi bulunca durur ve dönüş değeri en fazla 1 olur. Metindeki bütün eşleşmeler için preg_match_all() kullanın. Belirli bir konumdan sonra aramaya devam etmek isterseniz beşinci parametre olan $offset değerini (bayt cinsinden) verebilirsiniz; kılavuz bunun substr() ile kesmekle aynı olmadığını, çünkü ^ gibi işaretlerin hâlâ metnin gerçek başına baktığını hatırlatır.
ereg fonksiyonları nereye gitti?
PHP'nin eski POSIX düzenli ifade fonksiyonları (ereg(), eregi()) PHP 7.0 ile kaldırıldı. Güncel PHP'de düzenli ifadeler için yalnızca PCRE tabanlı preg_ fonksiyonları vardır; mb_ereg ailesi ise PCRE'nin değil, mbstring eklentisinin parçasıdır.
Deseni her çağrıda yeniden derlemek yavaş mıdır?
Kılavuza göre PCRE eklentisi derlenmiş desenleri bir önbellekte tutar; her iş parçacığının kendi önbelleği vardır ve 4096 desene kadar saklayabilir. Aynı desenle tekrar tekrar çağrı yapmak bu yüzden her seferinde baştan derleme anlamına gelmez. Kılavuz, eskiden desen analizini artıran S değiştiricisinin PHP 7.3'ten beri hiçbir etkisi olmadığını belirtir.
E-posta adresini regex ile mi doğrulamalıyım?
E-posta söz dizimini tam olarak karşılayan bir desen yazmak zordur. PHP'nin filter_var($eposta, FILTER_VALIDATE_EMAIL) fonksiyonu bu iş için hazırdır; kullanımı filter_var ile veri doğrulama yazısında.
Çok satırlı metinde her satırın başını nasıl yakalarım?
m değiştiricisi ^ ve $ işaretlerinin her satır sonunda da eşleşmesini sağlar: preg_match_all('/^- (.+)$/mu', $metin, $m) ile madde işaretli satırları toplayabilirsiniz. Noktanın satır sonlarını da kapsaması için ise s değiştiricisi kullanılır.
Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.
İlgili içerikler: PHP öğrenme rehberi · düzenli ifadeler başlangıç rehberi · PHP mbstring ve Türkçe karakterler · PHP string fonksiyonları · filter_var ile veri doğrulama