mysql den xml’e dump ettik ya sonra :
Artık elimizde bir XML dokumanımız var, şöyle düşünün bu XML dokumanı belki de başka bir sistemde SQL Server tarafından belkide Oracle tarafından oluşturulmuş bir dokuman da olabilir. Çünkü artık evrensel veri depolama kurallarına(biraz abartılı geliyor gibi
) uygun olarak oluşturulmuş bir dokumanınız var, sizde bu kuralları bilir ve kurallara uygun olarak okursanız hangi veritabanı tarafından oluşturulduğu ne farkeder, bütün veritabanı dökümlerini mysql’e atabilirsiniz.
Yazının bu bölümünde yine başka bir sınıf yapısı üzerinde duracağız, bu sefer sınıfın adı xml2mysql. Yazının tamamında, daha önceki yazılarımdan birisinde de ele aldığımız PHP’nin 2 tane XML parse etme aracından birisi olan Expat’ı kullanacağız. Yoğun olarak Expat kullanacağımız için eğer okumadıysanız önce Expat temelleri ile ilgili olan o yazıyı okumanızı tavsiye ederim.
mySQL verisini xml dokumanı haline getirirken XML dokumanının yapısını hatırlarsınız.
icerik
…
…
…
En fazla 3 kademe giden dokumanlar oluşturuyoruz. En üst kademede veritabanı ismi hemen altında tablo isim etiketleriyle oluşturulmuş kayıtlar, kayıt kümelerinin içinde ise alan isimleri ve içerikleri bulunuyor.
Her veritabanı tablolardan, tablolar alanlardan, alanlarda ise içerikler bulunduğundan (alanın boş bırakılması farketmez) bütün veritabanı XML dokumanları genel olarak en fazla ve en az 3 kademeden oluşur diyebiliriz.
Scripti Expat ile yazacağız, expat bildiğiniz gibi olay tabanlı bir parser idi. Yani başlangıç etiketine, bitiş etiketine ve karakter verisine rastaladığında bizim belirlediğimiz fonksiyonları çağırıyor ve rastladığı elementle ilgili bilgileri bu fonksiyona aktarıyordu.
Programımızda expat bize sadece etiketlere ve verilere rastladığında etiket ismini ve veriyi vereceğinden, yapısını bilmediğimiz bir veritabanında hangi etiketin tablo ismi,hangi etiketin alan ismi olduğunu bulmak bize kalıyor. Ama düşününce zor görünmesine rağmen zor olmayan bir işlem, çünkü elimizde ayırtedici bir özellik var.
1. Veritabanı ismi agaç yapısında en üst kademede
2. TAblo isimleri yani kayıtların başlangıç etiketleri 2. kademede
3. Alan isimleri ise 3.kademede karşımıza çıkıyor.
4. Sadece alan isimli etiketler karakter verisi taşıdığından script içinde expat’ın bize verdiği bütün karakter verileri doğrudan en son rastlanılmış başlangıç etiketine sahip alanın (Tablo alanı yani) içeriği sayılabilir.
Yani toparlarsak program içinde yapmamız gereken işlem :
1. 1- Kademe (derinlik) bilgisini bir değişkende tutmak
2. 2- Başlangıç etiketlerine rastlandığında derinliği bir artırmak (Kademe yerine daha uygun bir kelime olan derinliği kullanacağız artık)
3. 3- Bitiş etiketine rastlandığında derinliği bir azaltmak.
Öyleyse bir başlangıç etiketine rastlandığında
1. Eğer derinlik 1 ise bu root elementidir. Ve örneğimize göre veritabanı ismini içerir.
2. Eğer derinlik 2 ise bu bir tablo ismidir ve bir kayıt bilgileri başlangıcı olarak kabul edilmelidir.
3. Eğer derinlik 3 ise bu bir alan (field) ismidir.
4. Eğer derinlik 3 ten büyük ise XML dokumanı geçerli bir veritabanı yapısı içermemektedir.
Peki etiketleri ayrıştırdık ve kayıtları,tabloları,alanları bulduk. Bir kayıt bilgisini nasıl bir SQL sorgusu haline getireceğiz ?
Yapacağımız işlem şu :
Dikkat ettiyseniz XML yapısına
1
mkarabulut
2
Raist
ben.jpg
jpg
Her kayıt bilgisi bir tablo ismi taşıyan etiket – kayıdın bulunduğu tablo – ile başlıyor, sonra bu etiket kapanana kadar rastlanılan bütün etiketler bu kayıt ile ilgili bilgileri tutuyor. Yakaladınız di mi?
1. 1- Yapmamız gereken açılmış ve kapanmamış etiketlerin isimlerini tutmak
2. 2- Derinlik bilgisine göre açık etiketlerde rastlanılan bilgileri değerlendirmek.
3. 3- Bir etiket kapatıldığında ismini açık etiketler listesinden çıkarmak.
Açık etiketlerin isimlerini tutmamızın sebebi şöyle bi şey : Mesela bir karakter verisine rastladık,expat olay tabanlı olduğu için bu karakter verisinin hangi etikete ait olduğunu bize söylemeyecektir. Bunun için biz en son açılan ve kapatılmayan etiketin bu karakter verisine sahip olduğunu bileceğiz.
Bu kadar açıklamaya rağmen aslına bakılırsa en iyi yol sizin kodu bir parça incelemeniz olacaktır, çok karışık olmadığı için dikkatli incelediğinizde ve açıklamaları göz önünde bulundurduğunuzda olaylar daha net olacaktır.
Sınıf yapısı ile ilgili olarak: Hatırlarsınız, mysql2xml sınıfında eksikliklerden biriside hata kontrolleri idi. Bu sefer hata kontrollerini sınıfa dahil ettim. Ayrıca yine istatistik nevinden bilgileri toparlayan bir metodumuz var.
Buyrun xml2mysql sınıfımız …
parser=xml_parser_create($encoding);
xml_set_object($this->parser,&$this);
xml_set_element_handler($this->parser,”startE”,”endE”);
xml_set_character_data_handler($this->parser,”cData”);
$this->xmlfile=$xmlfile;
$this->setoption(XML_OPTION_CASE_FOLDING,false);
$this->setoption(XML_OPTION_TARGET_ENCODING,$encoding);
$this->depth=0;
$this->recordnum=0;
}
function setoption($option,$value){
xml_parser_set_option($this->parser,$option,$value);
}
function parse(){
if($file=@fopen($this->xmlfile,”r”)){
while ($xmlstr=fread($file,4096)){
if (!xml_parse($this->parser,$xmlstr,feof($file))){
$err = xml_error_string(xml_get_error_code($this->parser));
$err.= “
Satır :”.xml_get_current_line_number($this->parser);
$this->error[]=$err;
} // if !xml_parse
} // while
} else {
$this->error[]=”XML dosyası $file açılamadı”;
}
@fclose($file);
return ($this->error_exists() ? false : true);
} //function parser
function startE($parser,$name,$attr=”){
$this->opentags[]=$name;
if ($this->depth==0)
$this->root=$name;
if ($this->depth==1 && !in_array($name,$this->tables))
$this->tables[]=$name;
$this->depth++;
if ($this->depth>3){
$this->error[]=”XML dokumanı geçerli bir tablo yapısı içermiyor.”;
}
}
function cData($parser,$data){
$last=count($this->opentags)-1;
if (@$this->data[$$this->opentags[$last]]!=”")
$this->data[$this->opentags[$last]].=$data;
else
$this->data[$this->opentags[$last]]=$data;
}
function endE($parser,$name){
array_pop($this->opentags);
$this->depth–;
if ($this->depth==1){
//kayıt bilgileri bitti
$this->recordnum++;
$query=”INSERT INTO $name VALUES(“;
foreach ($this->data as $tag=>$veri){
if (!in_array($tag,$this->tables) && $tag!=$this->root)
$query.=”‘”.addslashes($veri).”‘,”;
}
$query=substr($query,0,-1);
$query.=”)”;
$this->queries[]=$query;
unset($this->data);
}
}
function querydb($localhost,$dbname,$user,$password){
if (!$this->error_exists()){
$conn=@mysql_connect($localhost,$user,$password);
if (@mysql_select_db($conn,$dbname)){
foreach ($this->queries as $query)
mysql_unbuffered_query($query);
} else {
$this->error[]=”Veritabanına bağlanılamadı.”;
return false;
}
} else {
return false;
}
}
function get_info($inf=”){
$info=array(
“Kayıt sayısı”=>$this->recordnum,
“Tablo sayısı”=>count($this->tables)
);
if ($inf!=”")
return $info[$inf];
else
return $info;
}
function error_exists(){
return count($this->error)>0 ? true : false;
}
function get_error(){
return @$this->error[count($this->error)-1];
}
} // class
?>
Kodu biraz inceleyin, yine tekrar edeceğim ama dikkatinizi toplamak için biraz dinlenin, çünkü bende çok yoruldum ![]()
Diğer sayfada açıklamalar ile devam edelim…
Class xml2mysql
Özellikler
* parser
* xml_file
* opentags
* data
* depth
* root
* tables
* recordnum
* queries
Metodlar
xml2mysql()
setoption()
parse()
startE()
endE()
cData()
querydb()
get_info()
error_exists()
get_error()
Özellik isimlerinden ve metod isimlerinden aslında ne işe yaradıkları anlaşılmaktadır. Zaten fonksiyon isimleri ve değişken isimleri seçilirken anlaşılır şeyler seçmek güzel bi yoldur. Peki diyeceksiniz neden ingilizce
? Buda kişisel bi seçim,sebebi basit, türkçe karakterlerin programlama dillerinde desteklenmemesi. (Belki de psikolojik bir sebebi de vardır,kimbilir ?
)
Metodlardan kısaca söz edersek :
xml2mysql() : Constructor olan bu fonksiyonumuz tıpkı, bir önceki sınıf yapımızda olduğu gibi sınıf yapısı için gerekli bazı değişkenlere ilk değer atıyor. Bu ilk değerleri ise parametre olarak alıyor.
Yalnız burada bahsetmek istediğim bir fonksiyon var, expat makalesini okumuş olsanız bile size yeni olacak bir fonksiyon kullandık :
parser,&$this);
?>
//Bu fonksiyon sadece Expat ile OOP ‘i birlestirmek
//istediginiz zaman kullanilir. Normalde
//ile call-back fonksiyonlarını tanıttığınızda bu fonksiyonlar
//yazılan script içinde global olarak erişilebilen fonksiyonlar
//arasında aranır bulunamazsa hata verir.
Bir sınıf yapısı içindeki fonksiyonlar ise global olarak erişilebilen fonksiyonlar değildir ancak sınıf adı ve ‘->’ operatörü ile erişilir (obj->fonk() gibi). İşte tam burada eğer sınıf içindeki fonksiyonları call-back fonksiyonu olarak tanımlamak istiyorsak xml_set_object() fonksiyonunu kullanmamız gerekiyor. Fonksiyonun 2. parametresi ile sınıf nesnesine bir referans parametre olarak veriliyor ve böylece tanımlanan call-back fonksiyonları artık sınıf metodları oluyor. (Bu arada &$this kullanımı içindeki ‘&’ işaretini ve referansları bilmiyorsanız şimdilik sadece bu kullanımı öğrenin.)
setoption(): Tanımlanan XML parser’ın özelliklerini ayarlamak için kullanacağımız metoddur. Kısaca parametre olarak aldığı özelliği yine parametre olarak aldığı değer olarak ayarlar. Burada yine expat’ın bir fonksiyonunu kullanıyoruz.
xml_parser_set_option($parser,özellik integer değeri ,”Değer”);
Bu fonksiyon parser için iki tane şeyi ayarlar :
1- Büyük küçük harf hassasiyeti : XML dokumanı içinde rastlanılan etiketlerde büyük-küçük harf duyarlılığı ayarlanır. true verilirse bütün etiket isimleri parse işlemi sırasında büyük harfe dönüştürülür. false verilirse etiket isimlerinin orjinalliği korunur.
2- Karakter encoding : Parse edilen dokumanlar verilen karakter setine göre parse edilir. Eğer karakter setinin desteklemediği karakterler olursa, parse işlemi hata verir ve durur.
parse() : sınıf yapısına uygun olarak nesne oluşturulurken verilen XML dokumanı,karakter setine göre XML dokumanına parse işlemi yapılır. Bu işlem sırasında bir hata oluşursa nesnenin $error özelliğine hata tanımlaması aktarılır. Böylece bu işlem sırasında bir hata oluşmuşsa sınıfın diğer metodlarının çalışması parse işleminin başarısına bağlı olduğu için olması gerektiği gibi kontrol edilmiş olur.
startE(),endE(),cData() : Bu fonksiyonlar parser oluşturulurken tanımlamış olduğumuz call-back fonksiyonlarıdır. Bu fonksiyonların kısaca ne iş yaptıklarını bir önceki sayfada anlattığımızdan çok fazla ne yaptıklarından bahsetmeyeceğim. Yalnız kodu incelerken size yabancı gelebilecek fonksiyonlardan bahsetmek istiyorum.
addslashes() : Bu fonksiyon string içindeki zararlı olabilecek karakterlerin başına ‘\’ koyarak onları zararsız hale getirir.Bir örnekle çıklayayım :
Yukarıdaki sorgu çalıştığı anda hata verecektir,çünkü string içindeki ‘ karakteri sorguya eklendiğinde sorgu şöyle bir şey olacaktır.
UPDATE tablo SET text_alan=’Zararlı string ‘ asdasd’
Gördüğünüz gibi ‘ karakteri sorguda sorun çıkarmaktadır. İşte eğer bunun gibi sorgu için veritabanına gönderilecek stringler öncelikle addslashes() fonksiyonundan geçirilerek eğer varsa sorun çıkarabilecek karakterler olan ‘,” gibi karakterlerin önüne \ getirilerek zararsız hale getirilir.
in_array($değişken,$dizi) : Bu fonksiyon 1. parametresi ile verilen değer eğer 2. parametredeki dizi içinde varsa true yoksa false dönderir.
array_pop($dizi) : Bu fonksiyon parametre ile verilen dizi değişkenin son elemanını diziden çıkarır, böylece dizinin bir elemanı eksilmiş olur. Çıkarılan eleman fonksiyonda geri dönderilir.
unset($değişken) : Verilen değişkeni siler.
Fonksiyonlardan sonra birde değişik bir kullanım dikkatinizi çekmiş olmalı,mesela şu ifade :
return ($this->error_exists() ? false : true);
return bildiğiniz return ama,? : gibi şeylerde nedir diyorsanız, bunun if-else için bir kısayol olduğunu söylemem gerek. C++ ile uğraşanların hemen tanıdığı bu syntax if-else yerine kullanılmaktadır. Kullanımı
$değisken = $d > 5 ? “D 5 ten büyük” : “D 5 ten büyük değil ” ;
Eğer $d 5′ten büyükse ? işaretinden sonraki ifade $degisken değişkenine atanır, değilse : işaretinden sonraki değer atanır.
Daha değişik bir kullanım
5 && $d
Yukarıdaki örnekte karşılaştırma ifadelerinin sonucu true ise “Herşey tamam” değilse “Bi şeyler hatalı galiba” ekrana yazılacaktır.
querydb() : Sınıfın endE() metodu ile elde edilen kayıtlar SQL sorgularına dönüştürülmüştü, işte bu sorgular işlenmeyip hepsi sırasıyla $queries özelliğine atandı. Böylece $queries bir dizi değişken olmuştu. querydb() metodu ise işte bu sorguları teker teker mysql’e sorgu olarak gönderip,işlemiş oluyor. Parametre olarak veritabanına bağlanmak için gerekli server,veritabanı adı, kullanıcı adı, parola alıyor. Tabii ki hata kontrolleri var.
get_info() : Parse işlemi sırasında bulunan kayıt sayısı,tablo sayısı gibi değerler sınfın bazı değişkenlerine atılmıştı. get_info() fonksiyonu istenirse parametre olarak verilen özelliği,verilmezse dizi olarak bütün (Şu an için toplam 2 tane) özellikleri geri dönderir.
error_exists() : Eğer işlemler sırasında bir hata oluşmuşssa true, hata yoksa false dönderir.
get_error() : Eğer hata varsa oluşmuş olan en son hata tanımını geri dönderir.
Böylece sınıf yapımızı incelemiş olduk, eğer dikkatli incelerseniz sınıf yapısına yeni özellikler ve metodlar eklenebileceğini, hatta varolan özelliklerin bile bazılarının kullanılmadığını göreceksiniz.Mesela $this->tables rastlanılan tüm tabloların isimlerini tutmasına rağmen pek kullanılmamıştır. Parse işleminden sonra güzel bir get_info() metodu yazılabilir örneğin veya buna benzer bir şeyler…
Şimdi sınıf yapımızın nasıl kullanıldığını gösteren örnek bir kod inceleyelim :
Sınıf yapımız
parse()){
echo “Dokuman basariyla parse edildi,bilgiler : “;
foreach ($par->get_info() as $info=>$val){
echo “$info : $val
“;
}
echo “Sorgular isleniyor”
:
$par->querydb(“localhost”,”veritabani”,”mkarabulut”,”parola”);
if ($par->error_exists()) {
echo “Sorgular islenirken bir hata olustu :”;
echo $par->get_error();
} else {
echo count($par->queries). “sorgu basariyla islendi(mi?)”;
}
} else {
echo “Bir hata olustu:
“;
echo $par->get_error();
}
?>
Böylece xml’den mysql’e ve mysql’den xml’e veri dump etme işlemlerini bitirmiş olduk. Elimizde iki tane sınıf yapımız var,bunlar sadece bu yazıyı yazarken göstermek amacıyla yazılmış scriptlerdir. Tabii ki daha çok geliştirilebilir ve belki de hataları çıkabilir.
Ama siz buradaki mantığı ve gidişatı anladıysanız bu kodları geliştirmek sizin elinizde. Mesela benim ilk aklıma gelen yazının içinde de bahsettiğim gibi mysql2xml sınıfına XSLT desteği eklemekti. Veya mysql2xml sınıfına hata kontrol metodları eklemek gibi.
xml2mysql sınıfı ilk bakışta ilk sınıfa göre daha detaylı ve hatasız görünüyor, fakat doğrusunu söylemek gerekirse xml2mysql sınıfını BLOB yapıları içeren veritabanlarıyla hiç denemedim
BLOB yani binary data belkide text olarak hata verecektir. Belkide bu sınıf için geliştirilmesi gereken nokta bu olabilir. Belki de olmayabilir ama bunları denemeyi ve çözmeyi size bırakıyorum ![]()
xml2mysql sınıfının bir eksik görünen noktası ise veritabanı işlemleri sırasındaki hataları kontrol etmemesi,bu da geliştirilmesi gereken başka bir nokta olarak görülüyor.
Herneyse, umarım size yardımcı olmuş ve bir fikir sahibi olabilmişsinizdir.
Ve umarım bu yazılar aşağıdakiler için size yardımcı olmuştur.
1. Nesneye yönelik programlama
2. Expat kullanma
3. Yeni bir kaç PHP özelliği ve fonksiyonu öğrenme.
4. XML dokumanlarına biraz daha yakınlık
Şimdilik bu kadar, ayrılmadan önce bu yazı ile ilgili görüş, yorum ve eleştirilerinizi bana yazabileceğiniz hatırlatmak isterim, mail adresim mkarabulut@ceviz.net. Ayrıca forumumuzda her zaman sizin için bir thread vardır, tek yapmanız gereken forum.ceviz.net adresinden Net programlama kategorisine girmek, orada bu konu veya diğer PHP ağırlıklı konuları konuşabiliriz.
Görüşmek dileğiyle.
‘Stand firm in the straight path as you are commanded’