最近用 php 寫了一個小程式 jesp 可以拿來把好幾個 csv 檔 join 在一起, 並且讓訪客可以很方便地按照任何欄位排序。 過程當中發現 php 語言有很多怪怪的地方, 包含表達能力不足的設定檔、 用過一次就會有意外副作用的 reference 變數、 不能疊起來的三元運算子。 這真是一個處處充滿詫異的神奇語言啊~~
一、 設定檔
你的 php 程式應該以什麼方式讀設定檔?
最多人推的解答是採用 include。
但我希望使用者可以在網址列上用 query string 指定設定檔, 而
以這種方式使用 include 會開啟重大安全漏洞 --
例如你的伺服器上的其他用戶可以藉此以你的身份執行他寫的程式。
第二名的解答是採用 parse_ini_file 。 可是
ini 檔案格式的表達力有限, 無法表達多層的陣列。
所以最後決定採用 json 格式。
注意: 讀取內含中文的 json 檔時, 要加上第二個參數 「TRUE」, 類似這樣:
$config = json_decode(file_get_contents($config), TRUE);
二、 foreach 迴圈的 reference dummy variable
php 的 foreach 迴圈允許你用 & 符號表達 reference, 也就是允許你在迴圈當中修改陣列的元素。 那麼你覺得這段程式碼會印出什麼呢?
<?php $L = array(18, 25, 3); foreach ($L as &$x) { $x += 1; } foreach ($L as $x) { echo " $x"; } echo "\n"; ?>
答案是 19 26 26 大驚! 原來是因為經過第一個迴圈之後, $x 跟 $L[2] 已經合體變成同一個變數, 所以在第二次的迴圈當中, 那個 $x 其實是... 也就是說 $L[2] 被拿來成迴圈變數, 導致它的值逐次變成陣列當中其他每個元素的值, 最後變成倒數第二個元素的值。 詳見 php foreach pass by reference bug? 及 wierd behavior of foreach 我看還是不要用 reference 版的 foreach 迴圈比較簡單。
三、 三元運算子
在其他語言用過三元運算子吧?
printf("%s\n", 5>3 ? "A" : "B");
會印出 A。 但若把兩個三元運算子疊起來呢?
(nested ternary operator)
C 語言的版本會印出 "B", 一如預料:
#include <stdio.h> int main() { printf("%s\n", 5>3 ? "A" : 5<3 ? "B" : "C"); }
但同樣的程式碼, 在 php 的版本卻會印出 "A"!
<?php printf("%s\n", 5>3 ? "A" : 5<3 ? "B" : "C"); ?>
原來是因為 php 的三元運算子的運算順序跟其他語言 (比較符合直覺的順序) 不一樣。 詳見 這個問答。 總之, 在 php 裡使用疊起來的三元運算子時, 一定要用括弧, 或乾脆避免疊起來最簡單。
四、 array_filter 的傳回值
以下程式碼會印出什麼?
$numbers = array(18, 25, 3, 49, 7); $small = array_filter($numbers, fn($x)=>$x<10); print("$mall[0]\n");
會印出 3 嗎? 不, 會出現錯誤訊息: Undefined offset: 0
用 print_r 查看完整的 $small 就會知道:
php 的陣列,即使採用整數作為註標,
它的行為仍舊比較像是字串型態的 key, 中間可以跳號的。
所以上例當中, $small[2]
才是 3。
array_filter 傳回來的經常就是含有很多跳號的子陣列。
如果
想要得到「註標從0開始,沒有跳號」的陣列,
就要這樣做: array_values(array_filter(...))
。
php 作者前些日子還說過 php 當初就是拿來當樣板語言。
回覆刪除結果現在還出現一堆 php 寫出來的樣板語言框架。(苦笑)
php 語言本身進化過程加入許多新語言的特性過程中也埋下不少地雷。