Kode PHP Buggy: 10 Kesalahan Paling Umum Dilakukan Pengembang PHP

Diterbitkan: 2022-03-11

PHP membuatnya relatif mudah untuk membangun sistem berbasis web, yang menjadi alasan popularitasnya. Namun, terlepas dari kemudahan penggunaannya, PHP telah berkembang menjadi bahasa yang cukup canggih dengan banyak kerangka kerja, nuansa, dan kehalusan yang dapat menggigit pengembang, yang menyebabkan berjam-jam debugging yang menarik. Artikel ini menyoroti sepuluh kesalahan umum yang perlu diwaspadai oleh pengembang PHP.

Kesalahan Umum #1: Meninggalkan referensi array yang menjuntai setelah loop foreach

Tidak yakin bagaimana cara menggunakan loop foreach di PHP? Menggunakan referensi dalam perulangan foreach dapat berguna jika Anda ingin mengoperasikan setiap elemen dalam larik yang Anda ulangi. Sebagai contoh:

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

Masalahnya adalah, jika Anda tidak hati-hati, ini juga dapat memiliki beberapa efek samping dan konsekuensi yang tidak diinginkan. Secara khusus, dalam contoh di atas, setelah kode dieksekusi, $value akan tetap berada dalam cakupan dan akan menyimpan referensi ke elemen terakhir dalam array. Oleh karena itu, operasi selanjutnya yang melibatkan $value dapat secara tidak sengaja berakhir dengan memodifikasi elemen terakhir dalam array.

Hal utama yang harus diingat adalah bahwa foreach tidak membuat ruang lingkup. Jadi, $value dalam contoh di atas adalah referensi dalam lingkup teratas skrip. Pada setiap iterasi, foreach menetapkan referensi untuk menunjuk ke elemen berikutnya dari $array . Setelah loop selesai, oleh karena itu, $value masih menunjuk ke elemen terakhir dari $array dan tetap dalam cakupan.

Berikut adalah contoh dari jenis bug yang mengelak dan membingungkan yang dapat menyebabkan hal ini:

 $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";

Kode di atas akan menampilkan sebagai berikut:

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

Tidak, itu bukan salah ketik. Nilai terakhir pada baris terakhir memang 2, bukan 3.

Mengapa?

Setelah melalui loop foreach pertama, $array tetap tidak berubah tetapi, seperti dijelaskan di atas, $value dibiarkan sebagai referensi menggantung ke elemen terakhir dalam $array (karena loop foreach mengakses $value dengan reference ).

Akibatnya, ketika kita melewati loop foreach kedua, "hal-hal aneh" tampaknya terjadi. Khususnya, karena $value sekarang sedang diakses oleh value (yaitu, dengan copy ), foreach menyalin setiap elemen $array berurutan menjadi $value di setiap langkah loop. Akibatnya, inilah yang terjadi selama setiap langkah dari loop foreach kedua:

  • Pass 1: Menyalin $array[0] (yaitu, “1”) ke $value (yang merupakan referensi ke $array[2] ), jadi $array[2] sekarang sama dengan 1. Jadi $array sekarang berisi [1, 2, 1].
  • Pass 2: Salin $array[1] (yaitu, “2”) ke $value (yang merupakan referensi ke $array[2] ), jadi $array[2] sekarang sama dengan 2. Jadi $array sekarang berisi [1, 2, 2].
  • Pass 3: Salin $array[2] (yang sekarang sama dengan “2”) ke $value (yang merupakan referensi ke $array[2] ), jadi $array[2] masih sama dengan 2. Jadi $array sekarang berisi [1 , 2, 2].

Untuk tetap mendapatkan manfaat menggunakan referensi di loop foreach tanpa menjalankan risiko masalah semacam ini, panggil unset() pada variabel, segera setelah loop foreach , untuk menghapus referensi; misalnya:

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

Kesalahan Umum #2: Kesalahpahaman perilaku isset()

Terlepas dari namanya, isset() tidak hanya mengembalikan false jika item tidak ada, tetapi juga mengembalikan false untuk nilai null .

Perilaku ini lebih bermasalah daripada yang mungkin muncul pada awalnya dan merupakan sumber masalah yang umum.

Pertimbangkan hal berikut:

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

Penulis kode ini mungkin ingin memeriksa apakah keyShouldBeSet diatur dalam $data . Tetapi, seperti yang telah dibahas, isset($data['keyShouldBeSet']) juga akan mengembalikan false jika $data['keyShouldBeSet'] disetel , tetapi disetel ke null . Jadi logika di atas salah.

Berikut contoh lain:

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

Kode di atas mengasumsikan bahwa jika $_POST['active'] mengembalikan true , maka postData harus disetel, dan oleh karena itu isset($postData) akan mengembalikan true . Jadi sebaliknya, kode di atas mengasumsikan bahwa satu- satunya cara isset($postData) akan mengembalikan false adalah jika $_POST['active'] juga mengembalikan false .

Bukan.

Seperti yang dijelaskan, isset($postData) juga akan mengembalikan false jika $postData disetel ke null . Oleh karena itu, isset($postData) dapat mengembalikan false bahkan jika $_POST['active'] mengembalikan true . Jadi sekali lagi, logika di atas salah.

Dan omong-omong, sebagai poin tambahan, jika maksud dalam kode di atas benar-benar untuk memeriksa kembali apakah $_POST['active'] mengembalikan nilai true, mengandalkan isset() untuk ini adalah keputusan pengkodean yang buruk dalam hal apa pun. Sebaliknya, akan lebih baik untuk memeriksa ulang $_POST['active'] ; yaitu:

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

Namun, untuk kasus, di mana penting untuk memeriksa apakah suatu variabel benar-benar disetel (yaitu, untuk membedakan antara variabel yang tidak disetel dan variabel yang disetel ke null ), metode array_key_exists() jauh lebih kuat larutan.

Misalnya, kita bisa menulis ulang yang pertama dari dua contoh di atas sebagai berikut:

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

Selain itu, dengan menggabungkan array_key_exists() dengan get_defined_vars() , kita dapat dengan andal memeriksa apakah variabel dalam cakupan saat ini telah disetel atau belum:

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

Kesalahan Umum #3: Kebingungan tentang pengembalian berdasarkan referensi vs. berdasarkan nilai

Pertimbangkan cuplikan kode ini:

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

Jika Anda menjalankan kode di atas, Anda akan mendapatkan yang berikut:

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

Apa yang salah?

Masalahnya adalah bahwa kode di atas membingungkan mengembalikan array dengan referensi dengan mengembalikan array berdasarkan nilai. Kecuali jika Anda secara eksplisit memberi tahu PHP untuk mengembalikan array dengan referensi (yaitu, dengan menggunakan & ), PHP secara default akan mengembalikan array "berdasarkan nilai". Ini berarti bahwa salinan array akan dikembalikan dan oleh karena itu fungsi yang dipanggil dan pemanggil tidak akan mengakses instance array yang sama.

Jadi panggilan di atas ke getValues() mengembalikan salinan array $values ​​daripada referensi ke sana. Dengan mengingat hal itu, mari kita tinjau kembali dua baris kunci dari contoh di atas:

 // 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'];

Salah satu perbaikan yang mungkin adalah menyimpan salinan pertama dari array $values ​​yang dikembalikan oleh getValues() dan kemudian mengoperasikan salinan itu selanjutnya; misalnya:

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

Kode itu akan berfungsi dengan baik (yaitu, akan menampilkan test tanpa menghasilkan pesan "indeks tidak terdefinisi"), tetapi tergantung pada apa yang Anda coba capai, pendekatan ini mungkin atau mungkin tidak memadai. Secara khusus, kode di atas tidak akan mengubah array $values ​​asli. Jadi, jika Anda ingin modifikasi Anda (seperti menambahkan elemen 'test') memengaruhi larik asli, Anda perlu memodifikasi fungsi getValues() untuk mengembalikan referensi ke larik $values ​​itu sendiri. Ini dilakukan dengan menambahkan & sebelum nama fungsi, dengan demikian menunjukkan bahwa itu harus mengembalikan referensi; yaitu:

 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'];

Output dari ini akan menjadi test , seperti yang diharapkan.

Tetapi untuk membuat segalanya lebih membingungkan, pertimbangkan cuplikan kode berikut:

 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'];

Jika Anda menebak bahwa ini akan menghasilkan kesalahan "indeks tidak terdefinisi" yang sama seperti contoh array kami sebelumnya, Anda salah. Bahkan, kode ini akan bekerja dengan baik. Alasannya adalah, tidak seperti array, PHP selalu melewatkan objek dengan referensi . ( ArrayObject adalah objek SPL, yang sepenuhnya meniru penggunaan array, tetapi berfungsi sebagai objek.)

Seperti yang ditunjukkan oleh contoh-contoh ini, tidak selalu sepenuhnya jelas dalam PHP apakah Anda berurusan dengan salinan atau referensi. Oleh karena itu, penting untuk memahami perilaku default ini (yaitu, variabel dan array dilewatkan berdasarkan nilai; objek dilewatkan dengan referensi) dan juga untuk memeriksa dokumentasi API dengan cermat untuk fungsi yang Anda panggil untuk melihat apakah fungsi tersebut mengembalikan nilai, salinan array, referensi ke array, atau referensi ke objek.

Semua yang dikatakan, penting untuk dicatat bahwa praktik mengembalikan referensi ke array atau ArrayObject umumnya adalah sesuatu yang harus dihindari, karena memberikan pemanggil kemampuan untuk memodifikasi data pribadi instance. Ini "terbang di muka" enkapsulasi. Sebagai gantinya, lebih baik menggunakan "getter" dan "setter" gaya lama, misalnya:

 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'

Pendekatan ini memberi pemanggil kemampuan untuk menyetel atau mendapatkan nilai apa pun dalam larik tanpa memberikan akses publik ke larik $values ​​itu sendiri.

Kesalahan Umum #4: Melakukan kueri dalam satu lingkaran

Tidak jarang menemukan sesuatu seperti ini jika PHP Anda tidak berfungsi:

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

Meskipun mungkin sama sekali tidak ada yang salah di sini, tetapi jika Anda mengikuti logika dalam kode, Anda mungkin menemukan bahwa panggilan yang tampak tidak bersalah di atas ke $valueRepository->findByValue() pada akhirnya menghasilkan semacam kueri, seperti:

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

Akibatnya, setiap iterasi dari loop di atas akan menghasilkan kueri terpisah ke database. Jadi, jika, misalnya, Anda memberikan larik 1.000 nilai ke loop, itu akan menghasilkan 1.000 kueri terpisah ke sumber daya! Jika skrip semacam itu dipanggil dalam banyak utas, itu berpotensi membuat sistem terhenti.

Oleh karena itu, penting untuk mengenali kapan kueri dibuat oleh kode Anda dan, bila memungkinkan, kumpulkan nilai-nilainya, lalu jalankan satu kueri untuk mengambil semua hasil.

Salah satu contoh tempat yang cukup umum untuk menemukan kueri yang dilakukan secara tidak efisien (yaitu, dalam satu lingkaran) adalah ketika formulir diposting dengan daftar nilai (ID, misalnya). Kemudian, untuk mengambil data rekaman lengkap untuk setiap ID, kode akan mengulang array dan melakukan kueri SQL terpisah untuk setiap ID. Ini akan sering terlihat seperti ini:

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

Tetapi hal yang sama dapat dilakukan dengan lebih efisien dalam satu kueri SQL sebagai berikut:

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

Oleh karena itu, penting untuk mengenali kapan kueri dibuat, baik secara langsung maupun tidak langsung, oleh kode Anda. Jika memungkinkan, kumpulkan nilai-nilainya, lalu jalankan satu kueri untuk mengambil semua hasil. Namun kehati-hatian juga harus dilakukan di sana, yang membawa kita ke kesalahan umum PHP berikutnya…

Kesalahan Umum #5: Headfake dan inefisiensi penggunaan memori

Meskipun mengambil banyak catatan sekaligus jelas lebih efisien daripada menjalankan satu kueri untuk setiap baris yang akan diambil, pendekatan seperti itu berpotensi menyebabkan kondisi "memori habis" di libmysqlclient saat menggunakan ekstensi mysql PHP.

Untuk mendemonstrasikannya, mari kita lihat kotak uji dengan sumber daya terbatas (RAM 512MB), MySQL, dan php-cli .

Kami akan mem-bootstrap tabel database seperti ini:

 // 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); }

Oke, sekarang mari kita periksa penggunaan sumber daya:

 // 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";

Keluaran:

 Before: 224704 Limit 1: 224704 Limit 10000: 224704

Dingin. Sepertinya kueri dikelola dengan aman secara internal dalam hal sumber daya.

Hanya untuk memastikan, mari kita tingkatkan batas sekali lagi dan setel ke 100.000. Uh oh. Ketika kita melakukan itu, kita mendapatkan:

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

Apa yang terjadi?

Masalahnya di sini adalah cara kerja modul mysql PHP. Ini benar-benar hanya proxy untuk libmysqlclient , yang melakukan pekerjaan kotor. Ketika sebagian data dipilih, itu langsung masuk ke memori. Karena memori ini tidak dikelola oleh manajer PHP, memory_get_peak_usage() tidak akan menunjukkan peningkatan dalam pemanfaatan sumber daya saat kami menaikkan batas kueri kami. Hal ini menyebabkan masalah seperti yang ditunjukkan di atas di mana kita tertipu untuk berpuas diri dengan berpikir bahwa manajemen memori kita baik-baik saja. Namun pada kenyataannya, manajemen memori kita sangat cacat dan kita dapat mengalami masalah seperti yang ditunjukkan di atas.

Anda setidaknya dapat menghindari headfake di atas (meskipun itu sendiri tidak akan meningkatkan penggunaan memori Anda) dengan menggunakan modul mysqlnd . mysqlnd dikompilasi sebagai ekstensi PHP asli dan menggunakan manajer memori PHP.

Oleh karena itu, jika kita menjalankan tes di atas menggunakan mysqlnd daripada mysql , kita mendapatkan gambaran yang jauh lebih realistis tentang pemanfaatan memori kita:

 Before: 232048 Limit 1: 324952 Limit 10000: 32572912

Dan itu bahkan lebih buruk dari itu, omong-omong. Menurut dokumentasi PHP, mysql menggunakan sumber daya dua kali lebih banyak daripada mysqlnd untuk menyimpan data, jadi skrip asli yang menggunakan mysql benar-benar menggunakan lebih banyak memori daripada yang ditampilkan di sini (kira-kira dua kali lebih banyak).

Untuk menghindari masalah seperti itu, pertimbangkan untuk membatasi ukuran kueri Anda dan menggunakan loop dengan sejumlah kecil iterasi; misalnya:

 $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"); }

Ketika kami mempertimbangkan kesalahan PHP dan kesalahan #4 di atas, kami menyadari bahwa ada keseimbangan yang sehat yang idealnya harus dicapai oleh kode Anda antara, di satu sisi, membuat kueri Anda terlalu terperinci dan berulang, vs. kueri individu terlalu besar. Seperti halnya kebanyakan hal dalam hidup, keseimbangan dibutuhkan; baik ekstrim tidak baik dan dapat menyebabkan masalah dengan PHP tidak bekerja dengan baik.

Kesalahan Umum #6: Mengabaikan masalah Unicode/UTF-8

Dalam beberapa hal, ini benar-benar lebih merupakan masalah dalam PHP itu sendiri daripada sesuatu yang akan Anda hadapi saat men-debug PHP, tetapi tidak pernah ditangani secara memadai. Inti PHP 6 akan dibuat sadar akan Unicode, tetapi itu ditunda ketika pengembangan PHP 6 dihentikan pada tahun 2010.

Tapi itu tidak berarti membebaskan pengembang dari menyerahkan UTF-8 dengan benar dan menghindari asumsi yang salah bahwa semua string akan selalu menjadi "ASCII lama biasa". Kode yang gagal menangani string non-ASCII dengan benar terkenal karena memperkenalkan bug heisenbugs ke dalam kode Anda. Bahkan panggilan strlen($_POST['name']) yang sederhana dapat menyebabkan masalah jika seseorang dengan nama belakang seperti "Schrodinger" mencoba mendaftar ke sistem Anda.

Berikut adalah daftar periksa kecil untuk menghindari masalah seperti itu dalam kode Anda:

  • Jika Anda tidak tahu banyak tentang Unicode dan UTF-8, Anda setidaknya harus mempelajari dasar-dasarnya. Ada primer yang bagus di sini.
  • Pastikan untuk selalu menggunakan fungsi mb_* alih-alih fungsi string lama (pastikan ekstensi "multibyte" disertakan dalam build PHP Anda).
  • Pastikan database dan tabel Anda diatur untuk menggunakan Unicode (banyak build MySQL masih menggunakan latin1 secara default).
  • Ingat bahwa json_encode() mengonversi simbol non-ASCII (misalnya, "Schrodinger" menjadi "Schr\u00f6dinger") tetapi serialize() tidak .
  • Pastikan file kode PHP Anda juga dikodekan UTF-8 untuk menghindari tabrakan saat menggabungkan string dengan konstanta string yang di-hardcode atau dikonfigurasi.

Sumber daya yang sangat berharga dalam hal ini adalah UTF-8 Primer untuk PHP dan MySQL yang diposting oleh Francisco Claria di blog ini.

Kesalahan Umum #7: Dengan asumsi $_POST akan selalu berisi data POST Anda

Terlepas dari namanya, array $_POST tidak akan selalu berisi data POST Anda dan dapat dengan mudah ditemukan kosong. Untuk memahaminya, mari kita lihat sebuah contoh. Asumsikan kita membuat permintaan server dengan panggilan jQuery.ajax() sebagai berikut:

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

(Kebetulan, perhatikan contentType: 'application/json' di sini. Kami mengirim data sebagai JSON, yang cukup populer untuk API. Ini adalah default, misalnya, untuk memposting di layanan AngularJS $http .)

Di sisi server dari contoh kita, kita cukup membuang array $_POST :

 // php var_dump($_POST);

Anehnya, hasilnya adalah:

 array(0) { }

Mengapa? Apa yang terjadi dengan string JSON kami {a: 'a', b: 'b'} ?

Jawabannya adalah PHP hanya mem-parsing muatan POST secara otomatis ketika memiliki tipe konten application/x-www-form-urlencoded atau multipart/form-data . Alasan untuk ini adalah historis — kedua jenis konten ini pada dasarnya adalah satu-satunya yang digunakan bertahun-tahun yang lalu ketika $_POST PHP diimplementasikan. Jadi dengan jenis konten lainnya (bahkan yang cukup populer saat ini, seperti application/json ), PHP tidak secara otomatis memuat muatan POST.

Karena $_POST adalah superglobal, jika kita menimpanya sekali (sebaiknya di awal skrip kita), nilai yang dimodifikasi (yaitu, termasuk muatan POST) kemudian akan menjadi referensi di seluruh kode kita. Ini penting karena $_POST umumnya digunakan oleh kerangka kerja PHP dan hampir semua skrip khusus untuk mengekstrak dan mengubah data permintaan.

Jadi, misalnya, saat memproses muatan POST dengan tipe konten application/json , kita perlu menguraikan konten permintaan secara manual (yaitu, mendekode data JSON) dan menimpa variabel $_POST , sebagai berikut:

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

Kemudian ketika kita membuang array $_POST , kita melihat bahwa array tersebut menyertakan payload POST dengan benar; misalnya:

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

Kesalahan Umum #8: Berpikir bahwa PHP mendukung tipe data karakter

Lihat contoh potongan kode ini dan coba tebak apa yang akan dicetaknya:

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

Jika Anda menjawab 'a' sampai 'z', Anda mungkin terkejut mengetahui bahwa Anda salah.

Ya, itu akan mencetak 'a' melalui 'z', tetapi kemudian juga akan mencetak 'aa' melalui 'yz'. Mari kita lihat mengapa.

Di PHP tidak ada tipe data char ; hanya string yang tersedia. Dengan mengingat hal itu, menambah string z di PHP menghasilkan aa :

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

Namun untuk lebih membingungkan, aa secara leksikografis kurang dari z :

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

Itu sebabnya kode contoh yang disajikan di atas mencetak huruf a sampai z , tetapi kemudian juga mencetak aa sampai yz . Itu berhenti ketika mencapai za , yang merupakan nilai pertama yang ditemuinya yang "lebih besar dari" z :

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

Karena itu, inilah satu cara untuk mengulang nilai 'a' hingga 'z' dengan benar di PHP:

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

Atau sebagai alternatif:

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

Kesalahan Umum #9: Mengabaikan standar pengkodean

Meskipun mengabaikan standar pengkodean tidak secara langsung menyebabkan perlunya men-debug kode PHP, ini mungkin masih menjadi salah satu hal terpenting untuk dibahas di sini.

Mengabaikan standar pengkodean dapat menyebabkan banyak masalah pada suatu proyek. Paling-paling, ini menghasilkan kode yang tidak konsisten (karena setiap pengembang "melakukan hal mereka sendiri"). Tetapi yang paling buruk, ini menghasilkan kode PHP yang tidak berfungsi atau bisa sulit (kadang-kadang hampir tidak mungkin) untuk dinavigasi, sehingga sangat sulit untuk men-debug, meningkatkan, memelihara. Dan itu berarti produktivitas tim Anda berkurang, termasuk banyak usaha yang sia-sia (atau setidaknya tidak perlu).

Untungnya bagi pengembang PHP, ada Rekomendasi Standar PHP (PSR), yang terdiri dari lima standar berikut:

  • PSR-0: Standar Pemuatan Otomatis
  • PSR-1: Standar Pengkodean Dasar
  • PSR-2: Panduan Gaya Pengkodean
  • PSR-3: Antarmuka Logger
  • PSR-4: Pemuat Otomatis

PSR awalnya dibuat berdasarkan masukan dari pengelola platform yang paling dikenal di pasar. Zend, Drupal, Symfony, Joomla, dan lainnya berkontribusi pada standar ini, dan sekarang mengikutinya. Bahkan PEAR, yang berusaha menjadi standar selama bertahun-tahun sebelumnya, berpartisipasi dalam PSR sekarang.

Dalam beberapa hal, hampir tidak masalah apa standar pengkodean Anda, selama Anda menyetujui standar dan menaatinya, tetapi mengikuti PSR umumnya merupakan ide yang baik kecuali Anda memiliki alasan kuat pada proyek Anda untuk melakukan sebaliknya. . Semakin banyak tim dan proyek yang sesuai dengan PSR. Tt jelas diakui pada titik ini sebagai standar "" oleh sebagian besar pengembang PHP, jadi menggunakannya akan membantu memastikan bahwa pengembang baru terbiasa dan nyaman dengan standar pengkodean Anda ketika mereka bergabung dengan tim Anda.

Kesalahan Umum #10: Menyalahgunakan empty()

Beberapa pengembang PHP suka menggunakan empty() untuk pemeriksaan boolean untuk hampir semua hal. Namun, ada kasus di mana hal ini dapat menyebabkan kebingungan.

Pertama, mari kembali ke array dan instance ArrayObject (yang meniru array). Mengingat kesamaannya, mudah untuk mengasumsikan bahwa array dan instance ArrayObject akan berperilaku identik. Ini membuktikan, bagaimanapun, menjadi asumsi yang berbahaya. Misalnya, dalam PHP 5.0:

 // 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?

Dan untuk membuat keadaan menjadi lebih buruk, hasilnya akan berbeda sebelum PHP 5.0:

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

Pendekatan ini sayangnya cukup populer. Misalnya, ini adalah cara Zend\Db\TableGateway dari Zend Framework 2 mengembalikan data saat memanggil current() pada TableGateway::select() seperti yang disarankan oleh dokumen. Pengembang dapat dengan mudah menjadi korban kesalahan ini dengan data tersebut.

Untuk menghindari masalah ini, pendekatan yang lebih baik untuk memeriksa struktur array kosong adalah dengan menggunakan count() :

 // 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)

Dan kebetulan, karena PHP memberikan 0 ke false , count() juga dapat digunakan dalam kondisi if () untuk memeriksa array kosong. Perlu juga dicatat bahwa, dalam PHP, count() adalah kompleksitas konstan ( operasi O(1) ) pada array, yang membuatnya semakin jelas bahwa itu adalah pilihan yang tepat.

Contoh lain ketika empty() bisa berbahaya adalah saat menggabungkannya dengan fungsi kelas ajaib __get() . Mari kita definisikan dua kelas dan memiliki properti test di keduanya.

Pertama mari kita definisikan kelas Regular yang menyertakan test sebagai properti normal:

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

Kemudian mari kita definisikan kelas Magic yang menggunakan operator __get() ajaib untuk mengakses properti test :

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

Oke, sekarang mari kita lihat apa yang terjadi ketika kita mencoba mengakses properti test dari masing-masing kelas ini:

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

Baik sejauh ini.

Tapi sekarang mari kita lihat apa yang terjadi ketika kita memanggil empty() pada masing-masing:

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

Ugh. Jadi jika kita mengandalkan empty() , kita dapat disesatkan untuk percaya bahwa properti test $magic kosong, padahal kenyataannya disetel ke 'value' .

Sayangnya, jika sebuah kelas menggunakan fungsi __get() ajaib untuk mengambil nilai properti, tidak ada cara yang sangat mudah untuk memeriksa apakah nilai properti itu kosong atau tidak. Di luar ruang lingkup kelas, Anda benar-benar hanya dapat memeriksa apakah nilai null akan dikembalikan, dan itu tidak berarti bahwa kunci yang sesuai tidak disetel, karena sebenarnya dapat disetel ke null .

Sebaliknya, jika kita mencoba mereferensikan properti yang tidak ada dari instance kelas Regular , kita akan mendapatkan pemberitahuan yang mirip dengan yang berikut:

 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

Jadi poin utama di sini adalah bahwa metode empty() harus digunakan dengan hati-hati karena dapat menyebabkan hasil yang membingungkan – atau bahkan berpotensi menyesatkan, jika seseorang tidak berhati-hati.

Bungkus

Kemudahan penggunaan PHP dapat meninabobokan pengembang ke dalam rasa nyaman yang salah, membuat diri mereka rentan terhadap debugging PHP yang panjang karena beberapa nuansa dan keanehan bahasa. Ini dapat mengakibatkan PHP tidak berfungsi dan masalah seperti yang dijelaskan di sini.

Bahasa PHP telah berkembang secara signifikan selama 20 tahun sejarahnya. Membiasakan diri dengan seluk-beluknya adalah upaya yang bermanfaat, karena akan membantu memastikan bahwa perangkat lunak yang Anda hasilkan lebih terukur, kuat, dan dapat dipelihara.