五大受損, 全面解析PHP的糟糕設(shè)計(jì)
當(dāng)前位置:點(diǎn)晴教程→知識(shí)管理交流
→『 技術(shù)文檔交流 』
譯者注:這篇文章很長,而且可能讀起來很亂,很難懂 前言我的脾氣古怪. 我會(huì)抱怨很多東西. 這個(gè)星球上大多數(shù)技術(shù)我都不喜歡. PHP不僅使用起來尷尬, 還有要嘛我想要的不適合, 要嘛不是最令人滿意, 要嘛違背我的信仰. 我可以告訴你關(guān)于一門語言, 所有我想避免的好方式, 所有我喜歡的壞方式. 來吧, 問吧! 談話會(huì)很有趣! php是唯一的例外. 幾乎php抽象的所有東西都是支離破碎的. 包括語言, 框架, 整個(gè)生態(tài)系統(tǒng)都一塌糊涂. 我?guī)缀醪荒軉为?dú)列出咒罵的事情, 因?yàn)樗矶級(jí)牧? 每次我打算編輯一堆雜亂如麻的php抱怨清單的時(shí)候, 我都被一些瑣事打亂, 越深入就越會(huì)發(fā)現(xiàn)其它令人震驚的事情. php讓人難堪. 它是如此的破碎, 但那些被培訓(xùn)的業(yè)余愛好者, 卻對(duì)它稱贊不已. php在做一些徽不足道的挽回措施, 但我選擇忘記它. 不過我得讓我的系統(tǒng)擺脫這些東西, 也就這樣了, 這是最后一次嘗試. 打個(gè)比喻我只是隨口和 Mel 抱怨下, 而她卻堅(jiān)決讓我發(fā)表出來. 我甚至說不出來PHP到底怎么了, 因?yàn)?-- 還好. 想想你有一個(gè), 嗯, 工具箱吧. 一堆工具. 看起來還好, 有標(biāo)準(zhǔn)的東西. 你拔除螺絲釘, 它怪異的有三個(gè)頭. OK, 好吧, 這對(duì)你不太有用, 但你猜遲早有天會(huì)有用. 你拿出榔頭, 被震住了, 兩邊都有是尖爪. 但它仍然能用, 我的意思是, 你可以用兩頭的中部斜著敲. 你拿出老虎鉗, 但它們沒有鋸齒面. 表面平而光滑. 這沒多大用, 但依然能用, 沒什么. 你可以繼續(xù). 工具箱的東西都是怪異和琢磨不定的, 但又不能說毫無價(jià)值. 整體看沒什么大問題; 它的工具都齊全. 現(xiàn)在, 想象有很多使用這些工具的木匠, 它們和你說:"這些工具有什么問題呢? 我們都用過, 它們工作都很好啊!". 工匠們給你展示他們建的房子,每個(gè)門都是五邊形的而屋頂是癲倒的. 你敲前門, 它向內(nèi)倒榻了, 而他們卻抱怨你打破了他們的門. 這就是PHP的問題. 立場(chǎng)我認(rèn)為下面的特質(zhì)對(duì)于一門語言的生產(chǎn)力和可用性是重要的, 而PHP在大范圍破壞它們. 如果你不同意這些, 好吧, 我無法想像, 我們永遠(yuǎn)不會(huì)達(dá)成一致. >> 一門語言必須是可預(yù)見的. 它是將人類的思想反映給計(jì)算機(jī)執(zhí)行的媒介, 因此它的關(guān)鍵是, 人類對(duì)程序的理解實(shí)際要正確. >> 語言必須一致. 相似的東西就要看起來相似, 不同的就是不同. 學(xué)習(xí)了語言的部分知識(shí), 就應(yīng)能很容易理解剩下的部分. >> 語言必須簡潔. 新語言應(yīng)該減少繼承舊語言的不好的形式. (我們也可以寫機(jī)器碼.) 新語言當(dāng)然應(yīng)努力避免織入新的特有的形式. >> 語言必須是可靠的. 語言是解決問題的工具; 應(yīng)盡量避免引入新問題. 任何"陷阱"都會(huì)大量的分散注意力. >> 語言必須是可調(diào)試的. 當(dāng)出錯(cuò)的時(shí)候, 程序員必須修正它, 我們需要獲得我們想要的幫助. 我的立場(chǎng)是: >> PHP到處處充滿驚奇: mysql_real_escape_string, E_ACTUALLY_ALL >> PHP不一致: strpos, str_rot13 >> PHP需要特別形式: error-checking around C API calls, === >> PHP古怪: ==. for($foo as &$bar) >> PHP晦澀: 默認(rèn)無棧跟蹤或fatals, 復(fù)雜的錯(cuò)誤報(bào)告 我不能就單個(gè)問題解釋為什么它歸為這些類, 否則將會(huì)沒完沒了. 我相信讀者自己會(huì)思考. 不要再和我扯這些東西了我知道很多有利的論點(diǎn). 我也聽到很多反駁的論點(diǎn). 這些都只能讓談話立即停止. 不要再跟我扯這些東西了, 求你了. :( >> 不要和我說"好的開發(fā)者能用任何語言寫出好的代碼", 或者壞開發(fā)者.. 吧啦吧啦. 這毫無意義. 好的工匠可以用石頭或錘子駕馭釘子, 但你見過有多少工匠用石頭的? 成為一個(gè)好開發(fā)者的標(biāo)準(zhǔn)之一就是善于選擇工具. >> 不要和我說熟記上千個(gè)例外和古怪行為是開發(fā)者的職責(zé). 是的, 這在任何系統(tǒng)中都是必要的, 因?yàn)殡娔X是傻的. 這不意味著, 系統(tǒng)能瘋狂的接受而沒有上限. PHP有的只是異常, 這是不行的, 一旦和語言摔角決斗, 你實(shí)際編寫程序就要花費(fèi)更多的努力. 我的工具不能為我創(chuàng)建應(yīng)用產(chǎn)生積極作用. >> 不要和我說 "那就是C API 的工作方式". 這星球上高級(jí)語言存在的目的是什么, 它們能提供的一切僅僅是一些字符串助手函數(shù)和一堆C的包裝器? 如果是這樣, 那就用C! 這里, 甚至還有為它準(zhǔn)備的CGI庫. >> 不要和我扯 "搞出奇怪的事, 是你活該". 如果存在兩個(gè)特性, 總有一天, 某些人會(huì)找到一起使用它們的理由. 再次強(qiáng)調(diào), 這不是C; 這里沒有規(guī)范, 這里不需要 "未定義行為". >> 不要再和我扯 Facebook 和 Wikipedia 就用的PHP. 我早知道了! 它們也能用 Brainfuck 寫, 但只要他們足夠陪明, 不斷折騰這些事情, 他們總能克服平臺(tái)的問題. 眾所周知, 如果使用其它語言編寫, 開發(fā)時(shí)間可能會(huì)減少一半或加倍; 單獨(dú)拿出這些數(shù)據(jù)毫無意義. 上帝保佑, 不要再和我扯任何東西了! 如果列出的沒有傷害你的PHP的觀點(diǎn), 無所謂, 因此請(qǐng)停止在網(wǎng)上做無意義的爭論, 繼續(xù)開發(fā)高帥富酷的站點(diǎn)來證明我是錯(cuò)的 :). 偷偷告訴你: 我非常喜歡Python. 我也很樂意對(duì)它說些你不愛聽的話, 如果你真想的話. 我并不要求它完美; 我只是想揚(yáng)長避短, 總結(jié)我想要的最佳東西. PHP語言核心CPAN被稱為 "Perl的標(biāo)準(zhǔn)庫". 這并沒有對(duì)Perl的標(biāo)準(zhǔn)庫做過多說明, 但它蘊(yùn)含了健壯的核心可以構(gòu)建強(qiáng)大的東西的思想. 基本原則PHP最初很明確的是為非程序員設(shè)計(jì)的(言外之意, 非專業(yè)程序); 根源已經(jīng)很難脫離. 從PHP 2.0 文檔中挑選出來的對(duì)話: 一旦你開始為每個(gè)類型區(qū)分不同的操作符, 你就開始使用語言變得復(fù)雜了. 例如, 你不能為strings使用 '==', 你現(xiàn)在必須用 'eq'. 我沒看出這點(diǎn)來, 特別是那些類似PHP的腳本語言, 它們大多數(shù)相當(dāng)簡單而多數(shù)情況下, 作為非程序員, 只想要一門包含少量基本邏輯語法的語言, 而不想付出過多學(xué)習(xí)曲線. >> PHP 為保持前進(jìn)不惜代價(jià). 什么都有比沒有好. >> 這不是個(gè)正確的設(shè)計(jì)原則. 早期的PHP受Perl影響; 大量的標(biāo)準(zhǔn)庫參考C使用 "out" 參數(shù); OO部分的設(shè)計(jì)像C++和Java. >> PHP從其它語言中引入大量的靈感, 但對(duì)那些熟知其它語言的人, 仍然難以理解. (int)看起來像 C, 但是 int 并不存在. 命名空間使用 \. 新的數(shù)組語法使用 [key => value], 不同于任何其它語言定義hash字面量的形式. >> 弱類型(例如, 默默的自動(dòng)在 strings/mumbers/等間轉(zhuǎn)換)是如此的復(fù)雜. >> 少量的新特性以新語法實(shí)現(xiàn); 大多數(shù)工作通過函數(shù)或者看起來像函數(shù)的東西完成. 除了類的支持, 這理所當(dāng)然的需要新的操作符和關(guān)鍵字. >> 本頁列出的問題都有官方解決方案 -- 如果你想資助 Zend 修復(fù)它們的開源編程語言的話. >> 路漫漫, 其修遠(yuǎn). 思考下面的代碼, 從PHP文檔的某地方挑出來的.
它將做什么? >> 如果PHP使用 --disable-url-fopen-wrapper編譯, 它將不工作. (文檔沒有說, "不工作"是什么意思; 返回 null, 拋出異常?) >> 注意這點(diǎn)已在 PHP 5.2.5 中移除. >> 如果 allow_url_fopen 在 php.ini 中禁用, 也將不工作. (為什么? 無從得知.) >> 由于 @ , non-existent file 的警告將不打印. >> 但如果在php.ini中設(shè)置了scream.enabled, 它又將打印. >> 或者如果用 ini_set 手動(dòng)設(shè)置 scream.enabled. >> 但, 如果 error_reporting 級(jí)別沒設(shè)置, 又不同. >> 如果打印出來了, 精確去向依賴于 display_errors , 再一次還是在 php.ini. 或者 ini_set中. 我無法告訴你這個(gè)函數(shù)調(diào)用的行為, 如果沒有查看編譯時(shí)標(biāo)志 , 服務(wù)器端配置, 和我的程序中的配置的話. 這些都是內(nèi)建行為. >> 該語言充滿了全局和隱似狀態(tài). mbstring 使用全局字符編碼. func_get_arg 之類的看起來像正常的函數(shù), 但是只對(duì)當(dāng)前正在執(zhí)行的函數(shù)操作. Error/exception 處理默認(rèn)是全局的. register_tick_function 設(shè)置了一個(gè)全局函數(shù)去運(yùn)行每個(gè) tick(鉤子?) ---- 什么?! >> 沒有任何線程支持. (不奇怪, 因?yàn)樯厦嬉呀o出.) 加之缺乏內(nèi)建的 fork (下面提到), 使得并行編程極其困難. >> PHP的某些部分在實(shí)踐中會(huì)產(chǎn)生錯(cuò)誤代碼. >> json_decode 對(duì)不正確的輸入返回 null, 盡管 null 也是一個(gè) JSON 解碼的合法對(duì)象 -- 該函數(shù)極不可靠, 除非你每次使用后都調(diào)用 json_last_error. >> 如果在位置0處找到, array_search , strpos, 和其它類似的函數(shù)返回0, 但如果都沒有找到的話. 會(huì)返回 false 讓我們稍稍展開最后一部分. 在C中, 函數(shù)如 strpos 返回 -1, 如果未找到. 如果你沒檢查這種情況, 卻試著以下標(biāo)使用它, 那將可能命中垃圾內(nèi)存, 程序會(huì)崩潰. (也許吧, 這是C. 誰泥馬知道. 我確定至少有工具處理它) 話說, Python中, 等效的 .index 方法將拋出一個(gè)異常, 如果元素沒找到的話. 如果你不檢查該情形, 程序?qū)⒈罎? 在PHP中, 該函數(shù)返回 false. 如果你把 FALSE 作為下標(biāo)使用, 或者用它做其他事情, PHP會(huì)默默的將它轉(zhuǎn)成0, 但除了用于 === 比較. 程序是不會(huì)崩潰的; 它將執(zhí)行錯(cuò)誤的邏輯, 且無任何警告, 除非你記得在每個(gè)使用 strpos 和其它類似函數(shù)的地方包含正確的樣版處理代碼. 這真是糟透了! 編程語言只是工具; 它們是為我服務(wù)的. 這里, PHP給我布下了陷阱, 等著我跳進(jìn)去, 而我不得不時(shí)刻警惕這些無聊的字符串操作和相等比較. PHP是個(gè)雷區(qū). 我已經(jīng)聽過很多關(guān)于PHP解析器的故事, 它的開發(fā)者來自世界各地. 有從事PHP核心開發(fā)工作的人, 有調(diào)試PHP核心的人, 也有和核心開發(fā)者交流過的人. 沒有一個(gè)故事是贊賞的. 因此不得不在這里插入一句, 因?yàn)樗档弥貜?fù): PHP是個(gè)業(yè)余愛好者的社區(qū). 極少數(shù)人設(shè)計(jì), 為它工作, 或極少有人知道他們?cè)谧鍪裁? (哦, 親愛的讀者, 你當(dāng)然是個(gè)極品例外!) 那些成長了, 想轉(zhuǎn)投其它平臺(tái)的人, 使整個(gè)社區(qū)的平均水平下降. 這個(gè), 就是這里, 是PHP的最大問題: 絕對(duì)的盲目領(lǐng)導(dǎo)盲目. 好了, 回來面對(duì)現(xiàn)實(shí)吧. 操作符== 不中用. >> "foo" == TRUE , 和 "foo" == 0... 但, 當(dāng)然 TRUE != 0. >> == 會(huì)將兩邊轉(zhuǎn)成數(shù)字, 如果可能的話, 這意味著它將轉(zhuǎn)成 floats 如果可能. 所以大的16進(jìn)制字符串(如, password hashes) 可能偶然會(huì)比較成 true , 盡管它們不一樣. 就連 JavaScript 都不會(huì)這樣做. >> 由于某些原因, "6" == "6", "4.2" == "4.20", 和 "133" == "0133". 但注意 133 != 0133, 因?yàn)?0133 是八進(jìn)制的. >> === 比較值和類型... 除了對(duì)象, 只有兩邊實(shí)際上是同一對(duì)象才為 true ! 對(duì)于對(duì)象, == 比較值(或每個(gè)屬性)和類型, 這又是 === 比較任何非對(duì)象類型的行為. 好玩嗎? 比較大小也好不到哪去. >> 甚至行為都不一致: NULL < -1, 而 NULL == 0. 排序也因此不確定; 它依賴于在排序中比較元素的算法的順序. >> 比較操作符嘗試排序數(shù)組, 以兩種不同的方式: 首先按長度, 然后按元素. 如果它們有相同數(shù)量的元素但不同的keys, 它們是不可比的. >> 對(duì)象比較比其它比較做得更多... 除了那些即不小于也不大于的對(duì)象. >> 為了類型更安全的 == 比較, 我們有 ===. 為了類型更安全的 < 比較, 我們有... 什么也沒有. "123" < "0124", 通常, 不管你怎么做. 類型轉(zhuǎn)換也無濟(jì)于事. >> 盡管上面的舉動(dòng)很瘋狂, 但卻明確拒絕Perl's的字符串 paris 和算術(shù)運(yùn)行符, PHP沒有重載 +. + 就是通常的 +, 而 . 是通常的連接符. >> [] 下標(biāo)操作符也可以拼寫成 {}. >> [] 可以用于任何變量, 不光是字符串和數(shù)組. 它返回 null , 無錯(cuò)誤警告. >> [] 僅能獲取單個(gè)元素. >> foo()[0] 是個(gè)語法錯(cuò)誤. (已在 PHP 5.4 中修復(fù)) >> 不像(從字面上看)任何其它語言都有的類似的操作符, ?: 是左結(jié)合的. 因此:
打印 horse. 變量>> 無法聲明變量. 當(dāng)?shù)谝淮问褂脮r(shí), 不存在的變量會(huì)被創(chuàng)建為 null 值. >> 全局變量在使用前, 需要 global 聲明. 這是根據(jù)上面得出的自然結(jié)果, 因此這是個(gè)完美的理由, 但, 如果沒有顯示的聲明, 全局變量甚至無法讀取 -- PHP 將悄悄的創(chuàng)建一個(gè)局部同名變量取代它. 我還沒見過其它語言使用類似的方法處理范圍問題. >> 沒有引用. PHP所謂的引用是個(gè)真正的別名; 這無疑是一種倒退, 不像 Perl 的引用, 也沒有像 Python 那樣的對(duì)象標(biāo)識(shí)傳遞. >> 沒有明顯的方式檢測(cè)和取消引用. >> "引用" 使變量在語言中與眾不同. PHP 是動(dòng)態(tài)類型的, 因此變量通常無類型... 除了引用, 它修飾函數(shù)定義, 變量語法, 和賦值. 一旦變量被引用(可在任何地方發(fā)生), 它就一直是個(gè)引用. 沒有明顯的方法探測(cè)和解引用需要的變量值. >> 好吧, 我說謊了. 有些"SPL types" 也作用于變量: $x = new SplBool(true); $x = "foo"; 將失敗. 這有點(diǎn)像靜態(tài)類型, 自己看看. >> A reference can be taken to a key that doesn’t exist within an undefined variable (which becomes an array). Using a non-existent array normally issues a notice, but this does not. >> 通過函數(shù)定義的常量稱為 taking a string; 這之前, 它們不存在. (這可能實(shí)際上是復(fù)制 Perl 使用常量的行為.) >> 變量名是大小寫敏感的. 函數(shù)和類名不是. 使得方法使用駝峰式命名會(huì)很奇怪. 結(jié)構(gòu)>> array() 和幾個(gè)類似的結(jié)構(gòu)不是函數(shù). $func = "array"; $func(); 不工作. >> 數(shù)組拆包可以使用 list($a,$b) = .... 操作完成. list() 是類函數(shù)語法, 就像數(shù)組那樣. 我不知道為什么不給一個(gè)真正的專用語法, 也不知道為什么名字如些的讓人迷惑. >> (int) 很顯然的被設(shè)計(jì)成類似C, 但它不是單獨(dú)的標(biāo)記; 在語言中, 沒有東西被稱為 int. 試試看: var_dump(int)不工作, 它會(huì)拋出一個(gè)解析錯(cuò)誤, 因?yàn)閰?shù)看起來像是強(qiáng)制轉(zhuǎn)操作符. >> (integer) 是 (int) 的別名. 也有 (bool)/(boolean)和(float)/(double)/(real). >> 有個(gè)(array)操作符用來轉(zhuǎn)成數(shù)組和 (object) 用來轉(zhuǎn)成對(duì)象. 這聽起來很貼心, 但常常有個(gè)用例: 你可以用 (array) 使得某個(gè)函數(shù)參數(shù), 既可以是單個(gè)元素,也可以是列表, 相同對(duì)待. 但這樣做不可靠, 因?yàn)槿绻橙藗鬟f了單個(gè)對(duì)象,把它轉(zhuǎn)換成數(shù)組將實(shí)際上生成了一個(gè)包含對(duì)象屬性的數(shù)組. (轉(zhuǎn)換成對(duì)象執(zhí)行了反轉(zhuǎn)操作.) >> include()這類的函數(shù)基本上就是C的#include: 他們將其它的文件源碼轉(zhuǎn)存到你的文件中. 沒有模塊系統(tǒng), 甚至對(duì) PHP 代碼也一樣. >> 沒有類似嵌套或者局部范圍的函數(shù)或類. 它們都是全局的. include 某文件, 它的變量導(dǎo)入到當(dāng)前函數(shù)范圍中(給了文件訪問你的變量的能力), 但是函數(shù)和類存入全局范圍中. >> 追加數(shù)組使用 $foo[] = $bar. >> echo 不是函數(shù). >> empty($var) 是如此極端, 對(duì)于任何其它東西不表現(xiàn)為函數(shù), 除了變量, e.g. empty($var || $var2), 是個(gè)解析錯(cuò)誤. 為什么地球上有這種東西, 解析器為什么需要了解 empty ? >> 還有些冗余的語法塊: if (...): ... endif;, 等等. 錯(cuò)誤處理>> PHP 的一個(gè)獨(dú)特操作符是 @ (實(shí)際上從DOS借用過來的), 它隱藏錯(cuò)誤. >> PHP 錯(cuò)誤不提供棧軌跡. 你不得不安裝一個(gè)處理器生成它們. (但 fatal errors不行 -- 見下文.) >> PHP 的解析錯(cuò)誤通常只拋出解析的狀態(tài), 沒其它東西了, 使得調(diào)試很糟糕. >> PHP 的解析器所指的例如. :: 內(nèi)部作為 T_PAAMAYIM_NEKUDOTAYIM, 而 << 操作符作為 T_SL. 我說 "內(nèi)部的", 但像上面說的, 給程序員顯示的 :: 或 << 出現(xiàn)在了錯(cuò)誤的位置. >> 大多數(shù)錯(cuò)誤處理打印給服務(wù)器日志打印一行錯(cuò)誤日志, 沒人看到而一直進(jìn)行. >> E_STRICT看起來像那么回事, 但它實(shí)際上沒多少保護(hù), 沒有文檔顯示它實(shí)際上是做什么的. >> E_ALL包含了所有的錯(cuò)誤類別 -- 除了 E_STRICT. >> 關(guān)于什么允許而什么不允許是古怪而不一致的. 我不知道 E_STRICT 是怎樣適用于這里的, 但這些卻是正確的: >> 試圖訪問不存在的對(duì)象屬性, 如, $foo->x. (warning) >> 使用變量做為函數(shù)名, 或者變量名, 或者類名. (silent) >> 試圖使用未定義常量. (notice) >> 試圖訪問非對(duì)象類型的屬性.(notice) >> 試圖使用不存在的變量名.(notice) >> 2 < "foo" (隱藏) >> foreach (2 as $foo); (warning) 而下面這些不行: >> 試圖訪問不存在的類常量, 如 $foo::x. (fatal error) >> 使用字符串常量作為函數(shù)名, 或變量名, 或類名. (parse error) >> 試圖調(diào)用一個(gè)示定義函數(shù). (fatal error) >> Leaving off a semicolon on the last statement in a block or file. (parse error) >> 使用 list 和其它準(zhǔn)內(nèi)建宏作為方法名. (parse error) >> 用下標(biāo)訪問函數(shù)的返回值, 如: foo()[0]. (parse error; 已在 5.4 中修復(fù)) 在列表的其他地方也有幾個(gè)關(guān)于其它怪異解析錯(cuò)誤的好例子 >> __toString 方法不能拋出異常. 如果你嘗試, PHP 將 ... 呃, 拋出一個(gè)異常. (實(shí)際上是個(gè) fatal error, 可以被通過的, 除了...) >> PHP 錯(cuò)誤和 PHP 異常是完全不同的物種. 它們不能相互作用. >> PHP 錯(cuò)誤 (內(nèi)部, 稱為 trigger_error)不能被 try/catch 捕獲. >> 同樣, 異常不能通過 set_error_handler 安裝的錯(cuò)誤處理器觸發(fā)錯(cuò)誤. >> 作為替代, 有一個(gè)單獨(dú)的 set_exception_handler 可以處理未捕獲的異常, 因?yàn)橛?try 塊包裝你程序入口在 mod_pho 模塊中是不可能的. >> Fatal 錯(cuò)誤 (例如, new ClassDoesntExist()) 不能被任何東西捕獲. 大量的完全無害的操作會(huì)拋出 fatal 錯(cuò)誤, 由 于一些有爭議的原因被迫終結(jié)你的程序. 關(guān)閉函數(shù)仍然運(yùn)行, 但它們無法獲取棧軌跡(它們運(yùn)行在上層), 它們很難告知該程序是由一個(gè)錯(cuò)誤還是程序的正常運(yùn)行結(jié)束. >> 沒有 finally 結(jié)構(gòu), 使得包裝代碼 (注冊(cè)處理器, 運(yùn)行代碼, 注銷處理器; monkeypatch, 運(yùn)行測(cè)試, unmonkeypatch) 很難看, 很難寫. 盡管 OO 和異常大量的復(fù)制了Java的模式, 這是故意的, 因?yàn)?finally "在PHP上下文中, 只得其形不得其神".Huh ? 函數(shù)>> 函數(shù)調(diào)用似乎相當(dāng)昂貴. >> 一些內(nèi)建函數(shù)與 reference-returning 函數(shù)交互, 呃, 一種奇怪的方式. >> 正如在別處提到的, 很多看起來像函數(shù)或者看起來它們應(yīng)該是函數(shù)的東西實(shí)際上是語言的構(gòu)成部分, 因此無法像正常函數(shù)一樣的工作. >> 函數(shù)參數(shù)可以具有 "類型提示", 基本上只是靜態(tài)類型. 你不能要求某個(gè)參數(shù)是 int 或是 string 或是 對(duì)象 或其它 "核心" 類型, 即使每個(gè)內(nèi)建函數(shù)使用這種類型, 可能因?yàn)?int 在PHP中不是個(gè)東西吧. (查看上面關(guān)于 (int) 的討論). 你也不能使用特殊的被大量內(nèi)建函數(shù)使用的偽類型裝飾: mixed, number, or callback. >> 因此, 下面:
產(chǎn)生錯(cuò)誤 the error: PHP Catchable fatal error: Argument 1 passed to foo() must be an instance of string, string given, called in... >> 你可能會(huì)注意到 "類型提示" 實(shí)際上并不存在; 在程序中沒有 string 類. 如果你試圖使用 ReflectionParameter::getClass() 動(dòng)態(tài)測(cè)試類型提示, 將會(huì)得到類型不存在, 使得實(shí)際上不可能取得該類型名. >> 函數(shù)的返回值不能被推斷 >> 將當(dāng)前函數(shù)的參數(shù)傳給另一個(gè)函數(shù) (分派, 不罕見) 通過 call_user_func_array('other_function', func_get_args())完成. 但 func_get_args 在運(yùn)行時(shí)拋出一個(gè) fatal 錯(cuò)誤, 抱怨它不能作為函數(shù)參數(shù). 為什么為什么這是個(gè)類型錯(cuò)誤? ( 已在 PHP 5.3 中修復(fù)) >> 閉包需要顯示的命名每個(gè)變量為 closed-over. 為什么解析器不想辦法解決? (Okay, it’s because using a variable ever, at all, creates it unless explicitly told otherwise.) >> Closed-over 變量, 通過和其它函數(shù)參數(shù)相同的語義"傳遞". 這樣的話, 數(shù)組和字符串等等, 將以傳值方式傳給閉包. 除非使用 &. >> 因?yàn)殚]包變量會(huì)自動(dòng)傳遞參數(shù), 沒有嵌套范圍, 閉包不能指向私有方法, 不管是否定義在類中. ( 可能在 5.4 中修復(fù)? 不清楚.) >> 函數(shù)沒有命名參數(shù). 實(shí)際上被 devs 顯示拒絕, 因?yàn)樗?"會(huì)導(dǎo)致代碼臭味". >> Function arguments with defaults can appear before function arguments without, even though the documentation points out that this is both weird and useless. (So why allow it?) >> 向函數(shù)傳遞額外的參數(shù)會(huì)被忽略 (除了內(nèi)建函數(shù), 會(huì)拋出異常). 丟失的參數(shù)被假定為 null. >> "可變" 函數(shù)需要 func_num_args, func_get_arg, 和 func_get_args. 這類事情沒有語法. OO>> PHP的函數(shù)部分被設(shè)計(jì)成類似C, 但面向?qū)ο?(ho ho) 被設(shè)計(jì)成類似 Java. 我不想過分強(qiáng)調(diào)這有多不合諧. 我還沒有發(fā)現(xiàn)一個(gè)有大寫字母的全局函數(shù), 重要的內(nèi)建類使用駝峰式方法命名, 并有g(shù)etFoo的Java風(fēng)格的屬性訪問器. 這是門動(dòng)態(tài)語言, 對(duì)嗎? Perl, Python, 和 Ruby 都有一些 通過代碼訪問"屬性"的概念; PHP 僅僅有笨重的 __get 之類的東西. 類型系統(tǒng)圍繞著低層的 Java語言設(shè)計(jì), Java 和PHP's處一時(shí)代, Java 有意的做了更多限制, 照搬Java, 我百思不得其解. >> 類不是對(duì)象. 元編程不得不通過字符串名指向它們, 就像函數(shù)一樣. >> 內(nèi)建的類型不是對(duì)象, (不像Perl) 也無法使得看起來像對(duì)象. >> instanceof 是個(gè)操作符, 盡管很晚才增加進(jìn)來, 而大多數(shù)語言都建有專門的函數(shù)和語法. 受Java影響嗎? 類不是第一類? (我不知道它們是不是.) >> 但有一個(gè) is_a 函數(shù). 它有個(gè)可選參數(shù)指定是否允許對(duì)象實(shí)際是一個(gè)字符串命名的類. >> get_class 是函數(shù); 沒有 typeof 操作符. 同樣有 is_subclass_of. >> 然而, 這對(duì)于內(nèi)建類型無法工作, (再一次, int 不是個(gè)東西). 這樣, 你需要 is_int 等等. >> 右值必須是變量或字面量; 不能是表達(dá)式. 不然會(huì)導(dǎo)致... 一個(gè)解析錯(cuò)誤. >> clone 是一個(gè)操作符?! >> OO 的設(shè)計(jì)是一只混合 Perl 和 Java 的怪物. >> 對(duì)象屬性通過 $obj->foo, 但類屬性是 $obj::foo. 我沒見過任何其它語言這樣做, 或者這樣做有什么用. >> 而, 實(shí)例方法仍然能通過靜態(tài)的(Class::method)調(diào)用. 如果從其它方法中這么調(diào)用, 會(huì)在當(dāng)前 $this 上被看成常規(guī)的方法調(diào)用. 我認(rèn)為吧. >> new, private, public, protected, static ,等等. 試圖虜獲 Java 開發(fā)者的芳心? 我知道這更多是個(gè)人的品位, 但我不知道為什么這些東西在一門動(dòng)態(tài)語言中是必要的 -- 在 C++ 中, 它們中的大多數(shù)是有關(guān)匯編和編譯時(shí)的命名決議. >> 子類不能覆蓋 private 方法. 子類覆蓋的公共方法也不可見, 單獨(dú)調(diào)用, 超類的私有方法. 會(huì)有問題, 如在測(cè)試mocks對(duì)象時(shí). >> 方法無法命名為, 例如 "list" , 因?yàn)?list() 是特殊的語法 (不是個(gè)函數(shù)) , 而解析器會(huì)被搞暈. 如此曖昧的原因無從得知, 而類工作得就很好. ($foo->list() 不是語法錯(cuò)誤.) >> 如果當(dāng)解析構(gòu)造函數(shù)參數(shù)時(shí)拋出異常(如, new Foo(bar()) 而 bar() 拋出), 構(gòu)造函數(shù)不會(huì)被調(diào)用, 但析構(gòu)函數(shù)會(huì). (已在PHP 5.3 中修復(fù)) >> 在 __autoload 和解析函數(shù)中的異常會(huì)導(dǎo)致 fatal 錯(cuò)誤. >> 沒有構(gòu)造器或析構(gòu)器. __construct 是個(gè)初始化函數(shù), 像 Python 的 __init__. 無法通過調(diào)用類申請(qǐng)內(nèi)存和創(chuàng)建對(duì)象. >> 沒有默認(rèn)的初始化函數(shù). 調(diào)用 parent::__construct()的時(shí)候, 如果父類沒定義它自己的 __construct 方法會(huì)導(dǎo)致 fatal 錯(cuò)誤. >> OO 帶來了個(gè)迭代器接口, 是語言規(guī)范的部分(如 ... as ...), 但該接口實(shí)際上沒有內(nèi)建實(shí)現(xiàn)(如數(shù)組) . 如果你想要個(gè)數(shù)組迭代器,你必須用 ArrayIterator 包裝它. 沒有內(nèi)建方式能夠讓迭代器將其作為第一類對(duì)像工作. >> 類可以重載它們轉(zhuǎn)化成字符串的方式, 但不能重載怎樣轉(zhuǎn)換成數(shù)字或任何其它內(nèi)建類型的方式. >> 字符串, 數(shù)字, 和數(shù)組都有字符串轉(zhuǎn)換方式; 語言很依賴于此. 函數(shù)和類都是字符串. 然而,如果沒定義 __toString , 試圖將換內(nèi)建或自定義對(duì)像(甚至于一個(gè)閉包) 轉(zhuǎn)換成字符串會(huì)導(dǎo)致錯(cuò)誤, 甚至連 echo 都可能出錯(cuò). >> 無法重載相等或比較操作. >> 實(shí)例方法中的靜態(tài)變量是全局的; 它們的值跨越該類的多個(gè)實(shí)例共享. 標(biāo)準(zhǔn)庫Perl "某些需要匯編". Python 是 "batteries included". PHP 是 "廚房水槽, 它來自加拿大, 但所有的水龍頭用C貼牌". 概括>> 沒有類型系統(tǒng). 你可以編譯PHP, 但必須通過 php.ini 指定要加載什么, 選項(xiàng)因擴(kuò)展部分存在(將它們的內(nèi)容注入到全局名稱空間中)或不存在. >> 因?yàn)槊Q空間是最近才有的特性, 標(biāo)準(zhǔn)庫一點(diǎn)沒被打亂. 在全局名稱空間中有上千個(gè)函數(shù). >> 庫的某些部分很不一致. >> 下劃線 對(duì) 無下劃線: strpos/str_rot13, php_uname/phpversion, base64_encode/urlencode, gettype/get_class >> “to” 對(duì) 2: ascii2ebcdic, bin2hex, deg2rad, strtolower, strtotime >> Object+verb 對(duì) verb+object: base64_decode, str_shuffle, var_dump versus create_function, recode_string >> 參數(shù)順序: array_filter($input, $callback) versus array_map($callback, $input), strpos($haystack, $needle) versus array_search($needle, $haystack) >> 前綴混亂: usleep vs microtime >> Case insensitive functions vary on where the i goes in the name. >> 大概一半的數(shù)組函數(shù)以 array_ 開頭. 剩下的不是. >> 廚房水槽. 庫包括: >> 綁定 ImageMagick, 綁定 GraphicsMagick (ImageMagick的派生), 少量的幾個(gè)函數(shù)能檢測(cè) EXIF 數(shù)據(jù) (其中ImageMagick已經(jīng)可以做到) >> 解析 bbcode 的函數(shù), 一些非常特殊的標(biāo)記, 被幾個(gè)少量的論壇包使用. >> 太多 XML 包. DOM (OO), DOM XML (not), libxml, SimpleXML, “XML Parser”, XMLReader/XMLWriter, 和一大砣我不能認(rèn)出的東西就省略了. 當(dāng)然會(huì)有些不同, 你可以自由的弄清晰它們的區(qū)別. >> 綁定了兩個(gè)特別的信用卡處理器, SPPLUS 和 MCVE. 什么? >> 三種訪問 MySQL 數(shù)據(jù)庫的方式: mysql, mysqli, 和 PDO 抽象的一些東西. C 影響它需要擁有的自己的符號(hào). PHP 是個(gè)高層的, 動(dòng)態(tài)類型的語言. 然后大量的標(biāo)準(zhǔn)庫的部分仍然只是圍繞 C APIS 的薄層封裝, 伴隨著下面的東西: >> "Out" 參數(shù), 盡管 PHP 可以返回 ad-hoc 哈?;蚝敛毁M(fèi)力的返回多參數(shù). >> 至少一打的函數(shù)是為了獲取某子系統(tǒng)的最近一次錯(cuò)誤(見下文), 盡管 PHP 已存存異常處理功能8年了. >> 有個(gè) mysql_real_escape_string, 盡管已有個(gè)具有相同參數(shù)的 mysql_escape_string, 僅僅因?yàn)樗?MySQL C API 的一部分. >> 全局行為卻是非全局功能的(如 MySQL). 使用多個(gè) MySQL 連接需要顯示的對(duì)每個(gè)函數(shù)調(diào)用傳遞連接句柄. >> 包裝器真的, 真的, 真的很薄. 例如, 調(diào)用了 dba_nextkey 而沒調(diào)用 dba_firstkey 將出現(xiàn)段錯(cuò)誤. >> 有一堆的 ctype_* 函數(shù) (如 ctype_alnum) 映射類似名稱的 C 字符函數(shù), 而不是如, isupper. Genericism如果函數(shù)相做兩件略有不同的事, PHP 就搞出兩個(gè)函數(shù). 你怎樣反向排序? 在 Perl 中, 你可以用 { $b <=> $a}. 在 Python 中, 你可能用 .sort(reverse = True). 在 PHP 中, 有個(gè)特別的函數(shù)叫 rsort(). >> 那些看起來像 C error 的函數(shù): curl_error, json_last_error, openssl_error_string, imap_errors, mysql_error, xml_get_error_code, bzerror, date_get_last_errors, 還有其它的嗎? >> 排序函數(shù): array_multisort, arsort, asort, ksort, krsort, natsort, natcasesort, sort, rsort, uasort, uksort, usort >> 文本檢索函數(shù): ereg, eregi, mb_ereg, mb_eregi, preg_match, strstr, strchr, stristr, strrchr, strpos, stripos, strrpos, strripos, mb_strpos, mb_strrpos, plus the variations that do replacements >> 有大量的別名: 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 返回一個(gè)當(dāng)前給出目錄的文件列表. 而不是(可能有益)按返回目錄順序返回, 函數(shù)返回一個(gè)已排序的文件列表. 有個(gè)可選的參數(shù)可以按字母逆順返回. 這些用于排序很顯然很不夠. >> str_split 將字符串拆成等長的塊. chunk_split 將字符串拆成等長的塊, 然后用個(gè)分隔符連接. >> 讀取壓縮文件需要一套單獨(dú)的函數(shù), 取決于格式. 有六套函數(shù), 它們的 API 都不同, 如 bzip2, LZF, phar, rar, zip, 和gzip/zlib >> 因?yàn)槭褂脜?shù)數(shù)組調(diào)用函數(shù)是如此的別扭(call_user_func_array), 所以有些配套的像 printf/vprintf 和 sprintf/vsprintf. 它們做相同的事, 但一個(gè)帶多個(gè)參數(shù), 另一個(gè)帶參數(shù)數(shù)組. 文本>> preg_replace 帶 /e (eval) 標(biāo)志的將用待替換的字符串替換匹配的部分, 然后 eval 它. >> strtok 的設(shè)計(jì)顯然是和 C 函數(shù)等效的, 由于很多原因, 已被認(rèn)為是個(gè)壞注意. PHP 可以輕易的返回一個(gè)數(shù)組(而這在C中別扭), 很多的hack strtok(3) 用法 (修改字符串某處), 在這里不能使用. >> parse_str 解析查詢字符串, 從函數(shù)名看不出任何跡象. 而它會(huì) register_globals 并轉(zhuǎn)存查詢字符串到本地范圍變量中, 除非你傳遞一個(gè)數(shù)組來填充. (當(dāng)然, 什么也不返回) >> 碰到空分隔符, explode 會(huì)拒絕分割. 每個(gè)其它的字符串拆分實(shí)現(xiàn)采取這種作法的意思應(yīng)該是把字符串應(yīng)拆分成字符; PHP有一個(gè)拆分函數(shù), 令人迷惑的稱為 str_split 而卻描述為 "將字符串轉(zhuǎn)成數(shù)組". >> 格式化日期, 有 strftime, 像 C API 處理本地語言環(huán)境一樣. 當(dāng)然也有 date, 完全不同的語法而僅用于 English. >> "gzgetss -- 獲取 gz 文件的行指針并去除 HTML 標(biāo)記." 知道了這一系列函數(shù)的概念, 讓我去死吧. >> mbstring>> 都是關(guān)于 "multi-byte", 解決字符集的問題. >> 仍然處理的是普通字符串. 有個(gè)單一的全局"默認(rèn)"的字符集. 一些函數(shù)允許指定字符集, 但它依賴于所有的參數(shù)和返回值. >> 提供了 ereg_* 函數(shù), 但這些都被廢棄了. preg_* 很幸運(yùn), 用一些 PCRE-specific 標(biāo)記, 它們能理解 UTF-8. 系統(tǒng)和反射>> 有一大堆的函數(shù), 聚焦于文本和變量. 壓縮和提取僅是冰山一角. >> 有幾種方式讓PHP動(dòng)態(tài), 咋一看沒有什么明顯的不同或相對(duì)好處. 類工具不能修改自定義類; 運(yùn)行時(shí)工具取代了它并能修改自定義的任何東西; Reflection* 類能反射語言的大部分東西; 有很多獨(dú)特的函數(shù)是為了報(bào)告函數(shù)和類的屬性的. 這些子系統(tǒng)是獨(dú)立, 相關(guān), 多余的嗎? >> get_class($obj) 返回對(duì)象的類名稱. get_class()返回被調(diào)用函數(shù)中的類的名稱. 撇開這些不說, 同一個(gè)函數(shù)會(huì)做完全不同的事情: get_class(null)... 行為象后者. 因此面對(duì)一個(gè)隨機(jī)的變量, 你不能信任它. 驚訝吧! >> stream_* 類允許實(shí)現(xiàn)自定義的流對(duì)象給fopen和其它的內(nèi)建的類似文件處理的東西使用. 由于幾個(gè)內(nèi)部原因, "通知" 不能被實(shí)現(xiàn). >> register_tick_function 能接受閉包對(duì)象. unregister_tick_function 不行; 相反, 它會(huì)拋出錯(cuò)誤, 抱怨閉包不能轉(zhuǎn)換成字符串. >> php_uname 告知你當(dāng)前操作系統(tǒng)相關(guān)東西. >> fork 和 exec 不是內(nèi)建的. 它們來自 pcntl 擴(kuò)展, 但默認(rèn)不包含. popen 不提供 pid 文件. >> session_decode 用于讀取任意的 PHP session 字符串, 但僅當(dāng)有個(gè)活躍的 session 時(shí)才工作. 它轉(zhuǎn)存結(jié)果到 $_SESSION 中, 而不是返回它的值. 雜項(xiàng)>> curl_multi_exec 不改變 curl_error 當(dāng)出錯(cuò)的時(shí)候, 但它改變 curl_error. >> mktime 的參數(shù)是有順序的: hour, minute, second, month, day, year 數(shù)據(jù)操縱程序什么都不是, 除了咀嚼和吐出數(shù)據(jù)以外. 大量的語言圍繞著數(shù)據(jù)操縱設(shè)計(jì), 從 awk 到 Prolog 到 C. 如果語言無法操縱數(shù)據(jù), 它就無法做任何事. 數(shù)字>> Integers 在32位平臺(tái)是是有符號(hào)32位數(shù). 不像PHP的同時(shí)代者, 沒有自動(dòng) bigint 提升. 因此你的數(shù)學(xué)運(yùn)算可能會(huì)由于CPU體系結(jié)構(gòu)結(jié)果不一樣. 你唯一選擇大整數(shù)的方式是使用 GMP 或 BC 包裝函數(shù). (開發(fā)者可能已經(jīng)建義加入新的, 單獨(dú)的,64位類型. 這真是瘋了.) >> PHP支持八進(jìn)制數(shù)語法, 以0開頭, 因此如 012 是10. 然而, 08變成了0. 8(或9)和任何接下來的數(shù)字消失了. 01c是個(gè)語法錯(cuò)誤. >> pi 是個(gè)函數(shù). 或者有個(gè)常量, M_PI. >> 沒有冪操作符, 只有 pow 函數(shù). 文本>> 無Unicode支持. 只有ASCII工作是可靠的, 真的. 有個(gè) mbstring 擴(kuò)展, 上面提過的, 但會(huì)稍被打擊. >> 這意味著使用內(nèi)建的string函數(shù)處理UTF-8文本會(huì)有風(fēng)險(xiǎn). >> 相似的, 在ASCII外, 也沒有什么大小寫比較概念. 盡管有擴(kuò)展版本的大小寫敏感的函數(shù), 但它們不會(huì)認(rèn)為 é 等于 É. >> 你不能在變量中內(nèi)插keys , 如, "$foo['key']"是個(gè)語法錯(cuò)誤. 你也不能 unquote it (這樣會(huì)產(chǎn)生警告, 無論什么地方!), 或使用 ${...}/{$...} >> "${foo[0]}"是對(duì)的. "${foo[0][0]}"是個(gè)語法錯(cuò)誤. 糟糕的拷貝類似 Perl 的語法 (兩個(gè)根本不同的語議)? 數(shù)組嘔, 騷年. >> 這家伙扮演list數(shù)據(jù)類型, 操作hash, 和排序set, 解析 list, 偶爾會(huì)有些奇怪的組合. 它是怎樣執(zhí)行的? 以何種方式使用內(nèi)存? 誰知道? 不喜歡, 反正我還有其它的選擇. >> => 不是操作符. 它是個(gè)特別的結(jié)構(gòu), 僅僅存在于 array(...) 和 foreach 結(jié)構(gòu)中. >> 負(fù)值索引不工作, 盡管 -1 也是個(gè)和0一樣的合法鍵值. >> 盡管這是語言級(jí)的數(shù)據(jù)結(jié)構(gòu), 但沒有簡短語法; array(...)是簡短語法. (PHP 5.4 帶來了"literals", [...].) >> => 結(jié)構(gòu)是基于 Perl , Perl允許 foo => 1 而不用引號(hào). 在PHP中, 你這么做會(huì)得到警告; 沒有無需引號(hào)創(chuàng)建 hash 字符串鍵值的方式. >> 數(shù)組處理函數(shù)常常讓人迷惑或有不確定行為, 因?yàn)樗鼈儾坏貌粚?duì) lists, hashes, 或可能兩者的結(jié)合體做運(yùn)算. 考慮 array 分組, "計(jì)算arrays的不同部分".
這段代碼將做什么? 如果 array_diff 將參數(shù)以 hashes 看待, 它們明顯是不同的; 相同的keys有不同的值. 如果以list看待, 它們?nèi)匀皇遣煌? 值的順序不同. 事實(shí)上 array_diff 認(rèn)為它們相等, 因?yàn)樗?sets 對(duì)待: 僅僅比較值, 忽略順序. >> 同樣, array_rand 隨機(jī)選擇keys時(shí), 也有奇怪的行為, 這對(duì)大多數(shù)需要從列表中挑出東西的用例沒什么幫助. 盡管大量PHP代碼依賴key的順序:
>> 如果兩個(gè)數(shù)組混合的話, 會(huì)發(fā)生什么? 我留給讀者自己弄清楚. (我不知道) >> array_fill 不能創(chuàng)建0長度的數(shù)組; 相反它會(huì)發(fā)出警告并返回 false. >> 所有的(很多的...) 排序函數(shù)就地操作而什么都不返回. 想新建一個(gè)已排序數(shù)組的拷貝, 沒門; 你不得不自己拷貝數(shù)組, 然后排序, 然后再使用數(shù)組. >> 但 array_reverse 返回一個(gè)新數(shù)組. >> 一堆被排序的東西和一些鍵值對(duì)聽起來像是個(gè)某種強(qiáng)大的處理函數(shù)參數(shù)的方式, 但, 沒門. 非數(shù)組>> 標(biāo)準(zhǔn)庫包含 "快速哈希", "特定的強(qiáng)類型"的hash結(jié)構(gòu)OO實(shí)現(xiàn). 然, 深入它, 有4類, 每種處理不同的鍵值對(duì)類型組合. 不清楚為什么內(nèi)建的數(shù)組實(shí)現(xiàn)不能優(yōu)化這些極其普通情況, 也不清楚它相對(duì)的性能怎樣. >> 有個(gè) ArrayObject 類 (實(shí)現(xiàn)了4個(gè)不同的接口) , 它包裝數(shù)組讓它看起來像對(duì)象. 自定義類可以實(shí)現(xiàn)同樣的接口. 但只有限的幾個(gè)方法, 其中有一半不像內(nèi)建的數(shù)組函數(shù), 而內(nèi)建的數(shù)組函數(shù)不知道怎樣對(duì)ArrayObject或其它的類數(shù)組的類型操作. 函數(shù)>> 函數(shù)不是數(shù)據(jù). 閉包實(shí)際上是對(duì)象, 但普通的函數(shù)不是. 你甚至不能通過它們裸名稱引用它們; var_dump(strstr) 會(huì)發(fā)出警告并猜測(cè)你的意思是字符串字面量, "strstr". 想辨別出字符串還是"函數(shù)"引用, 沒門. >> create_function 基本上是個(gè) eval 的包裝者. 它用普通的名字創(chuàng)建函數(shù)并在全局范圍安裝它(因此永遠(yuǎn)不會(huì)被垃圾回收---不要在循環(huán)中使用!). 它實(shí)際上對(duì)當(dāng)前上下文一無所知, 因?yàn)樗皇情]包. 名字包含一個(gè) NUL 字節(jié), 因此永遠(yuǎn)不會(huì)與普通函數(shù)沖突 (因?yàn)槿绻谖募娜魏蔚胤接?NUL的話, PHP 的解析器會(huì)失敗). >> Declaring a function named __lambda_func will break create_function—the actual implementation is to eval-create the function named __lambda_func, then internally rename it to the broken name. If __lambda_func already exists, the first part will throw a fatal error. 其它>> 對(duì) NULL 使用 (++) 生成 1. 對(duì) NULL 用 (--) 生成 NULL. >> 沒有生成器. Web 框架執(zhí)行環(huán)境>> 一個(gè)單一共享文件 php.ini, 控制了 PHP 的大部分功能并織入了復(fù)雜的針對(duì)覆蓋什么與何時(shí)覆蓋的規(guī)則. PHP軟件能部署在任意的機(jī)器上, 因此必須覆蓋一些設(shè)置使環(huán)境正常, 這在很大程序上會(huì)違背像 php.ini 這樣的機(jī)制的使用. >> PHP基本上以CGI運(yùn)行. 每次頁面被點(diǎn)擊, PHP 在執(zhí)行前, 重編譯整個(gè)環(huán)境. 就連 Python 的玩具框架的開發(fā)環(huán)境都不會(huì)這樣. >> 這就導(dǎo)致了整個(gè) "PHP 加速器" 市場(chǎng)的形成, 僅僅編譯一次, 就能加速PHP, 就像其它的語言一樣. Zend, PHP的幕后公司, 將這個(gè)做為它們的商業(yè)模式. >> 很長時(shí)間以來, PHP的錯(cuò)誤默認(rèn)輸出給客戶端 -- 我猜是為開發(fā)環(huán)境提供幫助. 我不認(rèn)為這是真相, 但我仍然看到偶爾會(huì)有mysql 錯(cuò)誤出現(xiàn)在頁面的頂部. >> 在 <?php ... ?>標(biāo)簽外的空白, 甚至在庫中, PHP以文本對(duì)待并解析給響應(yīng) (或者導(dǎo)致 "headers already sent" 錯(cuò)誤). 一個(gè)流行的做法是忽略 ?>關(guān)閉標(biāo)簽. 部署部署方式常常被引述為PHP的最高級(jí)部分: 直接部署文件就可以了. 是的, 這比需要啟動(dòng)整個(gè)進(jìn)程的 Python 或 Rury 或 Perl 要容易. 但 PHP 留下了許多待改進(jìn)的地方. 我很樂意以應(yīng)用服務(wù)器的方式運(yùn)行Web應(yīng)用程序并反向代理它們. 這樣的代價(jià)最小, 而好處多多: 你可以單獨(dú)管理服務(wù)器和應(yīng)用程序, 你可以按機(jī)器的多或少運(yùn)行運(yùn)行多個(gè)或少量應(yīng)用進(jìn)程, 而不需要多個(gè)web服務(wù)器,你可以用不同的用戶運(yùn)行應(yīng)用, 你可以選擇web服務(wù)器, 你可以拆下應(yīng)用而無需驚動(dòng)web服務(wù)器, 你可以無縫部署應(yīng)用等等. 將應(yīng)用與web服務(wù)器直接焊接是荒謬的, 沒有什么好的理由支持你這么做. >> 每個(gè) PHP 應(yīng)用程序都使用 php.ini . 但只有一個(gè) php.ini 文件, 它是全局的; 如果你在一個(gè)共享的服務(wù)器上, 需要修改它, 或者如果你運(yùn)行兩個(gè)應(yīng)用需要不同的設(shè)置, 你就不走運(yùn)了; 你不得不向組織申請(qǐng)所有必須的設(shè)置并放在應(yīng)用程序, 如使用 ini_set 或在 Apache 的配置文件或在 .htaccess設(shè)置. 如果你能做的話. 可能 wow , 你有大量的地方需要檢查以找出怎樣獲取已設(shè)置的值. >> 類似的, "隔離"PHP應(yīng)用的方法也不容易, 它依賴于系統(tǒng)的其它部分. 想運(yùn)行兩個(gè)應(yīng)用程序,想要不同的庫版本, 或不同的PHP版本本身? 開始構(gòu)建另一人Apache的拷貝吧. >> "一堆文件"方案, 除了使路由像只病重的笨驢外, 還意味著你不得不小心處理白名單或黑名單, 以控制什么東西可訪問, 這是因?yàn)槟愕?URL 層次也就是你的代碼樹的層次. 配置文件和其它的"局部模塊"需要C之類的東西守護(hù)以避免直接加載. 版本控制系統(tǒng)的文件(如 .svn) 需要保護(hù). 使用 mod_php , 使得文件系統(tǒng)的所有東西都是潛在的入口; 使用應(yīng)用服務(wù)器, 僅有一個(gè)入口, 并且僅通過 URL 控制調(diào)用與否. >> 你不能無縫的升級(jí)那堆以 CGI-style 運(yùn)行的文件, 除非你想要應(yīng)用崩潰和出現(xiàn)未定義行為, 當(dāng)用戶在升級(jí)的間歇期點(diǎn)擊你的站點(diǎn)時(shí). >> 盡管配置 Apache 運(yùn)行 PHP 很"簡單", 仍然會(huì)有一些陷阱. 而 PHP 文檔建議使用 SetHandler 使得 .php 文件以 PHP方式運(yùn)行, AddHandler 看起來運(yùn)行良好, 然而事實(shí)上會(huì)有問題. 當(dāng)你使用 AddHandler, 你在告知 Apache "以 php 執(zhí)行它" , 這是一個(gè)可能的處理 .php 文件的方式. 但! Apache 對(duì)文件的擴(kuò)展名不這樣認(rèn)為. 它被設(shè)計(jì)為能支持如, index.html.en 這樣的文件. 對(duì)于 Apache , 文件可以同時(shí)具有任意數(shù)量的擴(kuò)展名. 猜想, 你有個(gè)文件上傳的表單, 存儲(chǔ)一些文件到公共目錄中. 確保沒人能上傳 PHP 文件, 你僅僅檢查文件不能有.php 擴(kuò)展名. 所有的攻擊需要做的只是上傳以 foo.php.txt 命名的文件; 你的上傳工具不會(huì)看出問題, Apache 會(huì)認(rèn)為它是個(gè) PHP, 它會(huì)很高興的執(zhí)行. 這里不是 "使用原始文件名" 或 "沒有更好的驗(yàn)證"導(dǎo)致的問題; 問題是你的web服務(wù)器要被配置用來運(yùn)行任何舊代碼, 使得PHP "容易部署". 這不是理論上的問題; 我已發(fā)現(xiàn)很多實(shí)際的站點(diǎn)有類似的問題了. 缺失的特性我認(rèn)為所有這些都是以構(gòu)建一個(gè)Web應(yīng)用為中心的. 對(duì)PHP看起來很合理, 是它的銷售賣點(diǎn)之一, 它是 "Web語言", 理應(yīng)有它們. >> 無模塊系統(tǒng). PHP就是模版. >> 無 XSS 過濾器. htmlspecialchars 不是 XSS 過濾器. >> 無 CSRF 保護(hù). 你必須自己做. >> 無通用標(biāo)準(zhǔn)的數(shù)據(jù)庫API. 像PDO這類東西不得不包裝每個(gè)特定數(shù)據(jù)庫的API, 分別抽象不同部分. >> 無路由系統(tǒng). 你的站點(diǎn)結(jié)構(gòu)就是你的文件系統(tǒng)結(jié)構(gòu). >> 無認(rèn)證或授權(quán). >> 無開發(fā)服務(wù)器. >> 無交互調(diào)試模式. >> 無一致的部署機(jī)制; 僅僅"拷貝所有文件到服務(wù)器中". 安全語言邊界PHP的蹩腳安全機(jī)制可能會(huì)放大, 因?yàn)樗媚痴Z言拿出數(shù)據(jù), 又把它轉(zhuǎn)存到另一個(gè)中. 這是個(gè)壞注意. "<script>" 可能在SQL中意味著什么都不是, 但在HTML中就很是了. 讓情況更糟糕的是通常有人哇哇喊到 "你的輸入要消毒". 那完全錯(cuò)誤; 你不可能有什么魔法使塊數(shù)據(jù)完全"干靜". 你需要做的就是對(duì)語言說: SQL使用占位符, 進(jìn)程孵化使用參數(shù)列表, 等等. >> PHP公然鼓勵(lì) "消毒": 有個(gè)數(shù)據(jù)過濾擴(kuò)展可以做到. >> 所有的 addslashes, scripslashes, 和其它的 slashes相關(guān)的東西都是廢物, 毫無用處. >> 我只能告訴你這么多, 無法安全的孵化進(jìn)程. 你僅能通過shell執(zhí)行字符串. 你的選擇是瘋狂的轉(zhuǎn)義, 并希望默認(rèn)的shell使用正確的轉(zhuǎn)義, 或手動(dòng)的 pcntl_fork_exec 和 pcntl_exec. >> 所有的轉(zhuǎn)義命令和轉(zhuǎn)義參數(shù)存在大致相同的描述. 注意在Windows中, 轉(zhuǎn)義參數(shù)不工作 (因?yàn)樗僭O(shè)成 Bourne shell 語議), 轉(zhuǎn)義命令僅僅用空格替換一堆標(biāo)點(diǎn)符號(hào), 因?yàn)闆]人能搞清楚 Windows 命令轉(zhuǎn)義行為 (它可能默默的破壞你試圖做的任何事情). >> 原始的內(nèi)建 MySQL 綁定, 仍然廣泛使用, 它無法創(chuàng)建 prepared statements. 直到今天, PHP 文檔關(guān)于SQL注入的建議還是讓人抓狂的做如類型檢查, 使用sprintf 和 is_numeric, 在每個(gè)地方手動(dòng)的使用mysql_real_escape_string , 或在每處手動(dòng)使用 addslashes (這個(gè)"可能更有用"!) 這樣的實(shí)踐. 并沒有提到 PDO 或 參數(shù)化, 除了在用戶評(píng)論中有點(diǎn)線索. 至少在兩年以前, 我就有具體的向 PHP dev 抱怨過了 , 他被驚動(dòng)了, 而頁面卻從未變過. Insecure-by-default>> register_globals. 它被默認(rèn)關(guān)閉的,而在5.4中去除了. 我不在乎. >> include 接受 HTTL URLS. 和上面一樣. >> Magic quotes. So close to secure-by-default, and yet so far from understanding the concept at all. 核心PHP解釋器本身就有一些惱人的安全問題. >> 2007年的時(shí)候, 解析器有個(gè)整數(shù)溢出漏洞. 修復(fù)始于 if(size > INT_MAX) return NULL; 從那以后就走下坡路了. (對(duì)于那些不需要使用C的人: 曾經(jīng), INT_MAX 是適合變量最大整數(shù). 我希望你能從這里搞清楚其余的東西.) >> 最近, PHP 5.3.7 包括了個(gè) crypt() 函數(shù), 有個(gè)漏洞讓任何人可以用任何密碼登錄. >> PHP5.4是容易遭受拒絕服務(wù)攻擊,因?yàn)樗枰狢ontent-Length頭(任何人都可以設(shè)置),并試圖分配更多內(nèi)存。這是一個(gè)壞主意。 我可以挖掘更多, 但重點(diǎn)不是這有很多X漏洞 -- 是軟件就有bugs, 無論如何都有. 這些自然是令人咋舌. 我并沒有特意尋找這些; 但在過去的幾個(gè)月里, 它們自己送上門來了. 總結(jié)一些評(píng)論會(huì)理所當(dāng)然的指出我沒得出任何結(jié)論. 好吧, 我是沒有結(jié)論. 如果你一路看到了這里, 我假設(shè)一開始你就同意我了 :) 如果你僅了解PHP而對(duì)學(xué)習(xí)其它東西感興趣, 可以看看 Python 教程, 嘗試 Flask 這個(gè)為web準(zhǔn)備的家伙. (我不是它的模版語言的鐵桿粉絲, 但它確實(shí)很好的完成了這些工作.) 它將你的應(yīng)用分成多個(gè)部分, 但它們看起來仍然是一致的. 我可能稍后會(huì)寫個(gè)關(guān)于這個(gè)的貼子; 旋風(fēng)般的介紹整個(gè)語言和不同于這里所說的web堆棧. 之后或?qū)τ诟蟮捻?xiàng)目, 你可能需要 Pyramid, 一個(gè)中等規(guī)模的框架, 或者是 Django, 一個(gè)構(gòu)建站點(diǎn)的復(fù)雜的框架, 如 Django站點(diǎn). 英文原文 轉(zhuǎn)載請(qǐng)注明出處 OSCHINA.NET 該文章在 2012/4/18 22:07:41 編輯過 |
關(guān)鍵字查詢
相關(guān)文章
正在查詢... |