Buggy PHP Kodu: PHP Geliştiricilerinin Yaptığı En Yaygın 10 Hata

Yayınlanan: 2022-03-11

PHP, popülerliğinin büyük bir nedeni olan web tabanlı bir sistem oluşturmayı nispeten kolaylaştırır. Ancak kullanım kolaylığına rağmen, PHP geliştiricileri ısırabilecek, saatlerce tüyler ürpertici hata ayıklamaya yol açabilecek birçok çerçeve, nüans ve incelikle oldukça karmaşık bir dile dönüştü. Bu makale, PHP geliştiricilerinin dikkat etmesi gereken en yaygın on hatayı vurgulamaktadır.

Yaygın Hata 1: foreach döngülerinden sonra sarkan dizi referanslarını bırakmak

PHP'de foreach döngülerinin nasıl kullanılacağından emin değil misiniz? Yinelediğiniz dizideki her bir öğe üzerinde çalışmak istiyorsanız, foreach döngülerinde referansları kullanmak faydalı olabilir. Örneğin:

 $arr = array(1, 2, 3, 4); foreach ($arr as &$value) { $value = $value * 2; } // $arr is now array(2, 4, 6, 8)

Sorun şu ki, dikkatli olmazsanız bunun bazı istenmeyen yan etkileri ve sonuçları da olabilir. Spesifik olarak, yukarıdaki örnekte, kod yürütüldükten sonra $value kapsamda kalacak ve dizideki son öğeye bir referans tutacaktır. $value içeren sonraki işlemler bu nedenle istemeden dizideki son öğeyi değiştirmeye neden olabilir.

Hatırlanması gereken en önemli şey, foreach bir kapsam oluşturmamasıdır. Bu nedenle, yukarıdaki örnekteki $value , betiğin en üst kapsamındaki bir referanstır . Her yinelemede foreach , referansı $array öğesinin sonraki öğesine işaret edecek şekilde ayarlar. Döngü tamamlandıktan sonra, bu nedenle, $value hala $array son elemanına işaret eder ve kapsamda kalır.

İşte bunun yol açabileceği türden kaçamak ve kafa karıştırıcı hatalara bir örnek:

 $array = [1, 2, 3]; echo implode(',', $array), "\n"; foreach ($array as &$value) {} // by reference echo implode(',', $array), "\n"; foreach ($array as $value) {} // by value (ie, copy) echo implode(',', $array), "\n";

Yukarıdaki kod aşağıdaki çıktıyı verecektir:

 1,2,3 1,2,3 1,2,2

Hayır, bu bir yazım hatası değil. Son satırdaki son değer aslında 3 değil 2'dir.

Niye ya?

İlk foreach döngüsünden geçtikten sonra, $array değişmeden kalır, ancak yukarıda açıklandığı gibi, $value , $array son öğeye sarkan bir referans olarak bırakılır (çünkü bu foreach döngüsü $value by referansına erişir).

Sonuç olarak, ikinci foreach döngüsünden geçtiğimizde “garip şeyler” oluyor gibi görünüyor. Spesifik olarak, $value artık değerle (yani kopya ile) erişildiğinden, foreach , döngünün her adımında her sıralı $array öğesini $value değer'e kopyalar . Sonuç olarak, ikinci foreach döngüsünün her adımında şunlar olur:

  • 1 Geçişi: $array[0] ı (yani, "1") $value içine kopyalar (bu, $array[2] array[2] öğesine bir başvurudur), bu nedenle $array[2] şimdi 1'e eşittir. Dolayısıyla, $array şimdi [1, 2, 1].
  • Geçiş 2: $array[1] yi (yani, "2") $value içine kopyalar (bu, $array[2] bir başvurudur), bu nedenle $array[2] şimdi 2'ye eşittir. Dolayısıyla, $array şimdi [1, 2, 2].
  • 3. Geçiş: $array[2] yi (artık “2”ye eşittir) $value (bu, $array[2] referansıdır) kopyalar, bu nedenle $array[2] hala 2'ye eşittir. Dolayısıyla $array şimdi [1 içeriyor. , 2, 2].

Bu tür sorunlarla karşılaşmadan foreach döngülerinde referans kullanmanın avantajını elde etmek için, referansı kaldırmak için foreach döngüsünden hemen sonra değişkende unset() çağırın; Örneğin:

 $arr = array(1, 2, 3, 4); foreach ($arr as &$value) { $value = $value * 2; } unset($value); // $value no longer references $arr[3]

Yaygın Hata #2: yanlış anlama isset() davranışı

İsmine rağmen isset() yalnızca bir öğe yoksa false döndürmekle kalmaz, aynı zamanda null değerler için false döndürür .

Bu davranış, ilk bakışta göründüğünden daha sorunludur ve yaygın bir sorun kaynağıdır.

Aşağıdakileri göz önünde bulundur:

 $data = fetchRecordFromStorage($storage, $identifier); if (!isset($data['keyShouldBeSet']) { // do something here if 'keyShouldBeSet' is not set }

Bu kodun yazarı muhtemelen keyShouldBeSet $data içinde ayarlanıp ayarlanmadığını kontrol etmek istedi. Ancak tartışıldığı gibi isset($data['keyShouldBeSet']) $data['keyShouldBeSet'] , ancak null olarak ayarlanmışsa false döndürür . Yani yukarıdaki mantık hatalı.

İşte başka bir örnek:

 if ($_POST['active']) { $postData = extractSomething($_POST); } // ... if (!isset($postData)) { echo 'post not active'; }

Yukarıdaki kod, $_POST['active'] true değerini döndürürse, postData mutlaka ayarlanacağını ve bu nedenle isset($postData) true değerini döndüreceğini varsayar. Buna karşılık, yukarıdaki kod, isset($postData) false döndürmesinin tek yolunun $_POST['active'] öğesinin de false döndürmesi olduğunu varsayar.

Değil.

Açıklandığı gibi, $postData null olarak ayarlanmışsa isset($postData) da false döndürür. Bu nedenle $_POST['active'] true döndürse bile isset($postData) false false mümkündür. Yani yine yukarıdaki mantık hatalı.

Bu arada, bir yan nokta olarak, yukarıdaki koddaki amaç gerçekten $_POST['active'] öğesinin doğru olup olmadığını tekrar kontrol etmekse, bunun için isset() 'e güvenmek her durumda kötü bir kodlama kararıydı. Bunun yerine $_POST['active'] ; yani:

 if ($_POST['active']) { $postData = extractSomething($_POST); } // ... if ($_POST['active']) { echo 'post not active'; }

Yine de, bir değişkenin gerçekten ayarlanıp ayarlanmadığını kontrol etmenin önemli olduğu durumlarda (yani, ayarlanmamış bir değişken ile null olarak ayarlanmış bir değişken arasında ayrım yapmak için), array_key_exists() yöntemi çok daha sağlamdır. çözüm.

Örneğin, yukarıdaki iki örnekten ilkini aşağıdaki gibi yeniden yazabiliriz:

 $data = fetchRecordFromStorage($storage, $identifier); if (! array_key_exists('keyShouldBeSet', $data)) { // do this if 'keyShouldBeSet' isn't set }

Ayrıca, array_key_exists() ile get_defined_vars() birleştirerek, geçerli kapsamdaki bir değişkenin ayarlanıp ayarlanmadığını güvenilir bir şekilde kontrol edebiliriz:

 if (array_key_exists('varShouldBeSet', get_defined_vars())) { // variable $varShouldBeSet exists in current scope }

Yaygın Hata #3: Referansa göre ve değere göre döndürme konusunda kafa karışıklığı

Bu kod parçacığını düşünün:

 class Config { private $values = []; public function getValues() { return $this->values; } } $config = new Config(); $config->getValues()['test'] = 'test'; echo $config->getValues()['test'];

Yukarıdaki kodu çalıştırırsanız, aşağıdakileri alırsınız:

 PHP Notice: Undefined index: test in /path/to/my/script.php on line 21

Sorun nedir?

Sorun, yukarıdaki kodun, dizileri değere göre döndürmekle referans olarak dizileri döndürmeyi karıştırmasıdır. PHP'ye açıkça bir diziyi referansa göre döndürmesini söylemediğiniz sürece (yani, & kullanarak), PHP varsayılan olarak "değere göre" diziyi döndürür. Bu, dizinin bir kopyasının döndürüleceği ve bu nedenle çağrılan işlev ve arayan kişinin dizinin aynı örneğine erişemeyeceği anlamına gelir.

Bu nedenle, yukarıdaki getValues() çağrısı, bir referans yerine $values ​​dizisinin bir kopyasını döndürür. Bunu akılda tutarak, yukarıdaki örnekteki iki önemli satırı tekrar gözden geçirelim:

 // getValues() returns a COPY of the $values array, so this adds a 'test' element // to a COPY of the $values array, but not to the $values array itself. $config->getValues()['test'] = 'test'; // getValues() again returns ANOTHER COPY of the $values array, and THIS copy doesn't // contain a 'test' element (which is why we get the "undefined index" message). echo $config->getValues()['test'];

Olası bir düzeltme, getValues() tarafından döndürülen $values ​​dizisinin ilk kopyasını kaydetmek ve ardından bu kopya üzerinde çalışmak olabilir; Örneğin:

 $vals = $config->getValues(); $vals['test'] = 'test'; echo $vals['test'];

Bu kod iyi çalışacaktır (yani, herhangi bir “tanımsız dizin” mesajı oluşturmadan test çıktısı verecektir), ancak neyi başarmaya çalıştığınıza bağlı olarak, bu yaklaşım yeterli olabilir veya olmayabilir. Özellikle, yukarıdaki kod orijinal $values ​​dizisini değiştirmeyecektir. Bu nedenle, değişikliklerinizin (bir 'test' öğesi eklemek gibi) orijinal diziyi etkilemesini istiyorsanız, bunun yerine $values ​​dizisinin kendisine bir başvuru döndürmek için getValues() işlevini değiştirmeniz gerekir. Bu, işlev adından önce bir & eklenerek ve böylece bir başvuru döndürmesi gerektiği belirtilerek yapılır; yani:

 class Config { private $values = []; // return a REFERENCE to the actual $values array public function &getValues() { return $this->values; } } $config = new Config(); $config->getValues()['test'] = 'test'; echo $config->getValues()['test'];

Bunun çıktısı beklendiği gibi test olacaktır.

Ancak işleri daha da kafa karıştırıcı hale getirmek için bunun yerine aşağıdaki kod parçasını düşünün:

 class Config { private $values; // using ArrayObject rather than array public function __construct() { $this->values = new ArrayObject(); } public function getValues() { return $this->values; } } $config = new Config(); $config->getValues()['test'] = 'test'; echo $config->getValues()['test'];

Bunun, önceki array örneğimizle aynı "tanımsız dizin" hatasıyla sonuçlanacağını tahmin ettiyseniz, yanılıyorsunuz. Aslında, bu kod gayet iyi çalışacaktır. Bunun nedeni, dizilerden farklı olarak PHP'nin nesneleri her zaman referans yoluyla iletmesidir . ( ArrayObject , dizi kullanımını tamamen taklit eden ancak bir nesne olarak çalışan bir SPL nesnesidir.)

Bu örneklerin gösterdiği gibi, PHP'de bir kopyayla mı yoksa bir referansla mı uğraştığınız her zaman tamamen açık değildir. Bu nedenle, bu varsayılan davranışları anlamak (yani, değişkenler ve diziler değere göre iletilir; nesneler referansa göre iletilir) ve ayrıca çağırdığınız işlevin bir değer döndürüp döndürmediğini görmek için API belgelerini dikkatlice kontrol etmek önemlidir. bir dizinin kopyası, bir diziye yapılan başvuru veya bir nesneye yapılan başvuru.

Tüm bunlar, bir diziye veya ArrayObject bir referans döndürme uygulamasının, arayana örneğin özel verilerini değiştirme yeteneği sağladığı için genellikle kaçınılması gereken bir şey olduğunu belirtmek önemlidir. Bu, kapsüllemenin “yüzüne uçar”. Bunun yerine eski tarz "alıcılar" ve "ayarlayıcılar" kullanmak daha iyidir, örneğin:

 class Config { private $values = []; public function setValue($key, $value) { $this->values[$key] = $value; } public function getValue($key) { return $this->values[$key]; } } $config = new Config(); $config->setValue('testKey', 'testValue'); echo $config->getValue('testKey'); // echos 'testValue'

Bu yaklaşım, arayan kişiye, aksi takdirde özel olan $values ​​dizisinin kendisine genel erişim sağlamadan dizideki herhangi bir değeri ayarlama veya alma yeteneği verir.

Yaygın Hata #4: Bir döngüde sorgu gerçekleştirme

PHP'niz çalışmıyorsa, bunun gibi bir şeyle karşılaşmak nadir değildir:

 $models = []; foreach ($inputValues as $inputValue) { $models[] = $valueRepository->findByValue($inputValue); }

Burada kesinlikle yanlış bir şey olmasa da, ancak koddaki mantığı izlerseniz, yukarıdaki $valueRepository->findByValue() masum görünümlü çağrının nihayetinde aşağıdaki gibi bir sorguyla sonuçlandığını görebilirsiniz:

 $result = $connection->query("SELECT `x`,`y` FROM `values` WHERE `value`=" . $inputValue);

Sonuç olarak, yukarıdaki döngünün her yinelemesi, veritabanına ayrı bir sorgu ile sonuçlanacaktır. Örneğin, döngüye 1.000 değerlik bir dizi sağladıysanız, kaynağa 1.000 ayrı sorgu üretecektir! Böyle bir komut dosyası birden çok iş parçacığında çağrılırsa, sistemi potansiyel olarak durma noktasına getirebilir.

Bu nedenle, kodunuz tarafından sorguların ne zaman yapıldığını anlamak ve mümkün olduğunda değerleri toplamak ve ardından tüm sonuçları almak için bir sorgu çalıştırmak çok önemlidir.

Verimsiz bir şekilde (yani bir döngüde) yapılan sorgulamayla karşılaşmak için oldukça yaygın bir yere bir örnek, bir formun bir değerler listesi (örneğin kimlikler) ile gönderilmesidir. Ardından, her bir kimlik için tam kayıt verilerini almak için kod, dizi boyunca döngü yapacak ve her kimlik için ayrı bir SQL sorgusu yapacak. Bu genellikle şöyle görünecektir:

 $data = []; foreach ($ids as $id) { $result = $connection->query("SELECT `x`, `y` FROM `values` WHERE `id` = " . $id); $data[] = $result->fetch_row(); }

Ancak aynı şey, tek bir SQL sorgusunda aşağıdaki gibi çok daha verimli bir şekilde gerçekleştirilebilir:

 $data = []; if (count($ids)) { $result = $connection->query("SELECT `x`, `y` FROM `values` WHERE `id` IN (" . implode(',', $ids)); while ($row = $result->fetch_row()) { $data[] = $row; } }

Bu nedenle, kodunuz tarafından doğrudan veya dolaylı olarak sorguların ne zaman yapıldığını anlamak çok önemlidir. Mümkün olduğunda, değerleri toplayın ve ardından tüm sonuçları almak için bir sorgu çalıştırın. Yine de orada da dikkatli olunmalıdır, bu da bizi bir sonraki yaygın PHP hatamıza götürür…

Yaygın Hata #5: Bellek kullanımındaki hatalar ve verimsizlikler

Birçok kaydı aynı anda getirmek, getirilecek her satır için tek bir sorgu çalıştırmaktan kesinlikle daha verimli olsa da, böyle bir yaklaşım, PHP'nin mysql uzantısını kullanırken libmysqlclient potansiyel olarak “bellek yetersiz” durumuna yol açabilir.

Göstermek için sınırlı kaynaklar (512MB RAM), MySQL ve php-cli içeren bir test kutusuna bakalım.

Bunun gibi bir veritabanı tablosunu önyükleyeceğiz:

 // connect to mysql $connection = new mysqli('localhost', 'username', 'password', 'database'); // create table of 400 columns $query = 'CREATE TABLE `test`(`id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT'; for ($col = 0; $col < 400; $col++) { $query .= ", `col$col` CHAR(10) NOT NULL"; } $query .= ');'; $connection->query($query); // write 2 million rows for ($row = 0; $row < 2000000; $row++) { $query = "INSERT INTO `test` VALUES ($row"; for ($col = 0; $col < 400; $col++) { $query .= ', ' . mt_rand(1000000000, 9999999999); } $query .= ')'; $connection->query($query); }

Tamam, şimdi kaynak kullanımını kontrol edelim:

 // connect to mysql $connection = new mysqli('localhost', 'username', 'password', 'database'); echo "Before: " . memory_get_peak_usage() . "\n"; $res = $connection->query('SELECT `x`,`y` FROM `test` LIMIT 1'); echo "Limit 1: " . memory_get_peak_usage() . "\n"; $res = $connection->query('SELECT `x`,`y` FROM `test` LIMIT 10000'); echo "Limit 10000: " . memory_get_peak_usage() . "\n";

Çıktı:

 Before: 224704 Limit 1: 224704 Limit 10000: 224704

Serin. Sorgu, kaynaklar açısından dahili olarak güvenli bir şekilde yönetiliyor gibi görünüyor.

Yine de emin olmak için sınırı bir kez daha artıralım ve 100.000'e ayarlayalım. Ah-oh. Bunu yaptığımızda şunu elde ederiz:

 PHP Warning: mysqli::query(): (HY000/2013): Lost connection to MySQL server during query in /root/test.php on line 11

Ne oldu?

Buradaki sorun PHP'nin mysql modülünün çalışma şeklidir. Bu gerçekten sadece kirli işi yapan libmysqlclient için bir proxy. Verinin bir kısmı seçildiğinde, doğrudan belleğe gider. Bu bellek PHP'nin yöneticisi tarafından yönetilmediğinden, memory_get_peak_usage() yükselttiğimiz için kaynak kullanımında herhangi bir artış göstermez. Bu, bellek yönetimimizin iyi olduğunu düşünerek gönül rahatlığıyla kandırıldığımız yukarıda gösterilene benzer sorunlara yol açar. Fakat gerçekte, hafıza yönetimimiz ciddi şekilde kusurludur ve yukarıda gösterilene benzer sorunlar yaşayabiliriz.

Bunun yerine mysqlnd modülünü kullanarak en azından yukarıdaki headfake'den kaçınabilirsiniz (her ne kadar bellek kullanımınızı iyileştirmese de). mysqlnd , yerel bir PHP uzantısı olarak derlenmiştir ve PHP'nin bellek yöneticisini kullanır.

Bu nedenle, yukarıdaki testi mysql yerine mysqlnd kullanarak çalıştırırsak, bellek kullanımımızın çok daha gerçekçi bir resmini elde ederiz:

 Before: 232048 Limit 1: 324952 Limit 10000: 32572912

Ve bu arada, bundan daha da kötü. PHP belgelerine göre, mysql , verileri depolamak için mysqlnd iki kat daha fazla kaynak kullanır, bu nedenle mysql kullanan orijinal komut dosyası gerçekten burada gösterilenden daha fazla bellek kullandı (kabaca iki katı).

Bu tür sorunlardan kaçınmak için, sorgularınızın boyutunu sınırlamayı ve az sayıda yinelemeli bir döngü kullanmayı düşünün; Örneğin:

 $totalNumberToFetch = 10000; $portionSize = 100; for ($i = 0; $i <= ceil($totalNumberToFetch / $portionSize); $i++) { $limitFrom = $portionSize * $i; $res = $connection->query( "SELECT `x`,`y` FROM `test` LIMIT $limitFrom, $portionSize"); }

Hem bu PHP hatasını hem de yukarıdaki 4 numaralı hatayı göz önüne aldığımızda, kodunuzun ideal olarak sağlaması gereken sağlıklı bir denge olduğunu fark ederiz. bireysel sorgular çok büyük olabilir. Hayattaki çoğu şeyde olduğu gibi, denge gereklidir; her iki uç da iyi değildir ve PHP'nin düzgün çalışmamasıyla ilgili sorunlara neden olabilir.

Yaygın Hata #6: Unicode/UTF-8 sorunlarını yoksayma

Bir anlamda bu, PHP'de hata ayıklarken karşılaşacağınız bir sorundan çok PHP'nin kendisinde bir sorundur, ancak hiçbir zaman yeterince ele alınmamıştır. PHP 6'nın çekirdeği Unicode'a duyarlı hale getirilecekti, ancak PHP 6'nın geliştirilmesi 2010'da askıya alındığında bu beklemeye alındı.

Ancak bu hiçbir şekilde geliştiriciyi UTF-8'i düzgün bir şekilde vermekten ve tüm dizelerin mutlaka "düz eski ASCII" olacağı hatalı varsayımından kaçınmasını engellemez. ASCII olmayan dizeleri düzgün bir şekilde işlemeyen kod, kodunuza bunaltıcı heisenbug'ları eklemekle ünlüdür. Basit strlen($_POST['name']) çağrıları bile “Schrödinger” gibi bir soyadı olan biri sisteminize kaydolmaya çalışırsa sorunlara neden olabilir.

Kodunuzda bu tür sorunları önlemek için küçük bir kontrol listesi:

  • Unicode ve UTF-8 hakkında fazla bir şey bilmiyorsanız, en azından temel bilgileri öğrenmelisiniz. Burada harika bir astar var.
  • Eski dize işlevleri yerine her zaman mb_* işlevlerini kullandığınızdan emin olun ("multibyte" uzantısının PHP derlemenize dahil edildiğinden emin olun).
  • Veritabanınızın ve tablolarınızın Unicode kullanacak şekilde ayarlandığından emin olun (birçok MySQL yapısı varsayılan olarak hala latin1 kullanır).
  • json_encode() öğesinin ASCII olmayan sembolleri dönüştürdüğünü (örneğin, “Schrodinger”, “Schr\u00f6dinger” olur) ancak serialize() öğesinin dönüştürmediğini unutmayın .
  • Dizeleri sabit kodlanmış veya yapılandırılmış dize sabitleriyle birleştirirken çakışmaları önlemek için PHP kod dosyalarınızın da UTF-8 kodlu olduğundan emin olun.

Bu bağlamda özellikle değerli bir kaynak, Francisco Claria tarafından bu blogda yazılan PHP ve MySQL için UTF-8 Primer yazısıdır.

Yaygın Hata #7: $_POST her zaman POST verilerinizi içereceğini varsaymak

Adına rağmen $_POST dizisi her zaman POST verilerinizi içermez ve kolayca boş bulunabilir. Bunu anlamak için bir örneğe bakalım. Aşağıdaki gibi bir jQuery.ajax() çağrısı ile bir sunucu isteği yaptığımızı varsayalım:

 // js $.ajax({ url: 'http://my.site/some/path', method: 'post', data: JSON.stringify({a: 'a', b: 'b'}), contentType: 'application/json' });

(Bu arada, burada contentType: 'application/json' not edin. Verileri API'ler için oldukça popüler olan JSON olarak gönderiyoruz. Bu, örneğin AngularJS $http hizmetine göndermek için varsayılandır.)

Örneğimizin sunucu tarafında, sadece $_POST dizisini atıyoruz:

 // php var_dump($_POST);

Şaşırtıcı bir şekilde, sonuç şöyle olacaktır:

 array(0) { }

Niye ya? JSON dizgimize {a: 'a', b: 'b'} ne oldu?

Cevap, PHP'nin bir POST yükünü yalnızca application/x-www-form-urlencoded veya multipart/form-data içerik türüne sahip olduğunda otomatik olarak ayrıştırmasıdır. Bunun nedenleri tarihseldir - bu iki içerik türü aslında yıllar önce PHP'nin $_POST uygulandığında kullanılan tek içerik türüydü. Bu nedenle, diğer içerik türlerinde ( application/json gibi günümüzde oldukça popüler olanlarda bile), PHP POST yükünü otomatik olarak yüklemez.

$_POST bir süper küresel olduğundan, onu bir kez geçersiz kılarsak (tercihen betiğimizin başlarında), değiştirilen değer (yani, POST yükü dahil) daha sonra kodumuz boyunca referans alınabilir. $_POST , PHP çerçeveleri ve neredeyse tüm özel komut dosyaları tarafından istek verilerini çıkarmak ve dönüştürmek için yaygın olarak kullanıldığından bu önemlidir.

Bu nedenle, örneğin, application/json içerik türüyle bir POST yükünü işlerken, istek içeriğini manuel olarak ayrıştırmamız (yani, JSON verilerinin kodunu çözmemiz) ve $_POST değişkenini aşağıdaki gibi geçersiz kılmamız gerekir:

 // php $_POST = json_decode(file_get_contents('php://input'), true);

Sonra $_POST dizisini döktüğümüzde, POST yükünü doğru bir şekilde içerdiğini görüyoruz; Örneğin:

 array(2) { ["a"]=> string(1) "a" ["b"]=> string(1) "b" }

Yaygın Hata #8: PHP'nin bir karakter veri türünü desteklediğini düşünmek

Bu örnek kod parçasına bakın ve ne yazdıracağını tahmin etmeye çalışın:

 for ($c = 'a'; $c <= 'z'; $c++) { echo $c . "\n"; }

Eğer 'a' ile 'z' arasında cevaplar verdiyseniz, yanıldığınızı öğrenince şaşırabilirsiniz.

Evet, 'a' ile 'z' arasında yazdıracak, ancak daha sonra 'aa' ile 'yz' arasında da yazdıracaktır. Neden görelim.

PHP'de char veri tipi yoktur; sadece string mevcuttur. Bunu akılda tutarak, PHP'de z string artırmak, aa verir:

 php> $c = 'z'; echo ++$c . "\n"; aa

Yine de meseleleri daha da karıştırmak için, aa sözlükbilimsel olarak z küçüktür :

 php> var_export((boolean)('aa' < 'z')) . "\n"; true

Bu nedenle, yukarıda sunulan örnek kod, a z kadar olan harfleri yazdırır, ancak daha sonra yz aa da yazdırır. Karşılaştığı ilk değer olan za değerine ulaştığında durur z 'den "büyüktür":

 php> var_export((boolean)('za' < 'z')) . "\n"; false

Durum böyle olunca, PHP'de 'a' ile 'z' arasındaki değerler arasında düzgün bir şekilde dolaşmanın bir yolu şudur:

 for ($i = ord('a'); $i <= ord('z'); $i++) { echo chr($i) . "\n"; }

Veya alternatif olarak:

 $letters = range('a', 'z'); for ($i = 0; $i < count($letters); $i++) { echo $letters[$i] . "\n"; }

Yaygın Hata #9: Kodlama standartlarını göz ardı etmek

Kodlama standartlarını göz ardı etmek, doğrudan PHP kodunda hata ayıklama ihtiyacına yol açmasa da, muhtemelen burada tartışılması gereken en önemli şeylerden biridir.

Kodlama standartlarını göz ardı etmek, bir projede çok sayıda soruna neden olabilir. En iyi ihtimalle, tutarsız bir kodla sonuçlanır (çünkü her geliştirici "kendi işini yapıyor"). Ancak en kötüsü, çalışmayan veya gezinmesi zor (bazen neredeyse imkansız) olabilen ve hata ayıklamayı, geliştirmeyi ve bakımını son derece zorlaştıran PHP kodu üretir. Bu da, boşa harcanan (veya en azından gereksiz) çaba da dahil olmak üzere ekibiniz için azaltılmış üretkenlik anlamına gelir.

Neyse ki PHP geliştiricileri için, aşağıdaki beş standarttan oluşan PHP Standartları Önerisi (PSR) vardır:

  • PSR-0: Otomatik Yükleme Standardı
  • PSR-1: Temel Kodlama Standardı
  • PSR-2: Kodlama Stili Kılavuzu
  • PSR-3: Kaydedici Arayüzü
  • PSR-4: Otomatik Yükleyici

PSR, başlangıçta piyasadaki en tanınmış platformların bakımcılarından alınan girdilere dayanarak oluşturuldu. Zend, Drupal, Symfony, Joomla ve diğerleri bu standartlara katkıda bulundu ve şimdi onları takip ediyor. Ondan önce yıllarca standart olmaya çalışan PEAR bile şimdi PSR'ye katılıyor.

Bir anlamda, bir standart üzerinde anlaştığınız ve ona bağlı kaldığınız sürece, kodlama standardınızın ne olduğu neredeyse hiç önemli değil, ancak projenizde aksini yapmak için zorlayıcı bir nedeniniz olmadıkça PSR'yi takip etmek genellikle iyi bir fikirdir. . Gittikçe daha fazla ekip ve proje PSR ile uyumludur. Tt, bu noktada PHP geliştiricilerinin çoğunluğu tarafından kesinlikle “standart” olarak kabul edilmektedir, bu nedenle onu kullanmak, yeni geliştiricilerin ekibinize katıldıklarında kodlama standardınızı tanıdık ve rahat olmalarını sağlamaya yardımcı olacaktır.

Yaygın Hata #10: empty() işlevini yanlış kullanmak

Bazı PHP geliştiricileri, hemen hemen her şey için boolean kontrolleri için empty() kullanmayı sever. Bununla birlikte, bunun kafa karışıklığına yol açabileceği durumlar vardır.

İlk olarak, dizilere ve (dizileri taklit eden) ArrayObject örneklerine geri dönelim. Benzerlikleri göz önüne alındığında, dizilerin ve ArrayObject örneklerinin aynı şekilde davranacağını varsaymak kolaydır. Ancak bu, tehlikeli bir varsayım olduğunu kanıtlıyor. Örneğin, PHP 5.0'da:

 // PHP 5.0 or later: $array = []; var_dump(empty($array)); // outputs bool(true) $array = new ArrayObject(); var_dump(empty($array)); // outputs bool(false) // why don't these both produce the same output?

Daha da kötüsü, sonuçlar PHP 5.0'dan önce farklı olurdu:

 // Prior to PHP 5.0: $array = []; var_dump(empty($array)); // outputs bool(false) $array = new ArrayObject(); var_dump(empty($array)); // outputs bool(false)

Bu yaklaşım ne yazık ki oldukça popüler. Örneğin, belgenin önerdiği gibi, Zend\Db\TableGateway of Zend Framework 2, TableGateway::select() sonucu üzerinde current() çağrılırken verileri bu şekilde döndürür. Geliştirici, bu tür verilerle kolayca bu hatanın kurbanı olabilir.

Bu sorunlardan kaçınmak için boş dizi yapılarını denetlemeye yönelik daha iyi bir yaklaşım, count() işlevini kullanmaktır:

 // Note that this work in ALL versions of PHP (both pre and post 5.0): $array = []; var_dump(count($array)); // outputs int(0) $array = new ArrayObject(); var_dump(count($array)); // outputs int(0)

Ve tesadüfen, PHP 0 false değerine çevirdiğinden, count() boş dizileri kontrol etmek için if () koşulları içinde de kullanılabilir. Ayrıca PHP'de count() 'un diziler üzerinde sabit karmaşıklık ( O(1) işlemi) olduğunu ve bunun doğru seçim olduğunu daha da netleştirdiğini belirtmekte fayda var.

empty() 'un tehlikeli olabileceği başka bir örnek, onu sihirli sınıf işlevi __get() ile birleştirmektir. İki sınıf tanımlayalım ve her ikisinde de test özelliğine sahip olalım.

Önce normal bir özellik olarak test içeren bir Regular sınıf tanımlayalım:

 class Regular { public $test = 'value'; }

Ardından, test özelliğine erişmek için magic __get() operatörünü kullanan bir Magic sınıfı tanımlayalım:

 class Magic { private $values = ['test' => 'value']; public function __get($key) { if (isset($this->values[$key])) { return $this->values[$key]; } } }

Tamam, şimdi bu sınıfların her birinin test özelliğine erişmeye çalıştığımızda ne olacağını görelim:

 $regular = new Regular(); var_dump($regular->test); // outputs string(4) "value" $magic = new Magic(); var_dump($magic->test); // outputs string(4) "value"

Şimdiye kadar iyi.

Ama şimdi bunların her birine empty() çağırdığımızda ne olacağını görelim:

 var_dump(empty($regular->test)); // outputs bool(false) var_dump(empty($magic->test)); // outputs bool(true)

Ah. Bu nedenle, empty() öğesine güvenirsek, $magic öğesinin test özelliğinin boş olduğuna, oysa gerçekte 'value' olarak ayarlandığına inanarak yanıltılabiliriz.

Ne yazık ki, bir sınıf bir özelliğin değerini almak için sihirli __get() işlevini kullanıyorsa, bu özellik değerinin boş olup olmadığını kontrol etmenin kusursuz bir yolu yoktur. Sınıfın kapsamı dışında, gerçekten yalnızca bir null değerin döndürülüp döndürülmeyeceğini kontrol edebilirsiniz ve bu, gerçekte null olarak ayarlanmış olabileceğinden , karşılık gelen anahtarın ayarlanmadığı anlamına gelmez.

Buna karşılık, bir Regular sınıf örneğinin var olmayan bir özelliğine başvurmaya çalışırsak, aşağıdakine benzer bir bildirim alırız:

 Notice: Undefined property: Regular::$nonExistantTest in /path/to/test.php on line 10 Call Stack: 0.0012 234704 1. {main}() /path/to/test.php:0

Dolayısıyla buradaki ana nokta, eğer dikkatli olunmazsa, kafa karıştırıcı – hatta potansiyel olarak yanıltıcı – sonuçlara yol açabileceğinden, empty() yönteminin dikkatli kullanılması gerektiğidir.

Sarmak

PHP'nin kullanım kolaylığı, geliştiricileri sahte bir rahatlık duygusuna kaptırabilir ve dilin bazı nüansları ve kendine özgü özellikleri nedeniyle kendilerini uzun PHP hata ayıklamalarına karşı savunmasız bırakabilir. Bu, PHP'nin çalışmamasına ve burada açıklananlar gibi sorunlara neden olabilir.

PHP dili, 20 yıllık tarihi boyunca önemli ölçüde gelişmiştir. Ürettiğiniz yazılımın daha ölçeklenebilir, sağlam ve sürdürülebilir olmasını sağlamaya yardımcı olacağından, yazılımın inceliklerini öğrenmek değerli bir çabadır.