Düzenli İfadeler
Unix işletim sistemini geliştirenlerin bilişim teknolojisine kazandırdığı bir diğer
değer biçilemez araç olan Düzenli İfadeler (Regular Expressions), belki de işaretlerinin
karmaşıklığı sebebiyle programcıların fazla dokunmak istemedikleri bir alandır. Bu
kavramla daha önce tanışmış olmayanlara, CGI veya Javascript kodlarında gördükleri,
^.+@.+\\..+$
işaretlerinin, Düzenli İfade işlemlerine ait olduğunu belirtilim. Bu işaretler ve onların
arasına koyduğumuz karakter örnekleri ile, PHP'nin aradığımız bir metnin karakterlerinin
hangi diziliş, sıralanış konumunda olduğuna bakarak, bize o metni bulmasını sağlarız; ya
bu metni kullanırız, sileriz veya değiştiririz. Dolayısıyla, Düzenli İfade demek, bir diziliş,
sıralanış biçimi demektir. Düzenli İfade oluşturarak, PHP'ye "İşte buradaki gibi sıralanmış
karakterleri bul!" demiş oluruz. Düzenli İfade, PHP'ye verdiğimiz sıralanışa uygun metnin
nerede ve kaçıncı kez bulunmasını istediğimizi de belirtir. Ayrıca, bu ifade ile, PHP'nin
bulduğu metni (veya metinleri) ne yapmasını istediğimizi de söyleriz.
Eşleştirme deyimleri ve işaretler
PHP'nin karakter ve sıralanış eşlemede kullanılan düzenli ifade komutlarını kısaca
ele alalım; sonra bunları kullanmamıza imkan veren fonksiyonları görelim.
^hakk
"hakk" ile başlayan bütün kelimeleri bulur. ^işareti, onu izleyen karakterin sadece
alfanümerik değişkenin başında olması gerektiğine işaret eder. Bu deyimle, "Hakkı Öcal
bugün buradaydı!" veya "Hakkıdır hakka tapan, milletimin istiklâl!" eşleşebilir; fakat
"Anaların hakkı ödenmez!" eşleşmez; çünkü PHP açısından ^işaretinin anlamı,
"Başlangıcında 'hakk' karakterlerinin sıralanıyla aynı sıralanıştaki alfanümerik değeri bul!"
demektir.
edilemez$
Bu deyim ise PHP'ye "edilemez" ile biten bütün kelimeleri bulur. Sözgelimi,
"Kötümser insanın yükselişinden bahsedilemez" cümlesi bu deyimle bulunabilir; fakat
sonunda nokta olduğu için ".. edilemez." diye biten hiç bir cümle bu deyimle eşleşemez.
^hakkı$
PHP, başında ^ işareti, sonunda $ işareti bulunan kararter sıralanışını, aynen arar;
yani bu deyim, birinci örnekteki üç cümleyi de bulamaz.
hakk
Bu deyim ise her üç cümleyi de buldurur; çünkü üçünde de bu dört karakter bu
sıralanışla mevcuttur. PHP'nin Düzenli İfadeleri, bütün rakam ve harfleri eşleştirebilir. Fakat
sorun, özel karakterlerde çıkar. Sözgelimi, sekme işareti, satır sonlarında yenisatır/satırbaşı
işareti, gibi özel karakterleri, ancak önlerine Escape işareti olan ters bölü
işaretini koyarak buluruz.
Düzenli İfadelerde Özel Karakterler
[\b] Geri (Backspace) karakterini bulur.
\b Belirtilen karakterle sınırlanan kelimeyi bulur: k\b, "hak mücadelesi"
ifadesindeki birinci k'yı bulur; çünkü bu harf, bir kelime sınırlayıcıdır.
\B Belirtilen karakterle sınırlanmayan kelime yoksa, başlayanı bulur: k\Bi,
"üç kişi" ifadesindeki 'ki'yi bulur.
\cX X yerine yazacağımız kontrol karakterini bulur. Örneğin, \cA, Ctrl+A'yı,
\cZ ise Ctrl+Z'yi bulur.
\d 0'dan 9'ya kadar bir rakamı bulur: IE\d, her ikisi de herhangi bir rakamla
biten "IE5" ve "IE4" değerlerini ikisini de bulur,
\D Herhangi bir ondalık işaretini bulur.
\f Form-feed (kağıt çıkart) karakterini bulur.
\n Newline (yeni satır) karakterini bulur.
\r Return (satırbaşı) karakterini bulur.
\s Boşluk (space) bulur.
\S Yatay ve düşey sekme, kağıt-çıkart, yeni satır, satırbaşı ve boşluk
dışındaki herhangi bir karakteri bulur.
\t Yatay sekme (Tab) karakterini bulur.
\v Düşey sekme karakterini bulur.
\w Herhangi bir harf, rakam veya alt-çizgiyi bulur.
\W Harf, rakam ve alt-çizgi dışındaki karakteri bulur.
\xHex Verilen 16 tabanlı (Hexadecimal) sayıya uygun Escape karakterini bulur.
Örneğin, \n25, % işaretini bulur.
Bu arada noktalama işaretlerini arattırırken, önlerine ters bölü işareti koymak
gerekir. Ters bömü işaretini de yine önüne ters bölü işareti koyarak (\\) arttırabilirsiniz.
Karakter Grupları
PHP'nin Düzenli İfadeleri'nde kolaylık sağlayan ve mesela ziyaretçinin bir Form'da
bir INPUT etiketine verdiği yanıtıların içinde olmaması veya olmaması gereken karakterleri
bulmamıza imkan veren karakter grupları oluşturma yöntemini de kullanabiliriz. Sözgelimi
bütün sesli hafleri aratmak için şöyle bir karakter grubu oluşturabiliriz:
[OoUuÖöAaOoEeıIiİ]
Karakter gruplarını köşeli parantez içinde yazarız. Bu deyimle, PHP, içinde herhangi
bir sesli harf bulunan bütün değerleri eşleştirecektir. Bu yöntemden yararlanarak, şu
grupları kullanabiliriz:
[a-z] Herhangi bir küçük harfi bulur.
[A-Z] Herhangi bir büyük harfi bulur.
[a-zA-Z] Herhangi bir büyük veya büyük harfi bulur.
[0-9] Herhangi bir rakamı bulur.
[0-9\.\-] Herhangi bir rakamı, noktayı veya kesme çizgisini bulur.
[ \f\r\t\n] Herhangi bir Form-feed (kağıt çıkart), Newline (yeni satır), Return
(satırbaşı) karakterini veya boşluğu (space) bulur.
Sözgelimi, bir alfanümerik değer kümesinde b3, u2, n9 gibi birincisi küçük harf,
ikincisi rakam olan iki karakterlik dizileri bulmak istiyorsak, arama grubunu şöyle kurarız:
^[a-z][0-9]$
Bu deyim PHP'ye, a'da z'ye küçük harfle başlayan, (^işareti aranan unsurun
değerin başında olması gerektiğini söylüyor) ve sonunda 0'dan 9'a bir rakam bulunan
kelimeleri bulmasını söyleyecektir. PHP, bu kelimenin sadece iki harfli olmasına dikkat
edecektir; çünkü grubumuzun bir başı ve bir de sonu belirlendiğine göre, üç karakterli
değerlerin bulunması imkanı yoktur.
^işareti köşeli parantez içinde grup deyimi oluştururken kullanılırsa, bu olumsuzluk
anlamı taşır. Sözgelimi, iki rakamlı ancak birinci karakteri rakam olmayan fakat ikinci
karakteri rakam olan değerlerin bulunması için şu deyim gerekir:
^[^0-9][0-9]$
Burada en baştaki ^işareti "başında" demektir; ancak hemen arkasından gelen
grupta "rakam olmayan" demiş oluyoruz; ikinci grup ve sonundaki $ işareti ile "rakamla
biten" anlamına geliyor. Deyimde sadece baş ve sonu gösteren iki eşleştirme unsuru
bulunduğuna göre bu deyim, "başında rakam olmayan, sonunda rakam olan iki karakterli
değerleri" bulmaya yarayacaktır. Bu deyim söz gelimi 13'ü bulmayacak, fakat u2'yi
bulacaktır. Bu yöntemle şu grupları yapabiliriz:
[^a-z] Küçük harf olmayan herhangi bir harfi bulur.
[^A-Z] Büyük harf olmayan herhangi bir harfi bulur.
[^\\\/\^] \ , / veya ^ dışında herhangi bir karakteri bulur.
[^\"\'] Çift ve tek tırnak dışında herhangi bir karakteri bulur.
Grup oluşturmada kullandığımız özel karakterler de vardır. Örneğin nokta işareti (.),
yeni satır başlangıcı olmayan herhangi bir karakter anlamına gelir. Dolayısıyla,
^.0$
deyimi yeni satırla başlamayan ve sıfır ile biten herhangi iki karakterli değeri bulacaktır.
PHP'nin kullanılmaya hazır özel Düzenli İfade eşleştirme grupları da vardır:
[[:alpha:]] Herhangi bir harf
[[:digit:]] Herhangi bir rakam
[[:alnum:]] Herhangi bir harf veya rakam
[[:space:]] Herhangi bir boş karakter
[[:upper:]] Herhangi bir büyük harf
[[:lower:]] Herhangi bir küçük harf
[[:punc:]] Herhangi bir noktalama işareti
[[:xdigit:]] Herhangi bir Hexadecimal karakter. [0-9a-fA-F]
Karakter eşleştirmede tekrar sayısı da bir özellik olarak kullanılabilir. Tekrar sayısı
belirtmek için süslü parantez ({}) kullanırız. Örnekler:
^a{4}$ İçinde sadece dört adet küçük a harfi bulunan kelimeleri seç:
aaaa.
^a{2,4}$ İçinde sadece iki üç veya dört adet küçük a harfi bulunan
kelimeleri seç: aa, aaa, aaaa gibi
^a{2, } İki veya daha fazla küçük a harfi bulunan kelimeleri seç: haar,
haaar, haaaar gibi. Bu deyim "har" kelimesini seçmez.
\t{2} Ardarda iki sekme işaretini bul
.{2} Herhangi çift karakteri bul: aa, &&, == gibi
^\-{0,1}[0-9]{1,}$ Negatif veya pozitif herhangi bir tam sayıyı bul
^[0-9]{1,}$ Pozitif herhangi bir tam sayıyı bul
Bu tür deyim oluşturma işlemleri giderek karmaşıklaşabilir. Örneğin:
^\-{0,1}[0-9]{0, }\.{0,1}[0-9]{0, }$
Bu karmaşık deyim aslında sadece "Negatif veya pozitif bir ondalık (double) değeri
bul," anlamına geliyor. Kısaca irdelersek, aranan degerin sıfır veya bir kere tekrarlanan bir
kesme çizgisiyle başlayabileceğini ("Sıfır veya bir kere" demek, olsa da olur, olmasa da
anlamına geliyor!) bunu sıfır veya daha fazla kere tekrarlanan bir rakamın izleyebileceğini,
onu da sıfır veya bir kere tekrarlanan bir nokta işareti ile sonunda sıfır veya daha fazla
kere tekrarlanan herhangi bir rakamın izleyebileceğini söylemiş oluyoruz.
PHP bu tür karmaşık ifadelerin hatasız yazılmasını sağlayan kısayollara sahiptir.
Bunları sıralayalım:
? {0,1} anlamına gelir. Kendisinden önce yer alan unsurun en az sıfır en
çok bir kere tekrar edilmesi gerektiğini (olmayabileceğini ama olursa
en fazla bir kere olabileceğini) belirtir.
* {0, } anlamına gelir. Kendisinden önce yer alan unsurun sıfır veya
daha fazla kere tekrar edilmesi gerektiğini (tümüyle opsiyonel
olduğunu) belirtir.
+ {1, } anlamına gelir. Kendisinden önce yer alan unsurun en az bir veya
daha çok kere tekrar edilmesi gerektiğini (bulunmasının zorunlu
olduğunu) belirtir.
Bu kısa-yolları kullanarak, yukarıdaki karmaşık ifadeleri basitleştirelim:
^[a-zA-Z0-9_]+Ş En az bir harf veya rakam veya altçizgi içeren herhangi bir kelime
^[0-9]+Ş Herhangi bir pozitif tamsayı
^\-?[0-9]+Ş Herhangi bir tamsayı
^\-?[0-9]*\.[0-9*$]+Ş Herhangi bir kesinli (double) sayı
Bir Düzenli İfade'nin yazılışında birden fazla arama-sıralanış deyimine yer
verebiliriz. Bunu yapmamızı sağlayan | işaretidir. Örneğin,
\.com|\.co\.uk
ifadesi ile, ya ".com" ya da ".co.uk" değerlerinin bulunmasını sağlayabiliriz. Burada | işareti
"veya" kelimesi gibi düşünebilirsiniz.
Düzenli ifadeler yoluyla INPUT etiketinden gelen değerleri incelerken hata yapmak
kolaydır. Bunun için kendi ifadelerinizi mutlaka sçeşitli olasılıklara karşı sınamalısınız. Bu
bölümün başında örnek olarak verdiğimiz Düzenli İfade'yi hatırlıyor musunuz?
^.+@.+\\..+$
Örneğin bu ifade, ziyaretçinin elektronik posta adresini yazması gereken bir INPUT
etiketinin sağladığı değerin gerçekten elektronik adres biçimi taşıyıp taşımadığını sınar.
Baştaki ^ ve nokta işaretleri ile artı işareti değerin önünde boşluk olmamasını sağlıyor; @
işareti ise değerin içinde @ bulunması gerektiğine işaret ediyor. Tekrar eden nokta ve artı
işaretleri "ne kadar olursa olsun ve ne olursa olsun" anlamına geliyor. Bunu izleyen nokta
karakterini gösteren (\.) işaret buralarda bir de gerçekten nokta olması gerektiğini ve
bunu izleyen nokta ve artı tekrar "ne olursa olsun, ne kadar olursa olsun" anlamını taşıyor.
Başka bir deyişle, aradığımız değerin "herhangi bir şey" @ "herhangi bir şey daha" .
"birşeyler daha" şeklinde olduğunu belirtmiş oluyoruz. Ne var ki deyimiçinde iki nokta veya
iki @ işareti olan veya @ işareti ile nokta arasında bir şey bulunmayan veya @ veya
noktadan öncesi ya da sonrası boş olan bütün değerleri safdışı etmeye yetmeyecektir.
Sözgelimi bir ziyaretçimiz "@@@@.@@@" yazarsa, bu deyim bu değeri geçerli bir
elektronik adres sayacaktır.
PHP programlarımızda ziyaretçilerimizin verdiği değerleri çeşitli bakımlardan
sınamak ve seçmek mümkündür; ancak hiç bir zaman yazılanların doğruluğunu garanti
edemeyiz. Fakat özellikle bir metinde bulunmaması gereken işaretlleri PHP'ye aratabiliriz.
Bunu Düzenli İfade Fonksiyonları sağlar.
Düzenli İfade Fonksiyonları
Yukarıda öğrendiğimiz Düzenli İfade yazma tekniklerini, PHP'nin bize sağladığı beş
fonksiyonda parametre olarak kullanırız. PHP'nin ayrıca Perl-tarzı düzenli ifade
fonksiyonları da vardır. Bu fonksiyonlardan, ya bize bir boolean (doğru/yanlış) değer
döner; ya da fonksiyon istediğimiz işi yaparak vardığı sonuçları verdiğimiz değişkene yazar.
Biz, daha sonra bu değere bakarak veya değişkenin değerlerini kullanarak, PHP
programımızın akışını kontrol edebiliriz. Burada ele alacağımız fonksiyonlara ilişkin
örneklerde, daha önceki bölümlerde oluşturduğumuz konuk defteri programı ile Web
ziyaretçilerimizin sunucuya göndereceği bilgileri doğrulamaya ve muhtemel zararlı
kodlardan ayıklamaya çalışacağız.
ereg() ve eregi()
PHP'nin temel Düzenli İfade Fonksiyonu, ereg(), arattığımız karakter sıralanışı
bulunduğu taktirde doğru, bulamadığı taktirde yanlış karşılığı bir değer verir. Fonksiyonu
şöyle yazarız:
$bir_degisken = ereg("eşleştirilecek_sıra" , $kaynak , $yeni_değişken);
Fonksiyonun aradığımız eşleştirmeyi yapması halinde, buradaki $bir_degisken'in
değeri true/doğru, yapamaması halinde false/yanlış olacaktır. Eşleştirme sırasının nasıl
oluşturulduğunu yukarıda gördük; bu ifadelerden işimize uygun olanı buraya tırnak içinde
yazarız. $kaynak, eşleştirilecek sıralamanın içinde aranacağı değeri tutan değişkendir.
Fonksiyonun bir diğer becerisi, eğer eşleştirilecek sıralamayı gruplar halinde verirsek,
kaynakta yapacağı eşleştirme olursa, buna uygun değerleri bir dizi değişkene
yazabilmesidir; istersek bir parametre olarak bu yeni değişkenin almasını istediğimiz adı
veririz; böylece eşleştirme sonucu bulunan değerler kaydedilmiş olur.
eregi(), aynen ereg() fonksiyonu gibi çalışır; sadece eşleştireceği değerlerde büyükharf/küçük-harf
farkı gözetmez.
Daha önceki bölümde oluşturduğumuz ve kd_01.php adıyla kaydettiğimiz konuk
defteri programının akış planını, ziyaretçinin Form'a yazdığı ve sunucuda
$HTTP_POST_VARS dizi-değişkeninde tutulan değişkenlerinden elektronik posta adresi ilge
ilgili olanı gerçekten içinde en az bir @ işareti ile en az bir adet nokta içip içermediğine
bakarak sınayabiliriz. Böyle bir sınama için gerekli kod şöyle olabilir:
Kod:
if (eregi("^.+@.+\\..+$", $adres, $email)) {
}
else {
$hata = "Elektronik posta adresinizde bir hata var!<br>";
echo $hata;
include("kd_hata_halinde.htm");
exit;
}
Program, bu örnekte $adres değişkeninde kayıtlı değerin içinde aradığı sıralamayı
bulursa, eşleşen değeri $email adlı yeni bir değişkene yazacak ve if sınavının sonucu doğru
olacaktır. Bu sıralamaya uygun bir değer bulunamazsa, if sınavı else deyimine atlayacak ve
bir hata mesajı üretilerek, bu program durdurulacaktır. (Burada, yaptığı hayatı düzeltmesi
yani geçerli bir elektronik posta adresi vermesini sağlayan yeni sayfanın, include komutu
ile ziyaretçiye gönderildiğine dikkat edin. Bu programın yeni biçimi kitapçığın örnek kodları
arasında kd_02.php adıyla bulunabilir.)
ereg_replace() ve eregi_replace()
Gördüğümüz gibi, ereg() arattığımız karakter sıralanışı bulunduğu taktirde doğru,
bulamadığı taktirde yanlış karşılığı verdikten sonraduruyor! Oysa kimi zaman arattığımız
ve bulunan değerin başka bir değierle değiştirilmesi gerekebilir. Bunun için ereg_replace()
ve eregi_replace() fonksiyonlarını kullanırıız:
ereg_replace("eşleştirilecek_sıra" , yeni_metin , $kaynak);
Fonksiyonun aradığımız eşleştirmeyi bulursa, bu değerin yerine verdiğimiz yeni
metni koyacaktır; yeni metni bir değişkenin değeri olarak da verebiliriz. Uygulama örneği
için yine konuk defteri örneğine dönelim. Ziyaretçilerimiz kimi zaman yanlışlıkla, kimi
zaman pek de iyi niyet sonucu olmadan, kendilerinden beklediğimiz isim, adres ve mesaj
yerine sunucu veya başka ziyaretçilerin Browser programları tarafından kod gibi
algılanacak metinler yazabilirler. PHP'de güvenlik bölümünde bu konuda daha ayrıntılı bilgi
bulacaksınız. Burada sadece bu tür zararlı metinlerin genellikle programlarda bulunması
gereken karakterler içerdiğini söylemekle yetinelim. Bu tür karakterlerin başında < ve >
işaretleri bulunur! Dolayısıyla, biz de ziyaretçimizden gelecek verilerin yazıldığı
değişkenlerin değerlerinde bu işaretleri aratabilir ve bunları içi boş bir alfanümerik değer
ile değiştirebilir; yani silebilir. Zararlı olabilecek kodların arasında daha bir çok karakter
bulunabilir; ancak Script diliyle yazılması gereken bu kodlardan < ve > işaretlerini
kaldırılması kodları işlemez hale getireceği için, şu aşağıdaki örnek yeterli olabilir:
Kod:
$adi = ereg_replace("<","",$adi);
$adi = ereg_replace(">","",$adi);
$adres = ereg_replace("<","",$adres);
$adres = ereg_replace(">","",$adres);
$mesaj = ereg_replace("<","",$mesaj);
$mesaj = ereg_replace(">","",$mesaj);
Burada ereg_replace() fonksiyonu, ziyaretçiden gelecek üç değişkenin değerlerinde
< ve > işaretlerini aramakta onların yerine içi boş bir metin ("") yazmaktardır. (Bu örneği
içeren konuk defteri, bu kitapçığın örnek kodları arasında kd_03.php adıyla bulunabilir.)
split()
Düzenli İfade ile çalışan bu fonksiyon, vereceğimiz eşleştirme sıralamasını sınırlayıcı
olarak kullanarak, belirteceğimiz değerde bulduğu değer parçalarını ayırır ve bunları ayrı
ayrı bir dizi değişkenin elemanları olarak kaydeder. Bu fonksiyonu şöyle yazarız:
$yeni_dizi_değişken = split("eşleştirilecek_sıra" , $kaynak, sınır_sayısı);
Fonksiyon, aradığı sıralamayı bulamazsa, false/yanlış sonucunu verir. Burada sınır
sayısı olarak vereceğimiz rakam, oluşturulacak yeni dizi değişkene en fazla kaç eleman
yazılmasını istediğimizi gösterir. Bu sayıyı vermezsek, PHP yeni dizi değişkenin gerektiği
kadar elemana sahip olmasını sağlar. Bir örnek vererek, bu fonksiyonu nasıl
kullanibileceğimizi görelim:
Kod:
<?php
$metin = "İnsan sözüyle kendini gösterir, davranışlarıyla ruh halini
aksettirir.";
$aranan = " ";
$yeni_dizi_değişken = split($aranan, $metin);
foreach ($yeni_dizi_değişken as $eleman) {
print "$eleman <br>";
}
?>
Bu programda PHP, $metin değişkeninin içerdiği değerde $aranan değişkeninin
içerdiği değeri, yani boşluğu, eşleştirilecek unsur olarak kullanacak ve $metin değişkeninin
değerini boşluklarından parçalara ayıracaktır. Ayrılıcak her yeni parça, $yeni_dizi_değişken
adlı değişkenin elemanları olarak atanacaktır. Programın geri kalan kısmı ise, bu yeni
dizinin elemanlarını görüntülemekterdir.
sql_regcase()
İçinde büyük harf-küçük harf ayrımı olan bir değeri büyük harf-küçük harf ayrımı
olmayan Düzenli İfadeler haline çevirir. Bu fonksiyon bizden Düzenli İfade almaz, tersine
Düzenli İfade oluşturur. Örnek:
Kod:
<?php
$metin = "Sözler";
echo(sql_regcase($metin);
?>
Bu program, Browser penceresine şu metni yazdırır:
[Ss][Öö][Zz][Ll][Ee][Rr]