前言
自從加入a8c,PHP作為常備語言之一,時至今日仍然像顆出奇蛋一樣止不盡的驚喜。天底下沒有完美的語言,但至今確實沒有任何一個我寫過的程式語言比PHP讓我感到更『魔幻』。本系列專為從PHP之外的語言開啟碼農人生、卻因命運安排整天與PHP為伍的人撰寫,收集一些從其他語言的角度看來不可思議的設計。如果能讓在讀這篇文章的你在實務上踩到而懷疑人生前就釋懷,莫大的榮幸。
祝各位天天PHP,天天開心。
進入正題
首先來看看今天的主角:gethostbyname() 在文件中是怎麼描述的吧:
gethostbyname ( string
$hostname
) : stringReturns the IPv4 address of the Internet host specified by
hostname
.
舉例來說:
php> print_r( gethostbyname( 'softman.blog' ) );
192.0.78.25
php> print_r( gethostbyname( 'localhost' ) );
127.0.0.1
很簡單吧。這基本上就是一個gethostbyname()系統函式的包裝,
但問題出在它的回傳值設計。根據官方文件:
Returns the IPv4 address or a string containing the unmodified
hostname
on failure.
『當錯誤發生時,會回傳無修改的hostname引數』
……………..

這神設計有兩個問題。第一,和許多PHP函式的錯誤行為相悖;第二,複雜的錯誤處理讓這個函式很難正確使用。如果你認為:
$hostname === gethostbyname( $hostname )
這樣就結了,那就太小看PHP惹 …
問題一:引數若傳入IP,回傳值語意模糊
想想看,什麼時候會要用到gethostbyname()
呢?無非是想要得到正確IP的時候吧?那傳入的剛好就IP呢?
php> print_r( gethostbyname( '192.0.78.25' ) );
192.0.78.25
這就顯現了這個回傳值設計的問題:上例中,如果單看回傳的192.0.78.25,到底是解析成功還是不成功?這就算搭配引數一起看都很難說它到底是不是在報錯,因為IP作為主機名很合理;如果用filter_var()
去檢驗,PHP也是認同它是合法的主機名的:
php > print_r( filter_var( '192.168.1.1', FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME ) );
192.168.1.1
因此,它應當是『解析成功』,只是『長得跟錯誤一模一樣』。MAGIC!🧙♂️
實務中我們也常看到這樣的用法:
function somehow_i_need_to_process_your_ip( $hostname ) {
$ip = gethostbyname( $hostname );
if ( $hostname === $ip ) {
return false;
}
// 開始對IP上下其手囉!
}
somehow_i_need_to_process_your_ip()
會從第一個if述句中默默回傳false;但事實上傳入的若是合法IP,它是能夠處理的。
問題二:無法處理夾雜null字元的字串
官方文件沒告訴俺們的是,它事實上在錯誤時回傳的不是『未修改的$hostname引數』,而是『$hostname引數到第一個null字元為止的內容』
……………. 蝦餃?
真心不騙,試試就知:
php> $x = 'abc' . chr(0) . 'xxx';
php> print_r( gethostbyname( $x ) );
abc
php> var_dump( $x === gethostbyname( $x ) );
bool(false)
所以,只要有心人刻意搞了個夾著null字元的字串進去,該判斷式就會誤判,導致整個函式的後續處理崩潰。
其實不只函式崩潰,猶記得踩到這個雷的當下,我的心,也跟著崩潰了。
重拾玻璃心
如果真的要非常嚴謹,應該要把傳入gethostbyname()
的引數先sanitize過,再用filter_var()
等檢驗確實是合法的主機名,最後再檢驗它是不是合法IP。但這樣是有些過分了;每個語言都有其原生設計,我們應該要盡量發揮其本色,而不是硬要套其他語言的特色。這就好像英文不能拿來寫七言絕句,就算我再怎麼愛強式靜態型別,過度sanitization與validation,寫出來的PHP code不但不自然,還會很慢。
因此,建議只要檢驗回傳值是不是合法IP就行了:
$ip = gethostbyname( $hostname );
if ( filter_var( $ip, FILTER_VALIDATE_IP ) ) {
// ...
}
這樣就可以把以上情況都處理掉,也不至於把邏輯搞到太複雜。
心,好像又堅強了一點點。
對「我們與PHP的距離(一): gethostbyname()」的一則回應