Yazılım

Yazılım işleri

Bir Web Sunucusu Kodlama Denemesi

Her şey Altan Tanrıverdi‘nin Ubuntu üzerine nginx kurulumu yazısını görmemle başladı. Hayır, Nginx’i daha önce görmemiş değildim, ancak Tanrıverdi’yi Apache’den başka bir web sunucusu kurmaya yönelten şey Apache’nin sistem kaynakları açısından “pahalı” sayılabilecek bir sunucu olmasıydı.

Apache’de bir web sunucusundan beklediğimiz her şey ve hatta fazlası var. Apache modül listesine (http://httpd.apache.org/modules/ ve http://modules.apache.org/) daha önce göz atmadıysanız bir bakın; belki de adını bile duymadığınız, hiç işiniz düşmemiş olduğu için kullanmadığınız bir çok modül olduğunu göreceksiniz. Oysa çoğu LAMP/LAPP programcısı, günlük işlerinde php ve rewrite modulleri dışındakileri kullanmaya pek ihtiyaç duymaz.

Elbette asla yılların eskitemediği dost Apache’ye kafa tutmak için değildi, ancak kafama şu soruyu takmadan edemedim:

“Acaba yalnız PHP yorumlayan ve başka hiç bir iş yapmayan bir web sunucusunu yine PHP ile kodlasak nasıl olurdu?”

Tabii bu soru büyük olasılıkla benden başka bir sürü kişinin de aklından geçmişti veAmerika’yı yeniden keşfetmemek için önce Hz. Google’a sordum.

Google bana “Nano Web Server var.” dedi. Bir de baktım ki, gerçekten bu arkadaş Apache’yi neredeyse bire-bir PHP’ye port etmiş. Modüllerine kadar yazmış. Ubuntu için deb paketi bile var ve daemon olarak kuruluyor. Öte taraftan bu çalışma hem benim ölçeğimi aşıyor hem de başlangıçtaki mantığımla ters düşüyor: PHP’den başka şey çalıştırmak istesem zaten “runtime’da yorumlanan” değil “derlenmiş” kod kullanmayı tercih ederim. Benim fikrimin özü, zaten native olarak PHP yorumlayabilen bir kod yapısı kullanarak bir web sunucusu yapmak.

Bunun dışında bir de phpStack‘i buldum. phpStack benim amaçladığım şeye oldukça yakındı ancak bana göre işleri olması gerekenden daha karmaşık hale getiriyordu.

Böylece kendi denememi yapmaya karar verdim. Önce php5-cgi paketini kurdum. PHP Manual’dan jenerik bir soket kodu aldım. Kodu düzenledim, mime türlerine göre gönderilecek header’ları ayarladım, PHP yorumlayıcısı desteği verdim ve sonuç:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
<?php

interface server
{
    public function setWebRoot($path);
    public function setPort($port);
    public function serve();
}

final class webserver implements server
{
    private $_port , $_webroot;
    public function __construct()
    {

       //belki ileride lazım olur. şimdilik dummy
    }
   
    public function setPort($port)
    {
        $this->_port = $port;
    }
   
    public function setWebRoot($path)
    {
        $this->_webroot = $path;
    }
   
    private function _mimeHandler($input)
    {
            $fileinfo = pathinfo($input);
            switch ($fileinfo['extension'])
            {
                  case 'png':
                      $mime = "text/png";  
                      break;
                 
                  case 'jpg':
                  case 'jpeg':
                      $mime = "text/jpeg";  
                      break;
                 
                  case 'gif':
                      $mime = "text/gif";  
                      break;
                 
                  case 'css':
                      $mime = "text/css";  
                      break;
             
                  default:
                      $mime = "text/html; charset=UTF-8";
            }
        return $mime;
    }
   
    private function _handleCode($contents)
    {
         //eval() edilecek kodun başında <?php ya da <? varsa çakar. halledelim.
        $contents = trim($contents);
        if(substr($contents , 0 , 5) == '<?php')
        {
            $contents = substr($contents , 5);
        }
        elseif(substr($contents , 0 , 2) == '<?')
        {
            $contents = substr($contents , 2);
        }
       
        return $contents;
    }
   
    public function serve()
    {
        $socket = @socket_create_listen($this->_port);
        if (!$socket)
        {
          echo "Soket olamiyor!\n";
          exit;
        }
        while (1) //daire-i ilanihaye!
        {
          $client = socket_accept($socket);
          $input = trim(socket_read ($client, 4096));
          echo $input; //tarayıcı ne diyor? bu veriyi ileride $_SERVER array'ini doldurmak için kullanmak lazım.
          $input = explode(" " , $input);
          $input = $input[1];

          $mime = $this->_mimeHandler($input);
         
          if ($input == "" || $input == '/')
          {
            $input = "/index.html";
          }
          $input = ".$input";
          $inputArray = explode('?' , $input);
          $input = $this->_webroot . $inputArray[0];

          if (file_exists($input) && is_readable($input))
          {
            echo "Dosya: $input\n";
           
            $contents = file_get_contents($input);
            if(strstr($input , '.php'))
            {
             
                $contents = $this->_handleCode($contents);
               
                //PHP çalıştıktan sonra sonucu echo etmesini değil, $contents stringine atmasını istiyoruz.
                ob_start();
                eval($contents);
                //gidip $contents'in üstüne yazmak doğru alışkanlık değil ama yeni değişken register etmek istemiyorum
                $contents = ob_get_contents();
                ob_end_clean();
            }
            $output = "HTTP/1.0 200 OK\r\nServer: HyperTextServer\r\nConnection: close\r\nContent-Type: $mime\r\n\r\n$contents";
          }
          else
          {
            $contents = "<title>UHU 404</title><h2>UHU 404</h2>Iın-ıın-ıın-ıın-cav-cav-cav-cav-oooooo - oooooo - zört - zört - zört -zört (araba alarmı efekti) Çok afedersin müdürüm ama yok öyle bir dosya.";
            $output = "HTTP/1.0 404 OBJECT NOT FOUND\r\nServer: HyperTextServer\r\nConnection: close\r\nContent-Type: text/html; charset=UTF-8\r\n\r\n$contents";
          }
          socket_write($client, $output);
          socket_close ($client);
        }
        socket_close ($socket);
    }
}
$server = new webserver();
$server->setWebRoot('/var/www/');
$server->setPort(8008);
$server->serve();

Bu kodu (örneğin /var/www altına) hyperTextServer.php adıyla kaydediyoruz.
Daha sonra varsayılan olarak görüntülenmesi için örnek bir /var/www/index.html dosyası oluşturuyoruz.
Son olarak

1
php-cgi /var/www/hyperTextServer.php

ile sunucuyu çalıştırdıktan sonra tarayıcıdan

http://localhost:8008

adresini çağırdığımızda oluşturduğumuz index.html dosyasının servis edildiğini görüyoruz.
Artık bir adım daha ileri giderek bir PHP dosyası çağırmayı deneyebiliriz:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
$start = microtime(1);

for($n = 0; $n <= 10000; $n++)
{
    echo $n . ' ';
}
$end = microtime(1);
$elapsed = $end - $start;
echo "<br/>vakit(ms): $elapsed";
$fileHandle = fopen('serverBenchmark.txt' , 'a');
fputs($fileHandle , $elapsed . "\n");
fclose($fileHandle);

Bu kodu da /var/www/test.php adıyla kaydedip tarayıcıdan
http://localhost:8008/test.php adresini çağırdığımızda PHP kodumuzun da gayet başarılı şekilde yorumlandığını görüyoruz.
Gördüğünüz gibi test.php dosyası, serverBencmark.txt dosyasına, çalışmasının ne kadar zaman aldığını yeni bir satır olarak yazıyor. Böylece sevdiğimiz “benchmarkçılık” oyununu oynayabileceğiz.
test.php kodunu arka arkaya yüz kere önce kendi yazdığımız sunucu ile 8008′den, daha sonra da yine yüz kere apache üstünden (default 80.port) çalıştırıp basit bir aritmetik ortalama alacağız.
Benim lokal sistemimdeki sonuçlar söyle:
Apache ortalama: 0,0174917006493 ms
Bizim sunucu ortalama: 0,0098775601387 ms

Görüyoruz ki, beklediğimizin aksine PHP üstünde çalışan bizim sunucu, derlenmiş kod olarak çalışan Apache’den iki kat kadar hızlı çalışıyor. Bu durumun açıklamasının aslında çok da zor olmadığını düşünüyorum: Apache bir sürü kontrol yapıyor, aynı anda birden fazla bağlantı kabul edebiliyor, bir sürü port dinliyor, vs. Öte taraftan bizim sunucumuz yalnız PHP kodunu çalıştırmaya odaklanmış. $_SERVER’a, $_REQUEST’e bir değer atamak için tarayıcı verisini işlemiyor ve tek, basit bir süreç olarak çalışıyor.
Aynı işleri bizim sunucumuza yaptırdığımızda, büyük olasılıkla aradaki hız farkı kapanacak. Yine de şu andaki duruma bakarak iki kat yavaşlayacağını öngörmüyorum sonuç olarak başlangıçta umduğumun çok daha fazlasını alabildiğimi gözlemliyorum.

Bundan sonra yapılacaklar:
1- $_SERVER, $_REQUEST, $_POST, $_GET, $_FILES ve $_COOKIE arraylerinin doldurulması.
2- pcntl eklentisi ile süreçlerin “çatallanması”

Yukarıdaki iki maddelik yapılacaklar listesi de temizlendiğinde elimizde basbayağı düzgün çalışan bir web sunucumuz olacak.

Paylaş:
  • Print
  • FriendFeed
  • Twitter
  • Digg
  • Sphinn
  • del.icio.us
  • Facebook
  • Mixx
  • Google Bookmarks
  • Blogplay
  • DZone
  • LinkedIn
  • MySpace
  • Ping.fm
  • Reddit
  • StumbleUpon
  • Technorati

Etiketler: , , , ,

Don Quijote. Wikipedia arşivinden

Geliştirdiğim “büyük-iri” sayılabilecek türden bir projeyi müşterime göstermek istiyorum. Müşterim de ciddi bir insan; yaptığınız iş kadar, nasıl yaptığınızla da ilgileniyor. Bunun için Active Collab kullanıyoruz ve memnunuz. Kodlamayı da rahatça takip edebilmesi için ona da VPS üstünde bir hesap açtım; her şey iyi.

Kodun bitirdiğim bir kısmını VPS’e yükledim ve test edeceğim. Fakat o da ne! Lokal sistemde şahane çalışan kod, VPS üstünde patlamasın mı!

Lokal sistem ubuntu 9.10/Suhosin-Patch’li PHP 5.2.10-2ubuntu6.4. Sunucu ise ubuntu 9.04/PHP 5.2.6-3ubuntu4.5

Yani ortada çok tuhaf konfigürasyonlar yok. Ubuntu’nun iki ardıl sürümü. Yazdığım kod da öyle “patlayabilir” cinsinden değil, sıradan bir kod.

Yalınlaştırmak gerekirse:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?php

class B
{
    public function __construct()
    {
    }
}

class A extends B
{
    public function __construct()
    {
        parent::__construct();
        //biraz kod
    }
   
    public function index()
    {
        //biraz kod
        $this->_methodPrivate();
        //biraz daha kod
    }
   
    private function _methodPrivate()
    {
        //burada da biraz iş...
    }
}

Türünden bir kod. Bakıyorum bakıyorum “hata nerede?” diye, bulamıyorum.
Başkasına sorarak iş yapmayı sevmememe rağmen, “herhalde benim gözüm görmüyor” diyerek friendfeed PHP odasına dahi soruyorum. I-ıh. Çözebilen yok çünkü ortada çözülecek bir şey yok.

Genel olarak bu tür işlerin FTP encoding dertlerinden olduğunu önceki deneyimlerimden (“acılarımdan” diye de okuyabilirsiniz) biliyorum ve kullandığım gFTP’yi bırakıp konsoldan sftp çekiyorum: Sonuç aynı. SSH ile VPS’e geçip nano’ya yapıştırıyorum: “Bana mısın!” demiyor!
Hissel olarak şunu deniyorum:
_methodPrivate()’ı methodprivate() şeklinde denediğimde her ne hikmetse kod çalışıyor! Ancak öyle yarım-yamalak, ne yaptığımı bilmez halde iş yapamam ki!

“Hmm” diyorum, “Bu eski PHP sürümü. Bug vardır bunda!” (Bakınız kaşıntı başlıyor.)
“Sahi, bu Ubuntu 9.04′e nasıl PHP 5.3 kurarım?” (Madem 9.10′da çalışıyor, sürüm yükseltsene be adam! PHP de düzelmiş olur ha? Yook kaşınıyorum ya, 9.04′e kuracağım!)
Vakıa, “Ubuntu 9.04 PHP5.3″ diye arıyorum ve şu yazıyı buluyorum. Bir bakıyorum; abim derlemiş, çalıştırmış phpinfo() ekran görüntüsü bile koymuş.

Bir programcının, Ubuntu kurduktan sonra kuracağı ilk paketlerden biri “build-essential”dır, tamam anladık ama, insan olmanın belki de en kötü taraflarından biri de kişinin mutluluklar kadar acıları da çabuk unutması. Hafızam beni yanıltmıyorsa, en son geçen yıl yine bu aylarda kaynak koddan PHP derlemiştim; demek ki akıllanmamışım!

Abimin yazısından yola çıkarak PHP5.3.0 indirdikten sonra ve kendi işlerime uygun olarak şöyle bir konfigürasyon öngörüyorum:

1
sudo ./configure --with-apxs2=/usr/bin/apxs2 --with-mysql --with-pdo-mysql --with-zlib --enable-calendar --with-curl --enable-dba --with-enchant --with-gd --enable-gd-native-ttf --with-gmp --with-mcrypt --with-readline --enable-soap --enable-zip --with-pgsql --with-pdo-pgsql

Tabii Allah’ın emri: “o kütüphane eksik, şu yok, bu yok” uyarıları içinde bir sürem “sudo apt-get install libfalanca-dev” yazmakla geçiyor…

…ve derlemeye başlıyorum.
Amca önce enchant bindingini derlerken çakıyor. “Abim yazısında enchant’ı öve öve bitirememiş ama benim ne işim olacak?” diyorum ve konfigürasyondan önce –with-enchant’ı çıkarıyorum ve biraz daha ilerliyoruz.
Bilahare, Phar derlenirken su koyuveriyor. Gidiyorum, dokümantasyona bir bakıyorum ki adamlar 5.3′ten itibaren PHP-core’a dahil etmişler.

“Behey adam!”, diyorum, “madem core’a dahil edecektin, gcc4.3 ile çakmayanından yapsaydın ya!”… Zaten “warning”ler gırla gidiyor derleme boyunca; üstünde bile durmaya değmez…

Derken, PHP bug raporlarının içinde yama ararken kendimi buluyorum, ancak yama bulamıyorum!
Tek önerdikleri gecelik snapshottan kurulum.
Onu da yapıyorum. İndirdiğim ve üstünde uğraştığım PHP dizinini uçurarak php5.3-201004092030 diye bir sürüm buluyorum ve baştan başlıyorum. Tabii Phar yine derlenmiyor!

“Çok da sevmişim Phar’ını!” diyerek konfigürasyona nihai şeklini veriyorum:

1
sudo ./configure --with-apxs2=/usr/bin/apxs2 --with-mysql --with-pdo-mysql --with-zlib --enable-calendar --with-curl --enable-dba --with-gd --enable-gd-native-ttf --with-gmp --with-mcrypt --with-readline --enable-soap --enable-zip --with-pgsql --with-pdo-pgsql --disable--phar

Ve nihayet bu sefer derlenmeyi başarıyor PHP, ama dert burada bitmiyor.
“make install” kısmına geldiğimizde “İlla /etc/apache2/httpd.conf dosyanda ‘LoadModule’ bulacağım!” diye tutturuyor.
“Ya sabır!” çekerek açıyorum dosyayı, elle “#LoadModule tiriviri” yazıyorum ve bunu yutturmayı beceriyorum.
Son olarak apache’yi yeniden başlatıyorum phpinfo() çekince karşımda “PHP5.3.3-dev” yazısını görüyorum.

Artık çakan koduma bakıp çalışmasını keyifle izleyebilirim. Hala açık sayfaya refresh çekiyorum…ve…
Kod yine de çalışmıyor, aynı yerde çakıyor!
“Kesin encoding bu! Kesin encoding bu! Encoding ulan!” diyerek bildiğim son numaraları da deniyorum: lokal makineden bir dizini sshfs ile vps’in ilgili dizinine mount ediyorum, lokaldeki dizinden bağlanmış uzak sistem dizinine kopyala+yapıştır deniyorum… Tabii ki yine olmuyor!
…derken, bir anda ilham geliyor ve methodPrivate() içindeki tarihleri Türkçe bastırmak için kullandığım

1
setlocale(LC_ALL , 'tr_TR.UTF-8');

satırını comment ediyorum, deniyorum, a-aa çalışıyor!
O ana kadar hiç aklıma gelmemiş zira işletim sistemini kurar kurmaz apt-get install language-pack-tr-all gibisinden bir paketi de kurmuşum.
Yukarıdaki satırı

1
setlocale(LC_TIME , 'tr_TR.UTF-8');

yapıyorum (biraz altta tekrar en_GB lokaline çevirmem gerekiyor yoksa PgSQL sorgularım patlıyor) ve iş bitiyor!
Kodda bir kelimelik değişiklik yapabilmiş olmak için sisteme gecelik snapshot bir sürüm kurduğumla kalıyorum… Kendi kendime sinirli sinirli gülüyorum ve bu yazıyı yazmaya başlıyorum!

Paylaş:
  • Print
  • FriendFeed
  • Twitter
  • Digg
  • Sphinn
  • del.icio.us
  • Facebook
  • Mixx
  • Google Bookmarks
  • Blogplay
  • DZone
  • LinkedIn
  • MySpace
  • Ping.fm
  • Reddit
  • StumbleUpon
  • Technorati

Etiketler: , , ,

Her seçim bir kaybediştir.” Jean-Paul Sartre

Tedrisat itibarıyla dilbilimci/çevirmen sayılabilirim. (“Aşkın Metafiziği” kitabının başlangıcı gibi oldu ancak ben Danzig’li değilim) Doğal dillerle beraber, yapay dillerle tanışmamın da, yaşım/teknoloji karşılaştırmasını yaptığımız vakitte “erken” bir yaşta, televizyonun renkli yayına geçtiği tarihlerde, dokuz ya da on yaşımda (ve kaza eseri) olduğunu söyleyebilirim.

“Yapay dil” derken hem Esperanto, hem de Basic ve Assembly’den söz ediyorum. Aynı zamanda Internet’in kavram olarak bile yaşamımızda bulunmadığı, tüm bilebildiklerimizin, aslında bizden daha fazlasını bilmeyen “Sözde Ermeni Kıyımı” yollu “bilgisayar mühendislerinin” çevirdiği boktan kitaplardan ve bir takım ingilizce metinlerden edinilmeye çalışıldığı zamanlardan da söz ediyorum.

Sinclair ZX Spectrum ve daha sonra anamın zavallı memur maaşına yapılan taksitlerle edinilen Amstrad CPC 6128′den ve Locomotif Basic’ten, Pascal’dan, yetmedikleri yerde Assembly’den de söz ediyorum, zira o zamanlar o makinelerde oyun oynamak kod yazmaktan daha zor, kod yazmak ise deveye hendek atlatmak demekti!

Zaman içinde hasb-el-kader (daha doğrusu “merak” ve “yaşamın akışı”) bir kaç doğal ve yapay dil daha öğrenmek olanağı buldum, fırsat buldukça yenilerini inceleyebilmek ve vaktim oldukça öğrenmek için hala can atıyorum ve bu hevesim, görülebileceği gibi çocukluktan kalma. Diğer yandan, üst cümlede altı çizilecek ayrıntı benim için “vakit”.

Çocukluk hevesim programcılığın, akademik kariyerimin önüne geçip ekmek teknem haline gelmesi, 1999 yılının başlarına ve o vakit başka bir Türk programcıya gönderdiğim, yazdığı kodla ilgili bir soru içeren bir e-postaya gelen yanıta denk gelir: “Hocam, sen hakikaten dilbilimci misin? Benle kafa buluyorsun gibime geliyor. Söylediğin şeyleri ben programcı olarak yazdığım kodda düşünmemiştim.”

Elbet o vakitler çok cahildim ve nasıl bir batağa saplandığımı(!) bilmiyordum ancak kariyer değişikliği için beni tetikleyen bu küçücük “gaz” oldu. O vakitten bu yana durmak dinlenmek bilmeden öğreniyorum, kod yazıyorum, hala yazılım tasarım mantığı üstüne öğrenmeye ve öğretmeye devam ediyorum ve bugün geriye baktığımda utanmadan söyleyebilirim ki, akademik ortamlarda “şu ya da bu programlama dili”, “şu ya da bu kod tasarım örüntüsü” ile beynim düdüklenmediği için çok şanslıyım ve bu sayede aynı fakültelerde derslerde örnek verilen kodlar yazabiliyorum.

Bu uzun sayılabilecek girişi, nasıl düşündüğümü ve fikirlerimin altında yatan geçmiş alışkanlıkları ve önyargıları tanımlayabilmek için yazmak zorundaydım. Şimdiye dönelim.

Güncel durum şudur: İş çok. Buna rağmen gördüğünüz gibi blog yazmaya vakit ayırabiliyorum ve bunu temelde iki programlama diline borçluyum: Python ve PHP. Bana PHP hızlı, Python düzgün ve tertemiz kod yazdırıyor. Son on yıldır PHP ile uğraşmıyor olsam (sorunların nasıl çabucak çözülebileceğine dair deneyimi göz önüne alın) ve Python kütüphaneleri anında işimi PHP ile görebildiğim kadar zengin olsa, bugün tereddüt etmeden her şeyimi Python’a taşırdım. İşin doğrusu şu ki, bugün de çoğu hassas işlerimi Pyton’a yaptırıp, PHP’den çağırıyorum ve son üç yıldır günden güne Python’u daha çok benimsediğimi hissediyorum. Bunu ben koskoca bir PHP framework’ü yazacak derecede PHP’yi seven adam olarak söylüyorum.

Pekiyi, bu kadar vakti nereden bulabiliyorum? Basit: JAVA kodlamıyorum. JAVA biliyor muyum? Herhalde sizden iyi biliyorum(!) ve uzun vakti “Düzgün bir programlama dili nasıl olmalı?” sorusuyla geçmiş kişi olarak JAVA’yı hakikaten çok beğeniyorum ancak söz bana ait değil, anonim: “JAVA ile üç birim vakit harcayan, PHP ile bir birim vakit harcar.” Üstelik JAVA’nın PHP ve Python’a her benchmarkta üstün çıktığı da kılavuz sitemez bir köy.

Müşteri ya da patrona “Hacı bu işi JAVA/Spring ile kodlayalım, süper olur ama iki kat fazla para ve vakit isterim!” diye anlatamazsınız. Adam size fazladan $20.000 vereceğine, iş için kullandığı sunucuyu $500 fazla harcayarak daha iyi hale getirir, olur biter!

Neymiş? Hızlı kod için PHP, düzgün kod için Python, vaktiniz varsa JAVA!

Paylaş:
  • Print
  • FriendFeed
  • Twitter
  • Digg
  • Sphinn
  • del.icio.us
  • Facebook
  • Mixx
  • Google Bookmarks
  • Blogplay
  • DZone
  • LinkedIn
  • MySpace
  • Ping.fm
  • Reddit
  • StumbleUpon
  • Technorati

Etiketler: , ,

Diğer programlama dillerinin PHP’yi küçük görmek isteyen takipçilerinin dayandıkları en öne çıkan argümanlardan biri de PHP yorumlayıcısının loosely typed yapıyı ele alış biçimi. Gerçekten de bir değişkeni integer olarak kullanmaya başlayıp, daha sonra kod içinde bir yerlerde tür dönüşümü yapmaksızın string’e çevirip, nihayeten aynı değişkeni array olarak kullanmak yalnız PHP’de mümkün değilse de, genellikle PHP programcılarının sahip olduğu bir kötü alışkanlık.

Doğal olarak böylesi alışkanlık beklenmedik hataları da beraberinde getirmeye amade. PHP yorumlayıcısının nelere kadir olduğunu(!) görmek açısından şu örneğe göz atalım:

1
2
3
4
5
6
7
8
9
10
<?php

header('Content-Type: text/html; charset=utf-8');
$kunk = 'Künk';
$sifir = 0;

if ($kunk == TRUE && $sifir == FALSE && $kunk == $sifir)
{
echo 'Benim adım da bundan sonra Maykıl Ceksın olsun!';
}

Yukarıdaki kodu üşenmeden kopyalayıp çalıştırın. Bakalım ne oluyor? Ne, $kunk TRUE, $sifir FALSE ve buna rağmen $kunk ile $sifir birbirine eşit, öyle mi? PHP yorumlayıcısı öyle diyor!

Tabii burada

1
if ($kunk == TRUE && $sifir == FALSE && $kunk == $sifir)

ile eşitlik kontrol etmek yerine

1
if ($kunk == TRUE && $sifir == FALSE && $kunk === $sifir)

ile özdeşlik kontrol ederek bu dertten kurtulabiliriz, ancak bu tür sorunlar deneyimsiz ya da kötü alışkanlıklar edinmiş PHP programcılarının başına çok sık gelir ve bu arkadaşlar sorunun kaynağını bulana kadar gereksiz vakit yitirirler.
Öte taraftan, kişisel fikrimce kullandığım programlama dili, bana kullandığım değişkenlerin türleri üstünde de tam hakimiyet sağlayabilmeli.
PHP5′ten itibaren, nesneler ve array’ler üstünde bu hakimiyete sahibiz. PHP6 yol haritası bu konuda daha umut verici görünüyor, ancak geliştiriciler bir türlü kendi aralarında anlaşamadıklarından (http://news.php.net/php.internals/47120) PHP6, gün itibarıyla trunk deposundan kaldırılmış durumda ve bir daha ne zaman geri konulacağı ya da ne zaman kararlı PHP6 sürümü çıkacağı meçhul.

Öyleyse, değişkenlerimizi belirli bir türden (integer, string, float vb) olmaya zorlamak için ne yapabiliriz?
Bildiğim bir-iki yöntem var: İlki, Ilia Alshanetski’nin yayınladığı bir yamayı kullanarak PHP’yi yeniden derlemek.

İkincisi ve bu yazının konusu, PECL deposunda bulunan SPL_Types eklentisi. Bu eklenti sayesinde PHP ile değişkenleri istediğimiz türden olmaya zorlayabiliyoruz.

Önce SPL_Types eklentisini kurmamız gerekiyor. Aşağıda anlattığım kurulum Ubuntu 9.10 için geçerli ancak adam gibi işletim sistemlerinin hemen hepsinde adımlar çok benzer olmalı.

Konsola geçiyoruz. Önce iki satır “export” yapmamız gerekiyor:

1
2
export LC_ALL="C"
export LANG="C"

Üstteki iki satır olmadan pecl sorun çıkarıyor. Şimdi eklentiyi kurabiliriz:

1
sudo pecl install http://pecl.php.net/get/SPL_Types-0.3.0.tgz

…ve Apache’yi yeniden başlatıyoruz.

1
sudo service apache2 restart

Artık yeni eklentimizi deneyebiliriz:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php

$int = new SplInt(42);

try
{
    $int = 'Değişkeni stringe çevirmeyi deneyelim';
}
catch (UnexpectedValueException $uve)
{
    echo $uve->getMessage() . PHP_EOL;
}

var_dump($int);
echo $int;

Bu kodu çalıştırdığımızda aldığımız çıktı şöyle:

1
2
3
4
5
6
Value not an integer
object(SplInt)#1 (1) {
  ["__default"]=>
  int(42)
}
42

SPL_Types, SplInt yanında SplFloat, SplEnum, SplBool ve SplString nesneleriyle float, enum, boolean ve string değişken türlerini de destekliyor. Biz de kullandığımız, sevdiğimiz, ayrılmak istemediğimiz güzel PHP’mize biraz daha hakim olmanın verdiği tatmin hissiyle kodumuzu yazmaya devam ediyoruz.

Paylaş:
  • Print
  • FriendFeed
  • Twitter
  • Digg
  • Sphinn
  • del.icio.us
  • Facebook
  • Mixx
  • Google Bookmarks
  • Blogplay
  • DZone
  • LinkedIn
  • MySpace
  • Ping.fm
  • Reddit
  • StumbleUpon
  • Technorati

Etiketler: , ,

Sudo ilk kez Bob Coggeshall ve Cliff Spencer tarafından, SUNY/Bufalo’da Bilgisayar Bilimlieri Bölümü’nde 1980 yılı civarında tasarlandı.4.1BSD kullanan bir VAX-11/750 üstünde çalışıyordu. Phil Betchel, Cliff Spencer, Gretchen Phillips, John LoVerso ve Don Gworek tarafından 1985 Aralık ayında, güncellenmiş bir sürümü net.sources Usenet haber grubuna gönderildi.

1986 yazında, Garth Snyder sudo’nun genişletilmiş bir sürümünü yayınladı. Sonraki beş yıl boyunca sudo, CU-Boulder’da Bob Coggeshall, Bob Manchek ve Trent Hein’in aralarında bulunduğu bir kaç kişi tarafından beslenip büyütüldü.

1991 yılında, Dave Hieb ve Jeff Nieusma, “The Root Group” adında bir danışmanlık firmasıyla sözleşmeleri kapsamında, sudoers formatı daha da genişletilmiş bir sudo sürümü yazdılar. Bu sürüm daha sonra GNU kamu lisansı altında yayınlandı.

1994 yılında, sudo’yu gayrıresmi olarak CU-Boulder bünyesinde bir süredir geliştirmiş olan Todd Miller, “CU sudo”nun hata düzeltmeleri ve daha fazla işletim sistemi desteği içeren, herkese açık bir sürümünü yayınladı (sürüm 1.3). “CU”, “The Root Group” sürümünden farklılaşmak için eklenmişti.

1995 yılında, Chris Jepeway, sudoers dosyası için yazdığı yeni bir ayrıştırıcıyla katkıda bulundu. Yeni ayrıştırıcının grameri (eskisinin aksine) düzgündü ve hem sudo, hem de visudo ile çalışabiliyordu. (önceleri ayrıştırıcıları birbirinden biraz farklıydı).

1996 yılında, uzun yıllardır boş zamanlarında sudo’yu geliştiren Todd, sudo dağıtımını CU-Boulder ftp sitesinden, kendi domaini olan courtesan.com (“fahişe.com”) adresine taşıdı.

1999′da, “The Root Group” tarafından 1991 yılından beri resmi bir sudo sürümü yayınlanmamış olduğu için (asıl programcılar artık farklı yerlerde çalışıyordu), “CU” öneki düşürüldü. 1.6 sürümünden beri, Sudo’da artık özgün “Root Group” kodundan eser kalmadı ve ISC stili bir lisans altında yayınlanıyor.

2001′de, sudo web sitesi, ftp sitesi ve e-posta listeleri courtesancom’dan sudo.ws adresine taşındı (sudo.org daha öne başkası tarafından tescillenmişti).

2005′te Todd, son on yılda eklenen özellikleri daha iyi destekleyebilmek için sudoers ayrıştırıcısını yeniden yazdı. Yeni sürüm eskisinin bazı sınırlamalarını, sıralama kısıtlamalarını kaldırıyor ve birden çok sudoers dosyası için destek ekliyor.

Sudo, şimdiki haliyle Todd Miller tarafından yönetiliyor.

Todd sudo’yu genişletmeyi ve hataları gidermeyi sürdürüyor.

Özgün Metin:
A Brief History of Sudo

Paylaş:
  • Print
  • FriendFeed
  • Twitter
  • Digg
  • Sphinn
  • del.icio.us
  • Facebook
  • Mixx
  • Google Bookmarks
  • Blogplay
  • DZone
  • LinkedIn
  • MySpace
  • Ping.fm
  • Reddit
  • StumbleUpon
  • Technorati

Etiketler: ,

Web hizmeti genel olarak “ağ üzerinden erişilebilen ve istenen hizmeti sunan makinede çalıştırılan bir API” olarak tanımlanabilir.

Web hizmetlerini kabaca ikiye ayırmak olası:

  1. Simple Object Access Protocol (SOAP) kullanan geleneksel yöntem.
  2. REpresentational State Transfer (REST) kullanan görece yeni yöntem.

RESTful web hizmetlerini başka bir yazıya bırakarak, bu yazıda SOAP kullanarak basit birer web hizmet sunucusu ve istemcisi geliştirmeyi inceleyeceğiz. Yapacağımız iş, sunucuya e-posta adresi gönderip, sunucudan o e-posta adresiyle kayıtlı kullanıcıyı almaktan ibaret.

SOAP bir çok PHP prorgramcısının “öcü” gibi gördüğü bir protokol. Öyle ki, pek çok deneyimli ve gözüpek programcının bile “SOAP” adını duyunca irkildiklerine, hatta iş uygulamaya geldiğinde bazı durumlarda düpedüz “beceremediklerine” çok tanık oldum. Gerçekten de, bazı gereksiz işletim sistemleri üstünde çalışan bazı web hizmetlerinden GNU/Linux – PHP ikilisiyle hizmet almak, WSDL dosyalarını düzgün oluşturmak her zaman o kadar kolay olmayabiliyor.

Ancak tüm bu dertler sizin için bu yazıyı okuduktan sonra geçmişte kalmış olacak, sayın okur!

Öncelikle buradan NuSOAP kütüphanesini indiriyoruz. NuSOAP, bizim için WSDL oluşturma dahil hemen her işi halledecek.

İndirdiğimiz zip dosyasından iki klasör çıkıyor: lib ve samples. Bizim samples’a ihtiyacımız olmayacak, ancak örnekleri incelemenizde fayda var. Basit uygulamamız için webrootumuz altına yeni bir dizin oluşturup (örneğin Ubuntu ile “/var/www/soapTest”) lib’i bu dizine kopyalıyoruz.

/var/www/soapTest dizini altında üç dosya oluşturacağız:

  1. config.php : ayar dosyası
  2. service.php: SOAP sunucusu
  3. client.php: SOAP istemcisi

Ayrıca uygulamamız büsbütün yavan olmasın diye bir de veri çekeceğimiz MySQL veritabanı bağlantımız olacak. Benim veritabanımın adı “yazboz” ve bu veritabanında “users” isimli örnek basit bir tablom var.

Yapı ve tek satırlık kullanıcı verisi şöyle:

1
2
3
4
5
6
7
8
9
CREATE TABLE IF NOT EXISTS `users` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`email` varchar(100) COLLATE utf8_turkish_ci NOT NULL,
`name` varchar(100) COLLATE utf8_turkish_ci NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 COLLATE=utf8_turkish_ci AUTO_INCREMENT=2 ;

INSERT INTO `users` (`id`, `email`, `name`) VALUES
(1, 'test@mailinator.com', 'Georges Moustaki');

Yukarıdaki kodu konsol ya da phpMyAdmin marifetiyle veritabanımıza aktardıktan sonra ayar dosyamızı oluşturalım ve “config.php” ismiyle kaydedelim:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php

class Config
{
    public $wwwroot , $host , $db , $username , $password;
   
    public function __construct()
    {
        //URL
        $this->wwwroot = 'http://localhost/nusoap';
       
        //MySQL sunucusu
        $this->host = 'localhost';
       
        //veritabanı
        $this->db = 'yazboz';
       
        //MySQL kullanıcı adı
        $this->username = 'root';
       
        //MySQL şifresi
        $this->password = 'sifre';
    }
}

Yeni bir dosya daha oluşturalım ve “service.php” adı ile config.php ile aynı dizine kaydedelim.
Sonra, yeni service.php dosyamızda config sınıfını çağırıp veritabanı bağlantısını yapalım:

1
2
3
4
5
6
<?php

require 'config.php';
$config = new Config();

$conn = new mysqli($config->host , $config->username , $config->password , $config->db);

Artık e-posta adresinden kullanıcı adını döndüren fonksiyonu yazabiliriz:

1
2
3
4
5
6
7
8
9
10
11
function getUserNameByEmail($email)
{
    global $conn;
    $userNameQuery = $conn->query("SELECT name
                                FROM users
                                WHERE `email` = '$email' LIMIT 1"
);

    $fetch = $userNameQuery->fetch_array();
    if(!is_array($fetch)) return 'böyle bir kullanıcı yok';
    return $fetch['name'];
}

Gördüğünüz gibi normalden farklı hiç bir şey yapmıyoruz. Sıradaki iş SOAP server’ı içeri alıp ilklendirmek. Bu da çok basit ve sıradan:

1
2
3
4
5
6
7
8
9
require('lib/nusoap.php');

$server = new soap_server();

//aşağıdaki iki satır Türkçe karakter için gerekli
$server->soap_defencoding = 'UTF-8';
$server->decode_utf8 = false;

$server->configureWSDL('testnamespace', 'urn:testnamespace');

Son olarak yukarıda tanımladığımız fonksiyonu sunucuya kaydedip sunucuyu çalıştıracağız:

1
2
3
4
5
6
7
8
9
10
11
//string olarak email gönderip, sonucu yine string döndüreceğiz.
$server->register("getUserNameByEmail",
                array('email' => 'xsd:string'),
                array('return' => 'xsd:string'),
                'urn:testnamespace',
                'urn:testnamespace#getUserNameByEmail');

// aşağıdaki satır $HTTP_RAW_POST_DATA verisi ile aynı işi görür
$postData = file_get_contents("php://input");

$server->service($postData);

Sunucuyu kodlamayı bitirdik bile!
Sunucu kodumuzun tamamı:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<?php

require 'config.php';
$config = new Config();

$conn = new mysqli($config->host , $config->username , $config->password , $config->db);

function getUserNameByEmail($email)
{
    global $conn;
    $userNameQuery = $conn->query("SELECT name
                                FROM users
                                WHERE `email` = '$email' LIMIT 1"
);

    $fetch = $userNameQuery->fetch_array();
    if(!is_array($fetch)) return 'böyle bir kullanıcı yok';
    return $fetch['name'];
   
}

require('lib/nusoap.php');

$server = new soap_server();

//aşağıdaki iki satır Türkçe karakter için gerekli
$server->soap_defencoding = 'UTF-8';
$server->decode_utf8 = false;

$server->configureWSDL('testnamespace', 'urn:testnamespace');

//string olarak email gönderip, sonucu yine string döndüreceğiz.
$server->register("getUserNameByEmail",
                array('email' => 'xsd:string'),
                array('return' => 'xsd:string'),
                'urn:testnamespace',
                'urn:testnamespace#getUserNameByEmail');

// aşağıdaki satır $HTTP_RAW_POST_DATA verisi ile aynı işi görür
$postData = file_get_contents("php://input");

$server->service($postData);

Hepsi bu kadar. service.php dosyasını çağırdığınızda (örneğimizde http://localhost/nusoap/service.php) sizin için küçük bir belgelendirmeyi bile otomatik olarak oluşturduğunu göreceksiniz.
Kodu http://localhost/nusoap/service.php?wsdl şeklinde çağırdığınızda da WSDL dosyanız hazır!

Tamam, sunucuyu kodlamak gerçekten kolaymış. İstemci kısmı nasıl olacak pekiyi? Çok daha kısa ve basit olacak!
Boş bir dosya daha açalım ve onu da “client.php” ismiyle, aynı dizine kaydedelim. Ardından sunucu için yaptığımız şekilde config dosyasını çağırıp config nesnesini ilklendirelim:

1
2
3
4
ini_set("soap.wsdl_cache_enabled", "0"); //WSDL cache devre dışı kalsın
require_once 'lib/nusoap.php';
require 'config.php';
$config = new Config();

Bundan sonra yapmamız gereken tek şey, SOAP sınıfını ilklendirip, service.php’de tanımladığımız metodu çağırmak:

1
2
3
4
5
6
$soapClient = new SoapClient($config->wwwroot . '/service.php?wsdl' , array(
    "trace"      => 1,
    "exceptions" => 0));

$name = $soapClient->getUserNameByEmail('test@mailinator.com');
echo $name;

Hepsi bu! İstemci kodunun tamamı:

1
2
3
4
5
6
7
8
9
10
11
<?php
ini_set("soap.wsdl_cache_enabled", "0"); //WSDL cache devre dışı kalsın
require_once 'lib/nusoap.php';
require 'config.php';
$config = new Config();
$soapClient = new SoapClient($config->wwwroot . '/service.php?wsdl' , array(
    "trace"      => 1,
    "exceptions" => 0));

$name = $soapClient->getUserNameByEmail('test@mailinator.com');
echo $name;

http://localhost/nusoap/client.php kodunu çağırdığımızda “Georges Moustaki” yazısını görüyorsak işimiz bitmiş demektir.

Böylece NuSOAP kullanarak bir SOAP sunucusu ve bir de SOAP istemcisi yazmış olduk.
Kolay gelsin!

Paylaş:
  • Print
  • FriendFeed
  • Twitter
  • Digg
  • Sphinn
  • del.icio.us
  • Facebook
  • Mixx
  • Google Bookmarks
  • Blogplay
  • DZone
  • LinkedIn
  • MySpace
  • Ping.fm
  • Reddit
  • StumbleUpon
  • Technorati

Etiketler: , ,

VBulletin en çok beğenilen ve kullanılan forum yazılımlarından biri olma özelliğinin yanında, veri madencilerini de oldukça fazla uğraştıran bir forum olmasıyla da biliniyor.

Bu makalede LF’in cURL tabanlı NetClient kütüphanesini kullanarak VBulletin forumlarından veri çekmeyi inceleyeceğiz.

Not1: NetClient kütüphanesinin çalışabilmesi için sisteminizde php-curl eklentisinin kurulu olması gerekir. Çok kullanılan bir eklenti olduğundan genellikle kuruludur.

Not2: Siz kodun içinde görmeyecek olsanız da, cookie kullanacağımız için LF kurulum ana dizinine apache kullanıcısının yazma izni olmalı.

Örneğimizde VB Forumundan bir alt-forum sayfası içeriğini okumayı amaçlıyoruz. Akış diagramımız kabaca aşağıdaki gibi olacak:

  1. VB forumuna kullanıcı adı ve şifre ile giriş yap
  2. VB security token’ı al.
  3. VB sub-forum sayfasını çağır

Önce Controllerımızı oluşturalım, kullanacağımız private değişkenleri deklare edelim. Daha sonra constructor’ımızda NetClient kütüphanesini yükleyelim ve forum kullanıcı adı, şifre ve  sayfa içeriğini edinmek istediğimiz alt-forum ID’sini belirlediğimiz bir ayar metodu yazalım.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Vbulletin extends LogikitController
{
    private $_forum , $_username , $_password , $_loginUrl , $_loginPageContent , $_securityToken , $_subForumId;
   
    public function __construct()
    {
        parent::__construct();
       
        //NetClient kütüphanesini yükle
        $this->load->systemLibrary('NetClient');
       
        //ayarları yükle
        $this->_config();
       
        //VBulletin forumları için login sayfası
        $this->_loginPage = 'login.php?do=login';
    }
   
   
    private function _config()
    {
        $this->_forum = 'http://www.vbforumumuz.org/'; //hedef forum. sondaki bölü işaretini unutmayalım.
        $this->_username = 'kullaniciAdi'; //forum kullanıcı adı
        $this->_password = 'kullaniciSifresi'; // şifre
        $this->_subForumId = 22; //post listesini almak istediğimiz sub-forumun ID'si
    }
   
}

Yukarıdaki kodu application/controller altına Vbulletin.php adıyla kaydedelim.
Ardından, login işlemini gerçekleştirerek loginden sonra yönlendirileceğimiz sayfanın içeriğini alan private metodu yazalım:

1
2
3
4
5
    private function _doLogin()
    {
        $this->_loginPageContent = $this->NetClient->fetchUrl($this->_forum . $this->_loginUrl , "vb_login_username=" . $this->_username . "&vb_login_password=" . $this->_password . "&s=&do=login&vb_login_md5password=" . $passwordMD5 . "&vb_login_md5password_utf=" . $passwordMD5);
        sleep(1);
    }

Şimdi $this->_loginPageContent içinde login işleminden sonra yönlendirildiğimiz ana sayfanın HTML içeriği var.
Dikkatinizi çekmiştir; işlemden sonra bir saniye bekliyoruz. Bunu yapmadığımız zaman Vbulletin sorun çıkarabiliyor.
Artık Vbulletin forumunda yapacağımız tüm işlemler için bize gerekecek olan security token’ı, anasayfa içeriğindeki
var SECURITYTOKEN = “timestamp-md5token”;
biçiminde bulunan javascriptten çekebiliriz. Bunun için regEx kullanacağız. Yeri gelmişken belirtmekte fayda var: regEx bilmeden olmuyor. Ya iyice regEx öğrenin, ya da benim gibi sürekli cheatsheetlerden kopya çekin!

1
2
3
4
5
    private function _getSecurityToken($content)
    {
        preg_match('%(?:var\sSECURITYTOKEN\s\=\s\")(.*?)(\";)%', $content , $result , PREG_OFFSET_CAPTURE , 3);
        $this->_securityToken = $result[1][0];
    }

Böylece ana sayfa HTML kodundan
var SECURITYTOKEN = ”
ile başlayıp
“;
ile biten kısımda bulacağımız security token’ı da elde etmiş olduk.

Kullanacağımız son araç alt-forum sayfasının içeriğini alan metod olacak:

1
2
3
4
5
    private function _getSubForumContent($subForumId , $page = 1)
    {
        $subForumUrl = $this->_forum . "forumdisplay.php?f=$subForumId&order=desc&page=$page";
        return $this->NetClient->fetchUrl($subForumUrl , 'securitytoken=' . $this->_securityToken);
    }

Artık ana metodumuzu yazabilir ve kodumuzu hazır hale getirebiliriz:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    public function scrapeVBCategory()
    {

        //login işlemini yap ve login sayfası içeriğini $this->__loginPageContent stringine at
        $this->_doLogin();
       
        //security token'ı al
        $this->_getSecurityToken($this->_loginPageContent);
       
        //seçtiğimiz alt forumun birinci sayfasını yükle.
        $subForumPage = $this->_getSubForumContent($this->_subForumId);

        echo $subForumPage;

        //template ve view yüklemeden çık.
        exit;
    }

Gördüğünüz gibi oldukça derli-toplu ve gerçekten çok basit. Kodumuzun tamamlanmış hali:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
<?php

/**
 * Logikit::Framework
 *
 * Open source development framework for PHP 5
 *
 * @package     Logikit Framework
 * @author      Can Ince
 * @copyright           Copyright (c) 2010, Logikit / Can Ince.
 * @license     http://www.opensource.org/licenses/mit-license.php

 * @link        http://framework.logikit.net
 */


class Vbulletin extends LogikitController
{
    private $_forum , $_username , $_password , $_loginUrl , $_loginPageContent , $_securityToken , $_subForumId;
   
    public function __construct()
    {
        parent::__construct();
       
        //NetClient kütüphanesini yükle
        $this->load->systemLibrary('NetClient');
       
        //ayarları yükle
        $this->_config();
       
        //VBulletin forumları için login sayfası
        $this->_loginPage = 'login.php?do=login';
    }
   
   
    private function _config()
    {
        $this->_forum = 'http://www.vbforumumuz.org/'; //hedef forum. sondaki bölü işaretini unutmayalım.
        $this->_username = 'kullaniciAdi'; //forum kullanıcı adı
        $this->_password = 'kullaniciSifresi'; // şifre
        $this->_subForumId = 22; //post listesini almak istediğimiz sub-forumun ID'si
    }

    public function index()
    {
        //scrapeVBCategory metoduna yönlen.
        $this->scrapeVBCategory();
    }
   
    public function scrapeVBCategory()
    {

        //login işlemini yap ve login sayfası içeriğini $this->__loginPageContent stringine at
        $this->_doLogin();
       
        //security token'ı al
        $this->_getSecurityToken($this->_loginPageContent);
       
        //seçtiğimiz alt forumun birinci sayfasını yükle.
        $subForumPage = $this->_getSubForumContent($this->_subForumId);

        echo $subForumPage;

        //template ve view yüklemeden çık.
        exit;
    }
   
    private function _doLogin()
    {
        //ilk parametre URL, ikinci parametre POST verisi
        $this->_loginPageContent = $this->NetClient->fetchUrl($this->_forum . $this->_loginUrl , "vb_login_username=" . $this->_username . "&vb_login_password=" . $this->_password . "&s=&do=login&vb_login_md5password=" . $passwordMD5 . "&vb_login_md5password_utf=" . $passwordMD5);
        sleep(1);
    }
   
    private function _getSecurityToken($content)
    {
        preg_match('%(?:var\sSECURITYTOKEN\s\=\s\")(.*?)(\";)%', $content , $result , PREG_OFFSET_CAPTURE , 3);
        $this->_securityToken = $result[1][0];
    }
   
    private function _getSubForumContent($subForumId , $page = 1)
    {
        $subForumUrl = $this->_forum . "forumdisplay.php?f=$subForumId&order=desc&page=$page";
        return $this->NetClient->fetchUrl($subForumUrl , 'securitytoken=' . $this->_securityToken);
    }
   
}

// END Vbulletin class

/* End of file Vbulletin.php
   Location: ./system/application/controller/Vbulletin.php */

Kodun tamamlanmış hali yorumlar ve boş satırlar hariç 40-45 satır civarında.
NetClient kütüphanesi, bizim için gerekli cURL ayarlarını yaptı. HTTP yönlendirmelerini takip etti, cookie oluşturdu ve bize yalnız “git şu adrese, şunları post et ve gelen veriyi al” demek kaldı.

Veri madencisi arkadaşların işine yaraması dileğiyle.

Paylaş:
  • Print
  • FriendFeed
  • Twitter
  • Digg
  • Sphinn
  • del.icio.us
  • Facebook
  • Mixx
  • Google Bookmarks
  • Blogplay
  • DZone
  • LinkedIn
  • MySpace
  • Ping.fm
  • Reddit
  • StumbleUpon
  • Technorati

Etiketler: , , , , ,

Mongo DB, yeni yeni serpilen fakat şimdiden bir çok kişinin gözdesi haline gelmeyi başarmış bir veritabanı. PHP için de eklentisi mevcut: http://www.php.net/manual/en/intro.mongo.php

Buradan AMD 64 bit için derlediğim Mongo DB .deb paketini edinebilirsiniz.

Paylaş:
  • Print
  • FriendFeed
  • Twitter
  • Digg
  • Sphinn
  • del.icio.us
  • Facebook
  • Mixx
  • Google Bookmarks
  • Blogplay
  • DZone
  • LinkedIn
  • MySpace
  • Ping.fm
  • Reddit
  • StumbleUpon
  • Technorati

Etiketler: , , ,

Logikit::Framework yazılarımı LF blogunda yayınlıyorum ancak Türkçe belgelendirmeye de gereksinim duyuluyor. Bu nedenle LF’le ilgili Türkçe makaleleri bu blogda yayınlıyorum.

Bu makalede “Selectbox bileşen seçeneklerini veritabanından gelen veriyle doldurmak” üstüne çalışacağız.
LF ve MVC örüntüsü üstüne deneyimli kişiler için “Özet” bölümü yeterli olmakla birlikte, “yeni ısınan” arkadaşlar için ayrıntılı açıklamaları da bulacaksınız.

Özet:
Aşağıdaki metodu Controller’ımıza ekliyoruz. Çağırılan model, form ve view ile kullanılan veritabanı tablo yapısını “Ayrıntılar” kısmında bulabilirsiniz.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public function mySelectBoxTest()
{
//formu ilklendir
$this-&gt;load-&gt;form('MySelectBoxTestForm');

//modeli ilklendir
$this-&gt;load-&gt;model('TestModel');

//modelden kategorileri id =&gt; name yapısında bir array olarak al
$categoryNames = $this-&gt;UploaderModel-&gt;getCategories();

//formda category ismiyle tanımlanmış selectbox'ın içini veriyle doldur
foreach($categoryNames as $key =&gt; $val)
{
$optArray = array($key =&gt; $val);
$this-&gt;CategorySelectForm-&gt;addOption('category' , $optArray);
}

//id'si 1 olan kategoriyi seçili getir
$this-&gt;CategorySelectForm-&gt;setSelected('category' , '1');

//formu oluştur
$viewData['formItems'] = $this-&gt;MySelectBoxTestForm-&gt;renderAll();

// form verisini view'a geç ve view'u çağır
$viewData = array();
$this-&gt;load-&gt;view('categoryselect' , $viewData);
}

Ayrıntılar:

MySQL kullandığımızı varsayarak veritabanı yapımız:

1
2
3
4
CREATE TABLE `categories` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`name` VARCHAR( 100 ) NOT NULL
) ENGINE = MYISAM ;

Controller’ımızın adı “Test” olsun:

1
2
3
4
5
6
7
8
class Test extends LogikitController
{
public function __construct()
{
parent::__construct();
}

}

Yukarıdaki kodu application/controller altına “Test.php” adıyla kaydedelim.

Sırasıyla modelimizi ve formumuzu oluşturalım.

1
2
3
4
5
6
7
class TestModel extends LogikitModel
{
public function __contruct()
{
parent::__contruct();
}
}

Bu modelimiz. Yukarıdaki kodu /application/model altına “TestModel.php” adıyla kaydedelim ve formumuza geçelim:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class MySelectBoxTestForm extends LogikitForm
{
public function __construct()
{

$this-&gt;addItem(array(
'name' =&gt; 'category' ,
'type' =&gt; 'selectBox' ,
'value' =&gt; NULL ,
'validation' =&gt; array('notNull') ,
'properties' =&gt; ''));

$this-&gt;addItem(array(
'name' =&gt; 'Go' ,
'type' =&gt; 'submit' ,
'value' =&gt; 'Gönder' ,
'validation' =&gt; NULL ,
'properties' =&gt; 'class="submit1"'));
}
}

Böylece bir selectbox ve bir submit butonundan oluşan formumuzu tanımlamış olduk. Yukarıdaki kodu da application/form/Test altında “MySelectBoxTestForm.php” adıyla kaydedelim.
Böylece view hariç, gerekli dosya ve nesnelerin tamamını hazırlamış olduk.

Bir sonraki adımda, controller’ımıza dönelim (application/controllers/Test.php) ve kullanacağımız metodu yazalım:

1
2
3
4
public function mySelectBoxTest()
{

}

Şimdi de yukarıda oluşturduğumuz form ve modelimizi yükleyelim:

1
2
3
4
5
6
public function mySelectBoxTest()
{
$this-&gt;load-&gt;form('MySelectBoxTestForm');

$this-&gt;load-&gt;model('TestModel');
}

Bu andan itibaren form ve model nesneleri ile bu nesnelere bağlı metodlar, controller’ımız içinden $this->TestModel->method() ve $this->MySelectBoxTestForm->method() şeklinde çağırılabilir durumda.

Modelimize (application/model/TestModel.php) geri dönelim ve selectbox’ımızı doldurmak için gerekli veriyi array olarak veritabanından çeken metodu yazalım:

1
2
3
4
public function getCategories()
{
return $this-&gt;result("SELECT id , name FROM categories");
}

Controller’ımızdaki mySelectBoxTest() metoduna dönelim ve selectbox’ı veriyle dolduran kodu ekleyelim:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public function mySelectBoxTest()
{
$this-&gt;load-&gt;form('MySelectBoxTestForm');

$this-&gt;load-&gt;model('TestModel');

$categoryNames = $this-&gt;UploaderModel-&gt;getCategories();
foreach($categoryNames as $key =&gt; $val)
{
$optArray = array($key =&gt; $val);
$this-&gt;CategorySelectForm-&gt;addOption('category' , $optArray);
}

//id'si 1 olan kategoriyi seçili getir
$this-&gt;CategorySelectForm-&gt;setSelected('category' , '1');
}

Contoller’daki son işimiz, form verisini view’a aktarmak olacak ve bu işlemden sonra controller’ın son hali aşağıdaki gibi olmalı:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public function mySelectBoxTest()
{
$this-&gt;load-&gt;form('MySelectBoxTestForm');

$this-&gt;load-&gt;model('TestModel');

$categoryNames = $this-&gt;UploaderModel-&gt;getCategories();
foreach($categoryNames as $key =&gt; $val)
{
$optArray = array($key =&gt; $val);
$this-&gt;CategorySelectForm-&gt;addOption('category' , $optArray);
}

//id'si 1 olan kategoriyi seçili getir
$this-&gt;CategorySelectForm-&gt;setSelected('category' , '1');

//formu oluştur
$viewData['formItems'] = $this-&gt;MySelectBoxTestForm-&gt;renderAll();

// form verisini view'a geç ve view'u çağır
$viewData = array();
$this-&gt;load-&gt;view('categories' , $viewData);
}

ve son olarak view dosyasını oluşturarak işimizi bitirelim:

1
2
3
4
5
6
<div id="formDiv" style="display: none;">echo formOpenMultipart('form1' , siteUri(getController() . '/formIslemeMetodu') , 'POST', 'onsubmit="return false;"');
echo $secondFormItems['category'] . "
"
;
echo $secondFormItems['Go'] . "
"
;
formClose();</div>

Bu dosyayı da application/view/categories.php adıyla kaydedince işimiz bitmiş oluyor.

Artık kodu çalıştırabiliriz:

http://www.testsunucunuz.com/LFDizini/Test/mySelectBoxTest

Paylaş:
  • Print
  • FriendFeed
  • Twitter
  • Digg
  • Sphinn
  • del.icio.us
  • Facebook
  • Mixx
  • Google Bookmarks
  • Blogplay
  • DZone
  • LinkedIn
  • MySpace
  • Ping.fm
  • Reddit
  • StumbleUpon
  • Technorati

Etiketler: , , ,

PostgreSQL ile bir gece

Bir kaç ay önce, ailedeki yazılımcı sayısının artmasıyla “Artık şirketi kuralım!” dedik ve Logikit‘i kurduk. Kendimizi tanıtmakla geçen, programcı olarak bana boş vakit tanıyan sürede de Logikit::Framework‘ü (LF) kodladım ve bir kaç ay boyunca LF üstünde küçük ve orta ölçekli bir kaç proje geliştirerek frameworkü geliştirme ve olgunlaştırma olanağı da buldum. Buraya kadar iyi…

Bu ay (Mart 2010) başlarında oldukça büyük ölçekli bir projeye başladım. Usecase’ler yazılıp çizilip tartışıldı, entity relationship diagramlar yapıldı, veritabanı mimarisi oturdu. Projede veritabanı performansı çok önemli ve PostgreSQL kullanıyoruz.

LF’i kodlarken “database abstraction’ı da aradan çıkarıverelim!” diye uyanıklık edip PDO kullandım. Bir sürü RDMBS için sürücüsü var ya, rahatım!

LF’te model için yazdığım abstract class PDO’dan türüyor ve kullanıcı modelleri de bu abstract class’tan. Basit bir active record uygulaması. Kullanıcı (LF kullanan programcı) controller’ında record set için bir array oluşturuyor, bu array’i bizim modele veriyor ve model de duruma göre (“id” alanı varsa) INSERT ya da (“id” alanı yoksa) UPDATE sorgusu oluşturuyor. Tablo adını biliyoruz ama tablo alan adlarını bilmiyoruz.

Daha önce LF üstünde geliştirdiğim projelerin hepsi MySQL kullanıyordu ve benim aklıma da “yazdığım kod başka RDMBS üstünde çalışır mı, çalışmaz mı, çatlar-patlar mı?” soruları gelmedi. “En fazla bir-iki yeri düzeltmek gerekir.” dedim ve geçip gittim.

Projenin planlama aşaması bitip, PostgreSQL tablolar oluşturulup ilk controllerı yazmaya sıra geldiğinde gidip ayarlarımdan sürücüyü (büyük bir güvenle!) “mysql”den “pgsql”e değiştirdim ve safça çalışmasını umuyorum! Çalışmadı tabii…

Bir-iki yeri basitçe fiksledim; her şey normal görünüyor. Array doğru geliyor, tablo adı doğru, velhasıl her şey doğru.
Debug sürecinde daralta daralta çalışmayan yeri buldum: Tablodaki alan adları düzgün gelmiyordu!

MySQL ile kullandığım model asbtract’ında ilgili metod şöyleydi:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* Return list of columns
*
* @access   public
* @param        string  tableName
* @return   array
*/


public function columns($tableName)
{
$tableName = strtolower($tableName);

$arrFields = array();
$colQuery = $this-&gt;query("SHOW COLUMNS FROM $tableName");
while($col = $colQuery-&gt;fetch_array())
{
$arrFields[$col['Field']] = $col;
}

return $arrFields;
}

Şöyle bir durup kafamı kaşıdım: On yıldır yaşamımı yazılımcı olarak kazandığımı anımsadım.
Buna rağmen en son iki yıl kadar önce PostgreSQL ile bir proje geliştirmiştim ve hiç bir durumda tablo alan adlarını öğrenmeye ihtiyaç duymamıştım; zaten o adları hep ben vermiştim!
Doğal olarak LIMIT 1 ile bir sorgu yapıp bu adları öğrenebilirdim ancak hem performanstan yemek gibi bir lüksüm yoktu, hem de bu tür işi kendime yakıştıramadım. Tamam, PostgreSQL uzmanı olmadığımı biliyordum ancak PostgreSQL kullanarak en az 10-12 proje geliştirmiş bir kişi evladı olarak büsbütün cahil olduğumu da ummuyordum.
Biraz deştikten sonra, hayret verici şekilde, aşağıdaki kodun LIMIT 1 sorgusuna göre daha verimli olduğunu gördüm:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
* Return list of columns
*
* @access   public
* @param        string  tableName
* @return   array
*/


public function columns($tableName)
{
$tableName = strtolower($tableName);

$arrFields = array();

foreach($this-&gt;query("SELECT
a.attname as \"Column\",
pg_catalog.format_type(a.atttypid, a.atttypmod) as \"Datatype\"
FROM
pg_catalog.pg_attribute a
WHERE
a.attnum &gt; 0
AND NOT a.attisdropped
AND a.attrelid = (
SELECT c.oid
FROM pg_catalog.pg_class c
LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
WHERE c.relname ~ '^($tableName)$'
AND pg_catalog.pg_table_is_visible(c.oid)
)
;"
) as $col)

{
$arrFields[] = $col[0];
}

return $arrFields;
}

Bu durumda görünen o ki, LF’ün yeni sürümünde en az iki ayrı abstract model olacak.
Başıma yine gelirse anımsamak için bir not…

Paylaş:
  • Print
  • FriendFeed
  • Twitter
  • Digg
  • Sphinn
  • del.icio.us
  • Facebook
  • Mixx
  • Google Bookmarks
  • Blogplay
  • DZone
  • LinkedIn
  • MySpace
  • Ping.fm
  • Reddit
  • StumbleUpon
  • Technorati

Etiketler: , , , ,

Newer entries »