php讓人難堪。它是如此的破碎,但那些被培訓的業余愛好者,卻對它稱贊不已。php在做一些徽不足道的挽回措施,但我選擇忘記它。
前言
我的脾氣古怪。我會抱怨很多東西。這個星球上大多數技術我都不喜歡。
PHP不僅使用起來尴尬,還有要嘛我想要的不適合,要嘛不是最令人滿意,要嘛違背我的信仰。我可以告訴你關於一門語言,所有我想避免的好方式,所有我喜歡的壞方式。來吧,問吧!談話會很有趣!
php是唯一的例外。幾乎php抽象的所有東西都是支離破碎的。包括語言,框架,整個生態系統都一塌糊塗。我幾乎不能單獨列出咒罵的事情,因為它全身都壞了。每次我打算編輯一堆雜亂如麻的php抱怨清單的時候,我都被一些瑣事打亂,越深入就越會發現其它令人震驚的事情。
php讓人難堪。它是如此的破碎,但那些被培訓的業余愛好者,卻對它稱贊不已。php在做一些徽不足道的挽回措施,但我選擇忘記它。
不過我得讓我的系統擺脫這些東西,也就這樣了,這是最後一次嘗試。
打個比喻
我只是隨口和Mel抱怨下,而她卻堅決讓我發表出來。
我甚至說不出來PHP到底怎麼了,因為—還好。想想你有一個,嗯,工具箱吧。一堆工具。看起來還好,有標准的東西。
你拔除螺絲釘,它怪異的有三個頭。OK,好吧,這對你不太有用,但你猜遲早有天會有用。
你拿出榔頭,被震住了,兩邊都有是尖爪。但它仍然能用,我的意思是,你可以用兩頭的中部斜著敲。
你拿出老虎鉗,但它們沒有鋸齒面。表面平而光滑。這沒多大用,但依然能用,沒什麼。
你可以繼續。工具箱的東西都是怪異和琢磨不定的,但又不能說毫無價值。整體看沒什麼大問題;它的工具都齊全。
現在,想象有很多使用這些工具的木匠,它們和你說:”這些工具有什麼問題呢?我們都用過,它們工作都很好啊!”。工匠們給你展示他們建的房子,每個門都是五邊形的而屋頂是癫倒的。你敲前門,它向內倒榻了,而他們卻抱怨你打破了他們的門。
這就是PHP的問題。
立場
我認為下面的特質對於一門語言的生產力和可用性是重要的,而PHP在大范圍破壞它們。如果你不同意這些,好吧,我無法想像,我們永遠不會達成一致。
一門語言必須是可預見的。它是將人類的思想反映給計算機執行的媒介,因此它的關鍵是,人類對程序的理解實際要正確。
語言必須一致。相似的東西就要看起來相似,不同的就是不同。學習了語言的部分知識,就應能很容易理解剩下的部分。
語言必須簡潔。新語言應該減少繼承舊語言的不好的形式。(我們也可以寫機器碼。)新語言當然應努力避免織入新的特有的形式。
語言必須是可靠的。語言是解決問題的工具;應盡量避免引入新問題。任何”陷阱”都會大量的分散注意力。
語言必須是可調試的。當出錯的時候,程序員必須修正它,我們需要獲得我們想要的幫助。
我的立場是:
PHP到處處充滿驚奇:mysql_real_escape_string,E_ACTUALLY_ALL
PHP不一致:strpos,str_rot13
PHP需要特別形式:error-checkingaroundCAPIcalls,===
PHP古怪:==。for($fooas&$bar)
PHP晦澀:默認無棧跟蹤或fatals,復雜的錯誤報告
我不能就單個問題解釋為什麼它歸為這些類,否則將會沒完沒了。我相信讀者自己會思考。
不要再和我扯這些東西了
我知道很多有利的論點。我也聽到很多反駁的論點。這些都只能讓談話立即停止。不要再跟我扯這些東西了,求你了。
不要和我說”好的開發者能用任何語言寫出好的代碼”,或者壞開發者。.吧啦吧啦。這毫無意義。好的工匠可以用石頭或錘子駕馭釘子,但你見過有多少工匠用石頭的?成為一個好開發者的標准之一就是善於選擇工具。
不要和我說熟記上千個例外和古怪行為是開發者的職責。是的,這在任何系統中都是必要的,因為電腦是傻的。這不意味著,系統能瘋狂的接受而沒有上限。PHP有的只是異常,這是不行的,一旦和語言摔角決斗,你實際編寫程序就要花費更多的努力。我的工具不能為我創建應用產生積極作用。
不要和我說“那就是CAPI的工作方式”。這星球上高級語言存在的目的是什麼,它們能提供的一切僅僅是一些字符串助手函數和一堆C的包裝器?如果是這樣,那就用C!這裡,甚至還有為它准備的CGI庫。
不要和我扯“搞出奇怪的事,是你活該”。如果存在兩個特性,總有一天,某些人會找到一起使用它們的理由。再次強調,這不是C;這裡沒有規范,這裡不需要“未定義行為”。
不要再和我扯Facebook和Wikipedia就用的PHP.我早知道了!它們也能用Brainfuck寫,但只要他們足夠陪明,不斷折騰這些事情,他們總能克服平台的問題。眾所周知,如果使用其它語言編寫,開發時間可能會減少一半或加倍;單獨拿出這些數據毫無意義。
上帝保佑,不要再和我扯任何東西了!如果列出的沒有傷害你的PHP的觀點,無所謂,因此請停止在網上做無意義的爭論,繼續開發高帥富酷的站點來證明我是錯的。
偷偷告訴你:我非常喜歡Python.我也很樂意對它說些你不愛聽的話,如果你真想的話。我並不要求它完美;我只是想揚長避短,總結我想要的最佳東西。
PHP
語言核心
CPAN被稱為“Perl的標准庫”。這並沒有對Perl的標准庫做過多說明,但它蘊含了健壯的核心可以構建強大的東西的思想。
基本原則
PHP最初很明確的是為非程序員設計的(言外之意,非專業程序);根源已經很難脫離。從PHP2.0文檔中挑選出來的對話:
一旦你開始為每個類型區分不同的操作符,你就開始使用語言變得復雜了。例如,你不能為strings使用‘==’,你現在必須用‘eq’。我沒看出這點來,特別是那些類似PHP的腳本語言,它們大多數相當簡單而多數情況下,作為非程序員,只想要一門包含少量基本邏輯語法的語言,而不想付出過多學習曲線。
PHP為保持前進不惜代價。什麼都有比沒有好。
這不是個正確的設計原則。早期的PHP受Perl影響;大量的標准庫參考C使用“out”參數;OO部分的設計像C++和Java.
PHP從其它語言中引入大量的靈感,但對那些熟知其它語言的人,仍然難以理解。(int)看起來像C,但是int並不存在。命名空間使用。新的數組語法使用[key=>value],不同於任何其它語言定義hash字面量的形式。
弱類型(例如,默默的自動在strings/mumbers/等間轉換)是如此的復雜。
少量的新特性以新語法實現;大多數工作通過函數或者看起來像函數的東西完成。除了類的支持,這理所當然的需要新的操作符和關鍵字。
本頁列出的問題都有官方解決方案—如果你想資助Zend修復它們的開源編程語言的話。
路漫漫,其修遠。思考下面的代碼,從PHP文檔的某地方挑出來的。
@fopen('http://example.com/not-existing-file', 'r');
它將做什麼?
如果PHP使用–disable-url-fopen-wrapper編譯,它將不工作。(文檔沒有說,“不工作”是什麼意思;返回null,拋出異常?)
注意這點已在PHP5.2.5中移除。
如果allow_url_fopen在php.ini中禁用,也將不工作。(為什麼?無從得知。)
由於@,non-existentfile的警告將不打印。
但如果在php.ini中設置了scream.enabled,它又將打印。
或者如果用ini_set手動設置scream.enabled.
但,如果error_reporting級別沒設置,又不同。
如果打印出來了,精確去向依賴於display_errors,再一次還是在php.ini.或者ini_set中。
我無法告訴你這個函數調用的行為,如果沒有查看編譯時標志,服務器端配置,和我的程序中的配置的話。這些都是內建行為。
該語言充滿了全局和隱似狀態。mbstring使用全局字符編碼。func_get_arg之類的看起來像正常的函數,但是只對當前正在執行的函數操作。Error/exception處理默認是全局的。register_tick_function設置了一個全局函數去運行每個tick(鉤子?)—-什麼?!
沒有任何線程支持。(不奇怪,因為上面已給出。)加之缺乏內建的fork(下面提到),使得並行編程極其困難。
PHP的某些部分在實踐中會產生錯誤代碼。
json_decode對不正確的輸入返回null,盡管null也是一個JSON解碼的合法對象—該函數極不可靠,除非你每次使用後都調用json_last_error.
如果在位置0處找到,array_search,strpos,和其它類似的函數返回0,但如果都沒有找到的話。會返回false
讓我們稍稍展開最後一部分。
在C中,函數如strpos返回-1,如果未找到。如果你沒檢查這種情況,卻試著以下標使用它,那將可能命中垃圾內存,程序會崩潰。(也許吧,這是C.誰泥馬知道。我確定至少有工具處理它)
話說,Python中,等效的.index方法將拋出一個異常,如果元素沒找到的話。如果你不檢查該情形,程序將崩潰。
在PHP中,該函數返回false.如果你把FALSE作為下標使用,或者用它做其他事情,PHP會默默的將它轉成0,但除了用於===比較。程序是不會崩潰的;它將執行錯誤的邏輯,且無任何警告,除非你記得在每個使用strpos和其它類似函數的地方包含正確的樣版處理代碼。
這真是糟透了!編程語言只是工具;它們是為我服務的。這裡,PHP給我布下了陷阱,等著我跳進去,而我不得不時刻警惕這些無聊的字符串操作和相等比較。PHP是個雷區。
我已經聽過很多關於PHP解析器的故事,它的開發者來自世界各地。有從事PHP核心開發工作的人,有調試PHP核心的人,也有和核心開發者交流過的人。沒有一個故事是贊賞的。
因此不得不在這裡插入一句,因為它值得重復:PHP是個業余愛好者的社區。極少數人設計,為它工作,或極少有人知道他們在做什麼。(哦,親愛的讀者,你當然是個極品例外!)那些成長了,想轉投其它平台的人,使整個社區的平均水平下降。這個,就是這裡,是PHP的最大問題:絕對的盲目領導盲目。
好了,回來面對現實吧。
操作符
==不中用。
"foo"==TRUE,和"foo"==0…但,當然TRUE!=0。
==會將兩邊轉成數字,如果可能的話,這意味著它將轉成floats如果可能。所以大的16進制字符串(如,passwordhashes)可能偶然會比較成true,盡管它們不一樣。就連JavaScript都不會這樣做。
由於某些原因,"6"=="6","4.2"=="4.20",和"133"=="0133"。但注意133!=0133,因為0133是八進制的。
===比較值和類型…除了對象,只有兩邊實際上是同一對象才為true!對於對象,==比較值(或每個屬性)和類型,這又是===比較任何非對象類型的行為。好玩嗎?
比較大小也好不到哪去。
甚至行為都不一致:NULL<-1,而NULL==0。排序也因此不確定;它依賴於在排序中比較元素的算法的順序。
比較操作符嘗試排序數組,以兩種不同的方式:首先按長度,然後按元素。如果它們有相同數量的元素但不同的keys,它們是不可比的。
對象比較比其它比較做得更多…除了那些即不小於也不大於的對象。
為了類型更安全的==比較,我們有===。為了類型更安全的<比較,我們有…什麼也沒有。"123"<"0124",通常,不管你怎麼做。類型轉換也無濟於事。
盡管上面的舉動很瘋狂,但卻明確拒絕Perl’s的字符串paris和算術運行符,PHP沒有重載+.+就是通常的+,而。是通常的連接符。
[]下標操作符也可以拼寫成{}。
[]可以用於任何變量,不光是字符串和數組。它返回null,無錯誤警告。
[]僅能獲取單個元素。
foo()[0]是個語法錯誤。(已在PHP5.4中修復)
不像(從字面上看)任何其它語言都有的類似的操作符,?:是左結合的。因此:
$arg = 'T';
$vehicle = ( ( $arg == 'B' ) ? 'bus' :
( $arg == 'A' ) ? 'airplane' :
( $arg == 'T' ) ? 'train' :
( $arg == 'C' ) ? 'car' :
( $arg == 'H' ) ? 'horse' :
'feet' );
echo $vehicle;
打印horse.
變量
無法聲明變量。當第一次使用時,不存在的變量會被創建為null值。
全局變量在使用前,需要global聲明。這是根據上面得出的自然結果,因此這是個完美的理由,但,如果沒有顯示的聲明,全局變量甚至無法讀取—PHP將悄悄的創建一個局部同名變量取代它。我還沒見過其它語言使用類似的方法處理范圍問題。
沒有引用。PHP所謂的引用是個真正的別名;這無疑是一種倒退,不像Perl的引用,也沒有像Python那樣的對象標識傳遞。
沒有明顯的方式檢測和取消引用。
”引用”使變量在語言中與眾不同。PHP是動態類型的,因此變量通常無類型…除了引用,它修飾函數定義,變量語法,和賦值。一旦變量被引用(可在任何地方發生),它就一直是個引用。沒有明顯的方法探測和解引用需要的變量值。
好吧,我說謊了。有些”SPLtypes”也作用於變量:$x=newSplBool(true);$x="foo";將失敗。這有點像靜態類型,自己看看。
Areferencecanbetakentoakeythatdoesn’texistwithinanundefinedvariable(whichbecomesanarray)。Usinganon-existentarraynormallyissuesanotice,butthisdoesnot.
通過函數定義的常量稱為takingastring;這之前,它們不存在。(這可能實際上是復制Perl使用常量的行為。)
變量名是大小寫敏感的。函數和類名不是。使得方法使用駝峰式命名會很奇怪。
結構
array()和幾個類似的結構不是函數。$func="array";$func();不工作。
數組拆包可以使用list($a,$b)=…操作完成。list()是類函數語法,就像數組那樣。我不知道為什麼不給一個真正的專用語法,也不知道為什麼名字如些的讓人迷惑。
(int)很顯然的被設計成類似C,但它不是單獨的標記;在語言中,沒有東西被稱為int.試試看:var_dump(int)不工作,它會拋出一個解析錯誤,因為參數看起來像是強制轉操作符。
(integer)是(int)的別名。也有(bool)/(boolean)和(float)/(double)/(real)。
有個(array)操作符用來轉成數組和(object)用來轉成對象。這聽起來很貼心,但常常有個用例:你可以用(array)使得某個函數參數,既可以是單個元素,也可以是列表,相同對待。但這樣做不可靠,因為如果某人傳遞了單個對象,把它轉換成數組將實際上生成了一個包含對象屬性的數組。(轉換成對象執行了反轉操作。)
include()這類的函數基本上就是C的#include:他們將其它的文件源碼轉存到你的文件中。沒有模塊系統,甚至對PHP代碼也一樣。
沒有類似嵌套或者局部范圍的函數或類。它們都是全局的。include某文件,它的變量導入到當前函數范圍中(給了文件訪問你的變量的能力),但是函數和類存入全局范圍中。
追加數組使用$foo[]=$bar.
echo不是函數。
empty($var)是如此極端,對於任何其它東西不表現為函數,除了變量,e.g.empty($var||$var2),是個解析錯誤。為什麼地球上有這種東西,解析器為什麼需要了解empty?
還有些冗余的語法塊:if(…):…endif;,等等。
錯誤處理
PHP的一個獨特操作符是@(實際上從DOS借用過來的),它隱藏錯誤。
PHP錯誤不提供棧軌跡。你不得不安裝一個處理器生成它們。(但fatalerrors不行—見下文。)
PHP的解析錯誤通常只拋出解析的狀態,沒其它東西了,使得調試很糟糕。
PHP的解析器所指的例如。::內部作為T_PAAMAYIM_NEKUDOTAYIM,而<<操作符作為T_SL.我說“內部的”,但像上面說的,給程序員顯示的::或<<出現在了錯誤的位置。
大多數錯誤處理打印給服務器日志打印一行錯誤日志,沒人看到而一直進行。
E_STRICT看起來像那麼回事,但它實際上沒多少保護,沒有文檔顯示它實際上是做什麼的。
E_ALL包含了所有的錯誤類別—除了E_STRICT.
關於什麼允許而什麼不允許是古怪而不一致的。我不知道E_STRICT是怎樣適用於這裡的,但這些卻是正確的:
試圖訪問不存在的對象屬性,如,$foo->x.(warning)
使用變量做為函數名,或者變量名,或者類名。(silent)
試圖使用未定義常量。(notice)
試圖訪問非對象類型的屬性。(notice)
試圖使用不存在的變量名。(notice)
2<“foo”(隱藏)
foreach(2as$foo);(warning)
而下面這些不行:
試圖訪問不存在的類常量,如$foo::x.(fatalerror)
使用字符串常量作為函數名,或變量名,或類名。(parseerror)
試圖調用一個示定義函數。(fatalerror)
Leavingoffasemicolononthelaststatementinablockorfile.(parseerror)
使用list和其它准內建宏作為方法名。(parseerror)
用下標訪問函數的返回值,如:foo()[0]。(parseerror;已在5.4中修復)
在列表的其他地方也有幾個關於其它怪異解析錯誤的好例子
__toString方法不能拋出異常。如果你嘗試,PHP將…呃,拋出一個異常。(實際上是個fatalerror,可以被通過的,除了…)
PHP錯誤和PHP異常是完全不同的物種。它們不能相互作用。
PHP錯誤(內部,稱為trigger_error)不能被try/catch捕獲。
同樣,異常不能通過set_error_handler安裝的錯誤處理器觸發錯誤。
作為替代,有一個單獨的set_exception_handler可以處理未捕獲的異常,因為用try塊包裝你程序入口在mod_pho模塊中是不可能的。
Fatal錯誤(例如,newClassDoesntExist())不能被任何東西捕獲。大量的完全無害的操作會拋出fatal錯誤,由於一些有爭議的原因被迫終結你的程序。關閉函數仍然運行,但它們無法獲取棧軌跡(它們運行在上層),它們很難告知該程序是由一個錯誤還是程序的正常運行結束。
沒有finally結構,使得包裝代碼(注冊處理器,運行代碼,注銷處理器;monkeypatch,運行測試,unmonkeypatch)很難看,很難寫。盡管OO和異常大量的復制了Java的模式,這是故意的,因為finally“在PHP上下文中,只得其形不得其神”.Huh?
函數
函數調用似乎相當昂貴。
一些內建函數與reference-returning函數交互,呃,一種奇怪的方式。
正如在別處提到的,很多看起來像函數或者看起來它們應該是函數的東西實際上是語言的構成部分,因此無法像正常函數一樣的工作。
函數參數可以具有“類型提示”,基本上只是靜態類型。你不能要求某個參數是int或是string或是對象或其它“核心”類型,即使每個內建函數使用這種類型,可能因為int在PHP中不是個東西吧。(查看上面關於(int)的討論)。你也不能使用特殊的被大量內建函數使用的偽類型裝飾:mixed,number,orcallback.
因此,下面:
function foo(string $s) {}
foo("hello world");
產生錯誤theerror:
PHPCatchablefatalerror:Argument1passedtofoo()mustbeaninstanceofstring,stringgiven,calledin…
你可能會注意到“類型提示”實際上並不存在;在程序中沒有string類。如果你試圖使用ReflectionParameter::getClass()動態測試類型提示,將會得到類型不存在,使得實際上不可能取得該類型名。
函數的返回值不能被推斷
將當前函數的參數傳給另一個函數(分派,不罕見)通過call_user_func_array(‘other_function’,func_get_args())完成。但func_get_args在運行時拋出一個fatal錯誤,抱怨它不能作為函數參數。為什麼為什麼這是個類型錯誤?(已在PHP5.3中修復)
閉包需要顯示的命名每個變量為closed-over.為什麼解析器不想辦法解決?(Okay,it’sbecauseusingavariableever,atall,createsitunlessexplicitlytoldotherwise.)
Closed-over變量,通過和其它函數參數相同的語義”傳遞”。這樣的話,數組和字符串等等,將以傳值方式傳給閉包。除非使用&.
因為閉包變量會自動傳遞參數,沒有嵌套范圍,閉包不能指向私有方法,不管是否定義在類中。(可能在5.4中修復?不清楚。)
函數沒有命名參數。實際上被devs顯示拒絕,因為它“會導致代碼臭味”。
Functionargumentswithdefaultscanappearbeforefunctionargumentswithout,eventhoughthedocumentationpointsoutthatth
isisbothweirdanduseless.(Sowhyallowit?)
向函數傳遞額外的參數會被忽略(除了內建函數,會拋出異常)。丟失的參數被假定為null.
”可變”函數需要func_num_args,func_get_arg,和func_get_args.這類事情沒有語法。
OO
PHP的函數部分被設計成類似C,但面向對象(hoho)被設計成類似Java.我不想過分強調這有多不合諧。我還沒有發現一個有大寫字母的全局函數,重要的內建類使用駝峰式方法命名,並有getFoo的Java風格的屬性訪問器。這是門動態語言,對嗎?Perl,Python,和Ruby都有一些通過代碼訪問”屬性”的概念;PHP僅僅有笨重的__get之類的東西。類型系統圍繞著低層的Java語言設計,Java和PHP’s處一時代,Java有意的做了更多限制,照搬Java,我百思不得其解。
類不是對象。元編程不得不通過字符串名指向它們,就像函數一樣。
內建的類型不是對象,(不像Perl)也無法使得看起來像對象。
instanceof是個操作符,盡管很晚才增加進來,而大多數語言都建有專門的函數和語法。受Java影響嗎?類不是第一類?(我不知道它們是不是。)
但有一個is_a函數。它有個可選參數指定是否允許對象實際是一個字符串命名的類。
get_class是函數;沒有typeof操作符。同樣有is_subclass_of.
然而,這對於內建類型無法工作,(再一次,int不是個東西)。這樣,你需要is_int等等。
右值必須是變量或字面量;不能是表達式。不然會導致…一個解析錯誤。
clone是一個操作符?!
OO的設計是一只混合Perl和Java的怪物。
對象屬性通過$obj->foo,但類屬性是$obj::foo.我沒見過任何其它語言這樣做,或者這樣做有什麼用。
而,實例方法仍然能通過靜態的(Class::method)調用。如果從其它方法中這麼調用,會在當前$this上被看成常規的方法調用。我認為吧。
new,private,public,protected,static,等等。試圖虜獲Java開發者的芳心?我知道這更多是個人的品位,但我不知道為什麼這些東西在一門動態語言中是必要的—在C++中,它們中的大多數是有關匯編和編譯時的命名決議。
子類不能覆蓋private方法。子類覆蓋的公共方法也不可見,單獨調用,超類的私有方法。會有問題,如在測試mocks對象時。
方法無法命名為,例如“list”,因為list()是特殊的語法(不是個函數),而解析器會被搞暈。如此暧昧的原因無從得知,而類工作得就很好。($foo->list()不是語法錯誤。)
如果當解析構造函數參數時拋出異常(如,newFoo(bar())而bar()拋出),構造函數不會被調用,但析構函數會。(已在PHP5.3中修復)
在__autoload和解析函數中的異常會導致fatal錯誤。
沒有構造器或析構器。__construct是個初始化函數,像Python的__init__.無法通過調用類申請內存和創建對象。
沒有默認的初始化函數。調用parent::__construct()的時候,如果父類沒定義它自己的__construct方法會導致fatal錯誤。
OO帶來了個迭代器接口,是語言規范的部分(如…as…),但該接口實際上沒有內建實現(如數組)。如果你想要個數組迭代器,你必須用ArrayIterator包裝它。沒有內建方式能夠讓迭代器將其作為第一類對像工作。
類可以重載它們轉化成字符串的方式,但不能重載怎樣轉換成數字或任何其它內建類型的方式。
字符串,數字,和數組都有字符串轉換方式;語言很依賴於此。函數和類都是字符串。然而,如果沒定義__toString,試圖將換內建或自定義對像(甚至於一個閉包)轉換成字符串會導致錯誤,甚至連echo都可能出錯。
無法重載相等或比較操作。
實例方法中的靜態變量是全局的;它們的值跨越該類的多個實例共享。
標准庫
Perl“某些需要匯編”。Python是“batteriesincluded”。PHP是“廚房水槽,它來自加拿大,但所有的水龍頭用C貼牌”。
概括
沒有類型系統。你可以編譯PHP,但必須通過php.ini指定要加載什麼,選項因擴展部分存在(將它們的內容注入到全局名稱空間中)或不存在。
因為名稱空間是最近才有的特性,標准庫一點沒被打亂。在全局名稱空間中有上千個函數。
庫的某些部分很不一致。
下劃線對無下劃線:strpos/str_rot13,php_uname/phpversion,base64_encode/urlencode,gettype/get_class
“to”對2:ascii2ebcdic,bin2hex,deg2rad,strtolower,strtotime
Object+verb對verb+object:base64_decode,str_shuffle,var_dumpversuscreate_function,recode_string
參數順序:array_filter($input,$callback)versusarray_map($callback,$input),strpos($haystack,$needle)versusarray_se
arch($needle,$haystack)
前綴混亂:usleepvsmicrotime
Caseinsensitivefunctionsvaryonwheretheigoesinthename.
大概一半的數組函數以array_開頭。剩下的不是。
廚房水槽。庫包括:
綁定ImageMagick,綁定GraphicsMagick(ImageMagick的派生),少量的幾個函數能檢測EXIF數據(其中ImageMagick已經可以做到)
解析bbcode的函數,一些非常特殊的標記,被幾個少量的論壇包使用。
太多XML包。DOM(OO),DOMXML(not),libxml,SimpleXML,“XMLParser”,XMLReader/XMLWriter,和一大砣我不能認出的東西就省略了。當然會有些不同,你可以自由的弄清晰它們的區別。
綁定了兩個特別的信用卡處理器,SPPLUS和MCVE.什麼?
三種訪問MySQL數據庫的方式:mysql,mysqli,和PDO抽象的一些東西。
C影響
它需要擁有的自己的符號。PHP是個高層的,動態類型的語言。然後大量的標准庫的部分仍然只是圍繞CAPIS的薄層封裝,伴隨著下面的東西:
”Out”參數,盡管PHP可以返回ad-hoc哈希或毫不費力的返回多參數。
至少一打的函數是為了獲取某子系統的最近一次錯誤(見下文),盡管PHP已存存異常處理功能8年了。
有個mysql_real_escape_string,盡管已有個具有相同參數的mysql_escape_string,僅僅因為它是MySQLCAPI的一部分。
全局行為卻是非全局功能的(如MySQL)。使用多個MySQL連接需要顯示的對每個函數調用傳遞連接句柄。
包裝器真的,真的,真的很薄。例如,調用了dba_nextkey而沒調用dba_firstkey將出現段錯誤。
有一堆的ctype_*函數(如ctype_alnum)映射類似名稱的C字符函數,而不是如,isupper.
Genericism
如果函數相做兩件略有不同的事,PHP就搞出兩個函數。
你怎樣反向排序?在Perl中,你可以用{$b<=>$a}。在Python中,你可能用.sort(reverse=True)。在PHP中,有個特別的函數叫rsort()。
那些看起來像Cerror的函數:curl_error,json_last_error,openssl_error_string,imap_errors,mysql_error,xml_get_error_code,bzerror,da
te_get_last_errors,還有其它的嗎?
排序函數:array_multisort,arsort,asort,ksort,krsort,natsort,natcasesort,sort,rsort,uasort,uksort,usort
文本檢索函數:ereg,eregi,mb_ereg,mb_eregi,preg_match,strstr,strchr,stristr,strrchr,strpos,stripos,strrpos,strripos,m
b_strpos,mb_strrpos,plusthevariationsthatdoreplacements
有大量的別名:strstr/strchr,is_int/is_integer/is_long,is_float/is_double,pos/current,sizeof/count,chop/rtrim,implode/join,die/exit,trigger_error/user_error…
scandir返回一個當前給出目錄的文件列表。而不是(可能有益)按返回目錄順序返回,函數返回一個已排序的文件列表。有個可選的參數可以按字母逆順返回。這些用於排序很顯然很不夠。
str_split將字符串拆成等長的塊。chunk_split將字符串拆成等長的塊,然後用個分隔符連接。
讀取壓縮文件需要一套單獨的函數,取決於格式。有六套函數,它們的API都不同,如bzip2,LZF,phar,rar,zip,和gzip/zlib
因為使用參數數組調用函數是如此的別扭(call_user_func_array),所以有些配套的像printf/vprintf和sprintf/vsprintf.它們做相同的事,但一個帶多個參數,另一個帶參數數組。
文本
preg_replace帶/e(eval)標志的將用待替換的字符串替換匹配的部分,然後eval它。
strtok的設計顯然是和C函數等效的,由於很多原因,已被認為是個壞注意。PHP可以輕易的返回一個數組(而這在C中別扭),很多的hackstrtok(3)用法(修改字符串某處),在這裡不能使用。
parse_str解析查詢字符串,從函數名看不出任何跡象。而它會register_globals並轉存查詢字符串到本地范圍變量中,除非你傳遞一個數組來填充。(當然,什麼也不返回)
碰到空分隔符,explode會拒絕分割。每個其它的字符串拆分實現采取這種作法的意思應該是把字符串應拆分成字符;PHP有一個拆分函數,令人迷惑的稱為str_split而卻描述為“將字符串轉成數組”。
格式化日期,有strftime,像CAPI處理本地語言環境一樣。當然也有date,完全不同的語法而僅用於English.
”gzgetss—獲取gz文件的行指針並去除HTML標記。”知道了這一系列函數的概念,讓我去死吧。
mbstring
都是關於“multi-byte”,解決字符集的問題。
仍然處理的是普通字符串。有個單一的全局”默認”的字符集。一些函數允許指定字符集,但它依賴於所有的參數和返回值。
提供了ereg_*函數,但這些都被廢棄了。preg_*很幸運,用一些PCRE-specific標記,它們能理解UTF-8.
系統和反射
有一大堆的函數,聚焦於文本和變量。壓縮和提取僅是冰山一角。
有幾種方式讓PHP動態,咋一看沒有什麼明顯的不同或相對好處。類工具不能修改自定義類;運行時工具取代了它並能修改自定義的任何東西;Reflection*類能反射語言的大部分東西;有很多獨特的函數是為了報告函數和類的屬性的。這些子系統是獨立,相關,多余的嗎?
get_class($obj)返回對象的類名稱。get_class()返回被調用函數中的類的名稱。撇開這些不說,同一個函數會做完全不同的事情:get_class(null)…行為象後者。因此面對一個隨機的變量,你不能信任它。驚訝吧!
stream_*類允許實現自定義的流對象給fopen和其它的內建的類似文件處理的東西使用。由於幾個內部原因,“通知”不能被實現。
register_tick_function能接受閉包對象。unregister_tick_function不行;相反,它會拋出錯誤,抱怨閉包不能轉換成字符串。
php_uname告知你當前操作系統相關東西。
fork和exec不是內建的。它們來自pcntl擴展,但默認不包含。popen不提供pid文件。
session_decode用於讀取任意的PHPsession字符串,但僅當有個活躍的session時才工作。它轉存結果到$_SESSION中,而不是返回它的值。
雜項
curl_multi_exec不改變curl_error當出錯的時候,但它改變curl_error.
mktime的參數是有順序的:hour,minute,second,month,day,year
數據操縱
程序什麼都不是,除了咀嚼和吐出數據以外。大量的語言圍繞著數據操縱設計,從awk到Prolog到C.如果語言無法操縱數據,它就無法做任何事。
數字
Integers在32位平台是是有符號32位數。不像PHP的同時代者,沒有自動bigint提升。因此你的數學運算可能會由於CPU體系結構結果不一樣。你唯一選擇大整數的方式是使用GMP或BC包裝函數。(開發者可能已經建義加入新的,單獨的,64位類型。這真是瘋了。)
PHP支持八進制數語法,以0開頭,因此如012是10.然而,08變成了0.8(或9)和任何接下來的數字消失了。01c是個語法錯誤。
pi是個函數。或者有個常量,M_PI.
沒有冪操作符,只有pow函數。
文本
無Unicode支持。只有ASCII工作是可靠的,真的。有個mbstring擴展,上面提過的,但會稍被打擊。
這意味著使用內建的string函數處理UTF-8文本會有風險。
相似的,在ASCII外,也沒有什麼大小寫比較概念。盡管有擴展版本的大小寫敏感的函數,但它們不會認為é等於É。
你不能在變量中內插keys,如,“$foo[‘key’]“是個語法錯誤。你也不能unquoteit(這樣會產生警告,無論什麼地方!),或使用${…}/{$…}
”${foo[0]}”是對的。“${foo[0][0]}”是個語法錯誤。糟糕的拷貝類似Perl的語法(兩個根本不同的語議)?
數組
嘔,騷年。
這家伙扮演list數據類型,操作hash,和排序set,解析list,偶爾會有些奇怪的組合。它是怎樣執行的?以何種方式使用內存?誰知道?不喜歡,反正我還有其它的選擇。
=>不是操作符。它是個特別的結構,僅僅存在於array(…)和foreach結構中。
負值索引不工作,盡管-1也是個和0一樣的合法鍵值。
盡管這是語言級的數據結構,但沒有簡短語法;array(…)是簡短語法。(PHP5.4帶來了”literals”,[。..]。)
=>結構是基於Perl,Perl允許foo=>1而不用引號。在PHP中,你這麼做會得到警告;沒有無需引號創建hash字符串鍵值的方式。
數組處理函數常常讓人迷惑或有不確定行為,因為它們不得不對lists,hashes,或可能兩者的結合體做運算。考慮array分組,“計算arrays的不同部分”。
$first = array("foo" => 123, "bar" => 456);
$second = array("foo" => 456, "bar" => 123);
echo var_dump(array_diff($first, $second));
這段代碼將做什麼?如果array_diff將參數以hashes看待,它們明顯是不同的;相同的keys有不同的值。如果以list看待,它們仍然是不同的;值的順序不同。
事實上array_diff認為它們相等,因為它以sets對待:僅僅比較值,忽略順序。
同樣,array_rand隨機選擇keys時,也有奇怪的行為,這對大多數需要從列表中挑出東西的用例沒什麼幫助。
盡管大量PHP代碼依賴key的順序:
array("foo", "bar") != array("bar", "foo")
array("foo" => 1, "bar" => 2) == array("bar" => 2, "foo" => 1)
如果兩個數組混合的話,會發生什麼?我留給讀者自己弄清楚。(我不知道)
array_fill不能創建0長度的數組;相反它會發出警告並返回false.
所有的(很多的…)排序函數就地操作而什麼都不返回。想新建一個已排序數組的拷貝,沒門;你不得不自己拷貝數組,然後排序,然後再使用數組。
但array_reverse返回一個新數組。
一堆被排序的東西和一些鍵值對聽起來像是個某種強大的處理函數參數的方式,但,沒門。
非數組
標准庫包含“快速哈希”,“特定的強類型”的hash結構OO實現。然,深入它,有4類,每種處理不同的鍵值對類型組合。不清楚為什麼內建的數組實現不能優化這些極其普通情況,也不清楚它相對的性能怎樣。
有個ArrayObject類(實現了4個不同的接口),它包裝數組讓它看起來像對象。自定義類可以實現同樣的接口。但只有限的幾個方法,其中有一半不像內建的數組函數,而內建的數組函數不知道怎樣對ArrayObject或其它的類數組的類型操作。
函數
函數不是數據。閉包實際上是對象,但普通的函數不是。你甚至不能通過它們裸名稱引用它們;var_dump(strstr)會發出警告並猜測你的意思是字符串字面量,“strstr”。想辨別出字符串還是”函數”引用,沒門。
create_function基本上是個eval的包裝者。它用普通的名字創建函數並在全局范圍安裝它(因此永遠不會被垃圾回收—不要在循環中使用!)。它實際上對當前上下文一無所知,因為它不是閉包。名字包含一個NUL字節,因此永遠不會與普通函數沖突(因為如果在文件的任何地方有NUL的話,PHP的解析器會失敗)。
Declaringafunctionnamed__lambda_funcwillbreakcreate_function—theactualimplementationistoeval-createthefunctionnamed__lambda_func,theninternallyrenameittothebrokenname.If__lambda_funcalreadyexists,thefirstpa
rtwillthrowafatalerror.
其它
對NULL使用(++)生成1.對NULL用(–)生成NULL.
沒有生成器。
Web框架
執行環境
一個單一共享文件php.ini,控制了PHP的大部分功能並織入了復雜的針對覆蓋什麼與何時覆蓋的規則。PHP軟件能部署在任意的機器上,因此必須覆蓋一些設置使環境正常,這在很大程序上會違背像php.ini這樣的機制的使用。
PHP基本上以CGI運行。每次頁面被點擊,PHP在執行前,重編譯整個環境。就連Python的玩具框架的開發環境都不會這樣。
這就導致了整個“PHP加速器”市場的形成,僅僅編譯一次,就能加速PHP,就像其它的語言一樣。Zend,PHP的幕後公司,將這個做為它們的商業模式。
很長時間以來,PHP的錯誤默認輸出給客戶端—我猜是為開發環境提供幫助。我不認為這是真相,但我仍然看到偶爾會有mysql錯誤出現在頁面的頂部。
在標簽外的空白,甚至在庫中,PHP以文本對待並解析給響應(或者導致“headersalreadysent”錯誤)。一個流行的做法是忽略?>關閉標簽。
部署
部署方式常常被引述為PHP的最高級部分:直接部署文件就可以了。是的,這比需要啟動整個進程的Python或Rury或Perl要容易。但PHP留下了許多待改進的地方。
我很樂意以應用服務器的方式運行Web應用程序並反向代理它們。這樣的代價最小,而好處多多:你可以單獨管理服務器和應用程序,你可以按機器的多或少運行運行多個或少量應用進程,而不需要多個web服務器,你可以用不同的用戶運行應用,你可以選擇web服務器,你可以拆下應用而無需驚動web服務器,你可以無縫部署應用等等。將應用與web服務器直接焊接是荒謬的,沒有什麼好的理由支持你這麼做。
每個PHP應用程序都使用php.ini。但只有一個php.ini文件,它是全局的;如果你在一個共享的服務器上,需要修改它,或者如果你運行兩個應用需要不同的設置,你就不走運了;你不得不向組織申請所有必須的設置並放在應用程序,如使用ini_set或在Apache的配置文件或在.htaccess設置。如果你能做的話。可能wow,你有大量的地方需要檢查以找出怎樣獲取已設置的值。
類似的,“隔離”PHP應用的方法也不容易,它依賴於系統的其它部分。想運行兩個應用程序,想要不同的庫版本,或不同的PHP版本本身?開始構建另一人Apache的拷貝吧。
”一堆文件”方案,除了使路由像只病重的笨驢外,還意味著你不得不小心處理白名單或黑名單,以控制什麼東西可訪問,這是因為你的URL層次也就是你的代碼樹的層次。配置文件和其它的”局部模塊”需要C之類的東西守護以避免直接加載。版本控制系統的文件(如.svn)需要保護。使用mod_php,使得文件系統的所有東西都是潛在的入口;使用應用服務器,僅有一個入口,並且僅通過URL控制調用與否。
你不能無縫的升級那堆以CGI-style運行的文件,除非你想要應用崩潰和出現未定義行為,當用戶在升級的間歇期點擊你的站點時。
盡管配置Apache運行PHP很”簡單”,仍然會有一些陷阱。而PHP文檔建議使用SetHandler使得.php文件以PHP方式運行,AddHandler看起來運行良好,然而事實上會有問題。
當你使用AddHandler,你在告知Apache“以php執行它”,這是一個可能的處理.php文件的方式。但!Apache對文件的擴展名不這樣認為。它被設計為能支持如,index.html.en這樣的文件。對於Apache,文件可以同時具有任意數量的擴展名。
猜想,你有個文件上傳的表單,存儲一些文件到公共目錄中。確保沒人能上傳PHP文件,你僅僅檢查文件不能有.php擴展名。所有的攻擊需要做的只是上傳以foo.php.txt命名的文件;你的上傳工具不會看出問題,Apache會認為它是個PHP,它會很高興的執行。
這裡不是“使用原始文件名”或“沒有更好的驗證”導致的問題;問題是你的web服務器要被配置用來運行任何舊代碼,使得PHP“容易部署”。這不是理論上的問題;我已發現很多實際的站點有類似的問題了。
缺失的特性
我認為所有這些都是以構建一個Web應用為中心的。對PHP看起來很合理,是它的銷售賣點之一,它是“Web語言”,理應有它們。
無模塊系統。PHP就是模版。
無XSS過濾器。htmlspecialchars不是XSS過濾器。
無CSRF保護。你必須自己做。
無通用標准的數據庫API.像PDO這類東西不得不包裝每個特定數據庫的API,分別抽象不同部分。
無路由系統。你的站點結構就是你的文件系統結構。
無認證或授權。
無開發服務器。
無交互調試模式。
無一致的部署機制;僅僅”拷貝所有文件到服務器中”。
安全
語言邊界
PHP的蹩腳安全機制可能會放大,因為它利用某語言拿出數據,又把它轉存到另一個中。這是個壞注意。“