PDO yani PHP Data Object

Standard

PDO, PHP 5.1’den itibaren hayatımıza girmiş olan ve veritabanı işlemlerimizi çok daha güvenli, sağlıklı hale getirecek olan yakın dostumuzdur.. PHP 5.5 sürümünde mysql_* fonksiyonlarını kısmen kaldıracağını duyurdu ve otomatik olarak bizleri PDO veya MySqli kullanmaya yönlendirdi diyebiliriz. Esnektir ve birçok veritabanı ile bağlantıyı kolaylıkla sağlayabilirsiniz.

  • DBLIB: FreeTDS / Microsoft SQL Server / Sybase
  • Firebird (http://firebird.sourceforge.net/): Firebird/Interbase 6
  • IBM (IBM DB2)
  • INFORMIX – IBM Informix Dynamic Server
  • MYSQL (http://www.mysql.com/): MySQL 3.x/4.0
  • OCI (http://www.oracle.com): Oracle Call Interface
  • ODBC: ODBC v3 (IBM DB2 and unixODBC)
  • PGSQL (http://www.postgresql.org/): PostgreSQL
  • SQLITE (http://sqlite.org/): SQLite 3.x

PDO’nun en güzel yanlarından birisi de güvenlik. Maalesef insanların dikkatinden kaçan en önemli unsurlardan birisi SQL Injection. PDO yapısının bindings özelliği ile bu açığa sebebiyet veren değişkenler harici olarak dahil edilip bu açıktan yüksek seviye de kurtulmuş oluyorsunuz.. İsterseniz yavaş yavaş pdo nun faydalarına değinelim, kısa kısa örneklerle yol alalım.. Önce Bir Bağlanalım isterseniz temel olarak olmazsa olmaz ve hata ayıklayalım:

try {
    $conn = new PDO('mysql:host=localhost;dbname=Onur', $username, $password);
    $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch(PDOException $e) {
    echo 'Hata: ' . $e->getMessage();
}

Try / Catch bloğu arasında yazdık ama siz kendiniz manuel olarak kontrol etmek isterseniz $conn->errorCode(); ve $conn->errorInfo(); ile hata yakalayabilirsiniz.. Hata yakalamak için ben ERRMODE_EXCEPTION yaptım gördüğünüz gibi çünkü varsayılan olarak PDO::ERRMODE_SILENT gelecektir. Bu arada ERRMODE olarak elimizde neler var bakalım kısaca:

  • PDO::ERRMODE_SILENT
  • PDO::ERRMODE_WARNING
  • PDO::ERRMODE_EXCEPTION

Bağlantı işlemini yaptığımıza göre isterseniz pdo nun bize sunduğu güzelliklere, quote ve prepare işlemlerine bir göz gezdirelim..

try {
   $db = new PDO('mysql:host=localhost;dbname=test', 'root', '');
} catch (PDOException $e) {
    return 'DB Error: '. $e->getMessage();
}

$string = 'Merhaba \' dünya!';

echo "Escape edilmemiş: $string\n";
echo "Escape edilmiş:" . $db->quote($string) . "\n";

Yukarıda PDO ile bağlanıp basit bir Quote ile temizlik yapalım dedik. Ancak php.net üzerinde quote ile ilgili detaylara baktığımız zaman şu uyarı dikkatimizi çekecektir;

Bu işlevi SQL deyimlerini oluştururken kullanıyorsanız, kullanıcı girdisini bir SQL deyimi haline getirmek için PDO::quote() yerine PDO::prepare() ile değiştirgeleri ilişkilendirilmiş SQL deyimleri hazırlamanızı hararetle öneririz. Değiştirgelerle ilişkilendirimiş hazır deyimler taşınabilir olmaktan başka daha kullanışlı ve SQL zerkine bağışık olmanın yanında sorgunun derlenmiş hali hem sunucuda hem de istemcide bulunduğundan yorumlanan sorgulardan çok daha hızlıdır. PDO sürücülerinin hepsi bu yöntemi gerçeklemez. (özellikle PDO_ODBC) Bu bakımdan hazır deyimleri kullanmaya hazır olmalısınız.

Ben sizin için bold yaptım dikkatinizi belki çekmez diye ancak hazır deyimler kısmı dikkatinizi çekmiştir umarım.. Hazır değimlere birazdan geleceğim ama öncelikle data okuma için kullanabileceğiniz iki farklı  yönteme değinmek istiyorum.. Bunlar QUERY ve EXECUTE yöntemleri.. Örnekler ile ele alalım daha açık ve net olacak..

/*
 * Query Method
 * Anti-Pattern
 */

$name = 'Onur'; //Kullanıcı tarafından girilmiş olan veri

try {
    $conn = new PDO('mysql:host=localhost;dbname=onur', $username, $password);
    $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    $data = $conn->query('SELECT * FROM users WHERE name = ' . $conn->quote($name));

    foreach($data as $row) {
        print_r($row); 
    }
} catch(PDOException $e) {
    echo 'ERROR: ' . $e->getMessage();
}

Ancak Bu yöntem Form dan yani kullanıcı tarafından bi şekilde girilmiş datalarda önerilmez! Yazımı daha hızlı ve kolaydır ve dönen dataları foreach ile rahat şekilde okuyabilirsiniz. Gelelim Execute işlemine..

/*
 * Prepared Statements Method
 * Doğru olan yöntem..
 */

$id = 5;
try {
    $conn = new PDO('mysql:host=localhost;dbname=onur', $username, $password);
    $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);    

    $oc = $conn->prepare('SELECT * FROM users WHERE id = :id');
    $oc->execute(array('id' => $id));

    while($row = $oc->fetch()) {
        print_r($row);
    }
} catch(PDOException $e) {
    echo 'ERROR: ' . $e->getMessage();
}

Yukarıda sorgu içerisinde id değişkenini gördüğünüz gibi :id şeklinde aldık.. Ne oldu ne bitti orada anlamanız için makalenin başında bahsetmiş olduğum hazır değimler kısmına geçelim isterseniz.. Peki nedir bu hazır deyimler? Hazır deyimler, PDO’nun bize sağladığı en büyük avantajlardan birisidir. Bu avantajların başında PDO::quote’ye göre daha hızlı olan PDO::prepare vardır. SQL sorgusu placeholder yani Türkçesi kulağa garip gelsede yer tutucularla yazılır ve prepare ile hazırlanır. Bu yer tutucular isimlendirilmiş ve isimlendirilmemiş şekilde 2 ye ayrılırlar ve değerlerini diziler ile ayarlarız. Şimdi isimsiz yer tutucuya bir örnek verelim;

$sorgu = "INSERT INTO users (username, password, email) values (?,?,?)";
$datas = array(
'Onur Canalp',
'HASH32131234498334938',
'onur@onurcanalp.com');  
$kayit_sorgusu = $db->prepare($sorgu);
// Sorguyu çalıştır.
$kayit_sorgusu->execute($datas);

Değişkenlerimizin geleceği yerleri ? işareti ile tanımladık gördüğünüz gibi.. Şimdi bir de isimli yer tutucusuna örnek verelim;

$sorgu = "INSERT INTO users (username, password, email) values (:user,:pass,:email)";
$datas = array(
'user' => 'Onur Canalp',
'pass' => 'HASH32131234498334938',
'email' =>'onur@onurcanalp.com');  
$kayit_sorgusu = $db->prepare($sorgu);
// Sorguyu çalıştır.
$kayit_sorgusu->execute($datas);

Ben size isimli yer tutucu kullanmanızı önereceğim çünkü okunması siz ve diğer programcılar açısından çok daha kolay olacaktır..

Execute işleminde parametreleri direk dizi olarak gönderdik, peki ya bu değerlerde integer gibi tanımlama yapmak istersek nasıl yapacağız?

$oc->execute(array('id' => $id));

Ve şimdi integer olarak alalım:

$oc->bindParam(':id', $id, PDO::PARAM_INT);
$oc->execute();

Görüldüğü gibi gayet basit… Çıktı tiplerini değiştirmek için ne yapmamız lazım? PDO varsayılan olarak PDO::FETCH_ASSOC yapısı ile okur datalarımızı. Aşağıdaki şekilde isteğe göre düzenleyebilirsiniz

while($row = $oc->fetch(PDO::FETCH_OBJ)) {
    print_r($row);
}
  • PDO::FETCH_ASSOC: Sütun isimlerine göre indisli bir dizi döner.
  • PDO::FETCH_BOTH (öntanımlı): Hem sütun isimlerine hem de sütun numaralarına göre indislenmiş bir dizi döner. İlk sütunun indisi 0’dır.
  • PDO::FETCH_BOUND: Sütun değerlerini PDOStatement::bindColumn() ile ilişkilendirilmiş PHP değişkenlerine atar ve TRUE döndürür.
  • PDO::FETCH_CLASS: İstenen sınıfın özelliklerini sütun isimlerine eşleyerek sınıfın bir örneğini döndürür. Eğer alım_tarzı değiştirgesi PDO::FETCH_CLASSTYPE sabitini içeriyorsa (örn, PDO::FETCH_CLASS | PDO::FETCH_CLASSTYPE) sınıf ismi ilk sütunun değerine göre belirlenir.
  • PDO::FETCH_INTO: İstenen sınıfın mevcut örneğini sütun isimlerini sınıf özelliklerine eşleyerek günceller.
  • PDO::FETCH_LAZY: PDO::FETCH_BOTH ve PDO::FETCH_OBJ sabitlerinin birleşimidir.
  • PDO::FETCH_NUM: Sütun numaralarına göre indislenmiş bir dizi döner. İlk sütunun indisi 0’dır.
  • PDO::FETCH_OBJ: Özellik isimlerinin sınıf isimlerine denk düştüğü bir anonim nesne örneği döndürür.

Çok konuştuk çok söyledik, şimdi temel yapabileceğiniz CRUD işlemleri ile makalemizin son virajını dönelim 🙂

CRUD

Create (Insert)

try {
  $pdo = new PDO('mysql:host=localhost;dbname=onur', $username, $password);
  $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

  $oc = $pdo->prepare('INSERT INTO users VALUES(:name)');
  $oc->execute(array(
    ':name' => 'Onur Canalp'
  ));

  # Affected Rows
  echo $oc->rowCount(); // 1
} catch(PDOException $e) {
  echo 'Hata: ' . $e->getMessage();

 Update

$id = 5;
$name = "Abuzer Kadayıf";

try {
  $pdo = new PDO('mysql:host=localhost;dbname=onur', $username, $password);
  $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

  $oc = $pdo->prepare('UPDATE users SET name = :name WHERE id = :id');
  $oc->execute(array(
    ':id'   => $id,
    ':name' => $name
  ));

  echo $oc->rowCount(); // 1
} catch(PDOException $e) {
  echo 'Hata: ' . $e->getMessage(); }

Delete

$id = 5; // formdan gelen değer

try {
  $pdo = new PDO('mysql:host=localhost;dbname=onur', $username, $password);
  $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

  $oc = $pdo->prepare('DELETE FROM users WHERE id = :id');
  $oc->bindParam(':id', $id); // bu sefer bindParam metodu ile yaptık
  $oc->execute();

  echo $oc->rowCount(); // 1
} catch(PDOException $e) {
  echo 'Hata: ' . $e->getMessage();
}

CRUD işlemlerini anlattıktan sonra artık son düzlükte Object Mapping işlemine de değinelim..

class User {
  public $first_name;
  public $last_name;

  public function full_name()
  {
    return $this->first_name . ' ' . $this->last_name;
  }
}

try {
  $pdo = new PDO('mysql:host=localhost;dbname=onur', $username, $password);
  $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

  $result = $pdo->query('SELECT * FROM users');

  // Sonucu objeye yönlendirelim
  $result->setFetchMode(PDO::FETCH_CLASS, 'User');

  while($user = $result->fetch()) {
    // Kendi custom full_name method umuzu çağıralım
    echo $user->full_name();
  }
} catch(PDOException $e) {
  echo 'Hata: ' . $e->getMessage();
}

Öldürücü son vuruşu ise Callback özelliği ile yapalım.. Yani özetle callback ile yaptığınız işlemleri geri alabiliyorsunuz.. Örnek ile pekiştirmek gerekirse;

$pdo_conn=new PDO("mysql:host=localhost;dbname=onur",'user','pass');
$pdo_conn->exec("SET NAMES latin5");

// işlem başlangıcı
$pdo_conn->beginTransaction();
$sonuc1=$pdo_conn->exec("DELETE FROM users WHERE id ='9998'");
//9999 diye bi veri yok 
$sonuc=$pdo_conn->exec("DELETE FROM users WHERE id='9999'");

if(($sonuc==FALSE) or ($sonuc1==FALSE)) {
   $pdo_conn->rollBack(); // işlemi geri al
   echo "<h3>Hata oldu işlem geri alındı</h3>";
}else {
   $pdo_conn->commit(); // işlemi tamamla
}

   foreach($pdo_conn->query("SELECT * FROM users") as $row) {
     echo $row['adsoyad'].'-'.$row['tckimlik'].'<br>';
   }
$pdo_conn =null;

beginTransaction() özelliği Mysql InnoDB vb gibi depolama motorlarında çalışır.
MyIsam gibi motorlarda çalışmaz..

Uzun lafın kısası PDO iyidir , candır. prepare ve execute ile hayatınızı kolaylaştırır.. Callback ise yemekten sonra gelen çay gibi vazgeçilmezdir daha ne diyim 🙂 Daha detaylı bilgiye sahip olmak isterseniz buraya tıklayarak manuel de yazan dökümanlara göz gezdirebilirsiniz. Herkese iyi çalışmalar dilerim..