avatar

Sık Yapılan PHP Hataları
PHP ile kodlarken sıkça yapılan bazı hatalar...

10/08/2014
Hakan Özakar

PHP web tabanlı yazılım geliştirmeyi oldukça kolaylaştıran, açık kaynağa çok yatkın, öğrenmesi ve uygulaması kolay ve bu nedenlerden dolayı çok popüler bir programlama dili. Ancak bu sizi aldatmasın, programlama yapmak, kullanılan dilden bağımsız olarak dikkat, özen ve çalışma isteyen bir iş. Bunun doğal sonucu olarak hata yapmak kaçınılmaz oluyor.

 

PHP ile kodlarken dikkat edilmesi gereken bazı noktalar

 

isset() işlevini kullanırken dikkatli olalım:

Adı üstünde, isset() fonksiyonu bir değişkenin var olup olmadığını anlamak için kullanılır. Yalnız, bir değişken var olmasına rağmen değeri NULL ise isset() fonksiyonu yine de false döndürecektir. Bu sebeple dikkatle kullanılmadığında isset() problem çıkartabilir.

$degerler = array(
  "null" => NULL,
  "false" => FALSE,
  "true" => TRUE,
  "sifir" => 0,
  "sayi" => 1
);

// isset($degerler["null"]) = FALSE
// isset($degerler["false"]) = TRUE
// isset($degerler["true"]) = TRUE
// isset($degerler["sifir"]) = TRUE
// isset($degerler["sayi"]) = TRUE
// isset($degerler["baska_bir_sey"]) = FALSE

 

"isset.php"  isminde bir php dosyası yaratalım:

<body>
  <?=isset($_GET['id']) ? 'var' : 'yok'?>
</body>

URL satırına "localhost/isset.php" yazarsak ekranda göreceğimiz sonuç "yok" olacaktır ancak "localhost/isset.php?id" yazarsak sonuç "var" olacaktır çünkü her ne kadar bir değer atamadıysak da $_GET içinde bir id değişkeni yarattık. Yani değeri NULL değil.

Uzun lafın kısası, isset() bazen sağduyunuza aykırı sonuç döndürebilir.

Önemli: Bir değişken var ve değeri NULL'den farklı ise isset() TRUE döndürür. Bir değişken yok ya da değeri NULL ise isset() FALSE döndürür. Eğer bir değişken var ancak herhangi bir değer atanmamış ise isset() TRUE döndürür.

 

Return by reference - return by value karmaşası

Öncelikle return by reference ve return by value nedir ona bakalım. Bir fonksiyondan aldığımız dönüş eğer asli değişkenin kendisi ise buna return by reference (referans ile dönüş), eğer asli değişkenin bir kopyası ise return by value (değer ile dönüş) diyoruz.

Hadi bir class oluşturalım ve bu classdaki değerler ile biraz oynayalım:

  class isim {
    private $deger = array(
      "isim" => "Mehmet",
      "soyad" => "Yılmaz"
    );

    public function isimAl() {
      return $this->deger;
    }
  }

  $isim = new isim();

  $isim->isimAl()["isim"] = "Hakan";
  $isim->isimAl()["soyad"] = "Özakar";
  $isim->isimAl()["site"] = "beltslib.net";

  print_r($isim->isimAl());

Bu işlemin çıktısı şöyle olur:

Array
(
  [isim] => Mehmet
  [soyad] => Yılmaz
)

Neden? İsim ve soyad değerlerini değiştirdik ve "site" adında yeni bir değer ekledik. Neden ilk halini görüyoruz?

Çünkü bu, değer döndüren (by value) bir fonksiyon. Başka bir deyişle fonksiyonun bize gönderdiği veri, asli değişkenin kendisi değil bir kopyası. Biz değişiklikleri bir kopyanın üzerinde yaptık, asıl değişken gıdıklanmadı bile...

Bu durumu nasıl düzeltiriz?

  $isim = new isim();

  $kukla = $isim->isimAl();

  $kukla["isim"] = "Hakan";
  $kukla["soyad"] = "Özakar";
  $kukla["site"] = "beltslib.net";

  print_r($kukla);

Bu işlemin çıktısı:

Array
(
  [isim] => Hakan
  [soyad] => Özakar
  [site] => beltslib.net
)

İstediğimiz çıktıyı aldık. Ancak fonksiyonumuz hala by value çalışıyor yani değişiklikleri yine bir kopyada yaptık, asli değişkeni yine etkilemedik. Peki yaptığımız değişiklik asli değişkene de yansısın istersek ne yapmalıyız?

  class isim {
    private $deger = array(
      "isim" => "Mehmet",
      "soyad" => "Yılmaz"
    );

    public function &isimAl() { // Buradaki "&"
                                // işaretine dikkat...
      return $this->deger;
    }
  }

  $isim = new isim();

  $isim->isimAl()["isim"] = "Hakan";
  $isim->isimAl()["soyad"] = "Özakar";
  $isim->isimAl()["site"] = "beltslib.net";

  print_r($isim->isimAl());

Bu işlemin çıktısı:

Array
(
  [isim] => Hakan
  [soyad] => Özakar
  [site] => beltslib.net
)

Bu defa hem istediğimiz çıktıyı aldık hem de asli değişkeni değiştirdik çünkü artık fonksiyonumuz by reference çalışıyor. Peki bunu nasıl yaptık? Fonksiyon isminin başına koyduğumuz "&" işareti sayesinde. Bu işaret sayesinde fonksiyonun by reference çalışmasını istediğimizi belirttik.

 

Bir örnek daha inceleyelim:

  class isim {
    private $deger = arrayObject();

    public function __construct() {
      $this->deger = new ArrayObject();
      $this->deger["isim"] = "Mehmet";
      $this->deger["soyad"] = "Yılmaz";
    }

    public function isimAl() {
      return $this->deger;
    }
  }

  $isim = new isim();

  $isim->isimAl()["isim"] = "Hakan";
  $isim->isimAl()["soyad"] = "Özakar";
  $isim->isimAl()["site"] = "beltslib.net";

  print_r($isim->isimAl());

Peki bu işlemin çıktısı nasıl olacak? İlk örnekteki gibi olacağını tahmin ettiyseniz yanıldınız. Burada fonksiyonumuz by reference çalıştı... Neden mi? Çünkü $deger degiskenini ArrayObject() olarak tanımladık. ArrayObject() aynen bir dizi (array) gibi çalışır ama aslında bir nesnedir (object) ve PHP, nesneleri her zaman by reference olarak gönderir.

Kısacası, fonksiyondan aldığımız bir veri ile çalışacaksak öncelikle bu verinin bize by value mu yoksa by reference mi ulaştığından emin olmalıyız. PHP değişkenleri ve dizi-değişkenleri by value, nesneleri by reference gönderir.

 

Foreach ile çalışırken değişkenlerimizi by reference kullanmak

Bazen bir dizinin tüm elemanları üzerinde değişiklik yapmak isteriz ancak foreach döngüsünü alıştığımız gibi kullanarak bunu başaramayız:

$dizi = array(1, 2, 3);
foreach($dizi as $deger) {
  $deger++; 
}
print_r($dizi);

Sonuç: 

Array
(
  [0] => 1
  [1] => 2
  [2] => 3
)

Neden? Bir önceki örnekte incelemiştik, PHP dizileri by value olarak gönderiyor. Bu nedenle yaptığımız değişiklikleri aslında dizimizde değil, onun bir kopyası üzerinde yaptık. Asıl dizi bu işten etkilenmedi. Bu nedenle bu işlemin bir anlam ifade edebilmesi için dizimizi by reference olarak göndermeliyiz:

$dizi = array(1, 2, 3);
foreach($dizi as &$deger) { // "&" işaretine dikkat!
  $deger++; 
}
print_r($dizi);

Sonuç: 

Array
(
  [0] => 2
  [1] => 3
  [2] => 4
)

Buraya kadar güzel. Sonuç tam da beklediğimiz gibi. Ama asıl dikkat etmemiz gereken işler bundan sonra gerçekleşmeye başlıyor. Şimdi de aynı dizi üzerinde by value bir foreach çalıştırarak değerleri yazdıralım:

foreach($dizi as $deger) {
  echo $deger."<br>";
} 

Sonuç:

2
3
3

Garip işler oluyor. Değerler 2, 3, 4 değil miydi? Sondaki "3" nereden çıktı?

İlk foreach döngüsünü by reference çalıştırdığımız için böyle oldu. İlk döngüde $deger değişkenine dizimizi by reference olarak çağırmıştık. İşlem tamamlandığında $deger değişkeni dizimizin son elemanına işaret eder şekilde kaldı, yani $deger = $dizi[2]

Yeni foreach döngümüzü by value olarak çalıştıralım ve adım adım inceleyelim:

  • İlk dönüşte $deger'e $dizi[0]'ın değerini atadık. $deger aynı zamanda $dizi[2] olduğundan $dizi[2] = 2,
  • İkinci dönüşte $deger'e $dizi[1]'in değerini atadık. $deger aynı zamanda $dizi[2] olduğundan $dizi[2] = 3,
  • Üçüncü dönüşte $deger'e = $dizi[2]'nin değerini atadık. $deger aynı zamanda $dizi[2] olduğundan $dizi[2] = 3 

 

Sonuç: Foreach döngüsünde dizimizi by reference olarak kullanacaksak işlem tamamlandıktan sonra kullandığımız değeri sıfırlamayı "unset($deger);" unutmayalım. Tabii kodumuzun devamında saç baş yoldurtan hatalarla karşılaşmak istemiyorsak...

 

Mecbur kalmadıkça döngü içinde sorgu kullanmayın

PHP kodu yazarken genelde veri tabanından çektiğimiz bilgileri düzenleyerek kullanırız. Bir çok internet yazılımcısı için "veri tabanından bilgi kümesi çekmek" cümlesi yalnızca "SELET * FROM tablo" yazmayı ifade ediyor. Belki biraz abartılı bulabilirsiniz ama bence SQL yazmayı bilmeden PHP kullanmak okuma yazma bilmeden roman yazmaya çalışmak gibi.

İşte size süratle giden bir otomobilde frene asılmaya benzeyen bir kod:

<?php
  $rs = $db->query("SELECT * FROM urunler");
  while($row = $rs->fetch_assoc()) {
    $frs = $db->query("SELECT * FROM fotolar WHERE urun_id = $row[id] LIMIT 1");
    $foto = $frs->fetch_assoc();
    echo '<div><img src="'.$foto['foto'].'" alt="Foto" />&nbsp;'.$row['isim'].'</div>';
  }
?>

Bunu yapmayın!

Onun yerine veri kümesini ihtiyaç duyacağınız tüm bilgileri içerecek şekilde oluşturun ve döngünüzü öyle başlatın:

<?php
  $rs = $db->query("
    SELECT
      urunler.isim, fotolar.foto
    FROM
      urunler
      INNER JOIN fotolar ON fotolar.urun_id = urunler.id
    GROUP BY
      urunler.id
  ");
  while($row = $rs->fetch_assoc()) {
    echo '<div><img src="'.$row['foto'].'" alt="Foto" />&nbsp;'.$row['isim'].'</div>';
  }
?>

 

Döngü içinde sorgu çalıştırmak hem işlemi yavaşlatacak hem de sunucunuzun kaynaklarını sömürecektir. Mecbur kalmadığınız sürece döngü içinde sorgu çalıştırmayın. Veri tabanınızı doğru tasarlayın, SQL cümlenizi doğru yazın ve döngü içinde sorgu çalıştırmaya mecbur kalmayın!

Yorumlar Yorum Yap

  • kaan

    Başarılı Bir yazı.

    kaan 09/05/2015 19:53
  • nusu

    INNER JOIN'in ne işe yaradığını bu yazınızda öğrendim, varlığını biliyordum ama hiç kullanmamıştım, aydınlattığınız için teşekkürler, bundan sonra döngü içinde sorgu yok ! :)

    nusu 20/01/2015 13:02
  • Tuğrul

    Güzel bir yazı olmuş elinize sağlık.

    Tuğrul 04/11/2014 20:56