別再像2009年那樣寫PHP代碼了
離開在Facebook擔任工程師的僅僅2個月時間,我就很困惑,外面的世界看上去仍然像是在2009年的時候那樣寫 PHP。
貌似人們從來沒聽過 Hack、 HHVM、 XHP 等等,人們仍舊在代碼里大量使用 require() 和 include() 語句。簡直了。
我仍然認為 PHP 是一門寫前端應用的優秀語言(業務邏輯和 API 層),但只有當你應用了以下它的現代優勢時,這一說法才成立:
1. Hack
打出你的變量:
說實話,PHP 最大的問題是它缺乏強類型。 變量可以是任何類型,很多時候這就是一個定時炸彈。
如果你不得不寫這樣的代碼:
- if ($var !== null && is_int($var)) {
- //...
- }
這意味著你可以想引用一個null變量,或者錯誤的變量類型。
Hack 是向 PHP 漸漸添加類型信息的途徑,而且它是基于 PHP 的。
如果你添加了 hack 類型提示,它強制約束你的變量(包括把他們標記為可能為 null)。例如:
- class Foo {
- ?int $var = null; // ... some code ...
- }
可以用在方法簽名、類屬性等上面,接著它允許你通過 hh_client 檢查代碼里是否存在錯誤,存在就會把類型錯誤高亮出來。
Hack 文檔頁面有更多更好的對于 Hack 類型的解釋: https://docs.hhvm.com/hack/overview/typing
Async 異步
對于體面的 PHP 網站來說,下一個重要的跨越是使用 hack 的 async/await 關鍵詞。
如果你從未接觸過類似特性的語言,我來解釋一下。
比如講,你需要對數據庫做 3 次函數調用,為了獲取 3 塊數據。為了計算出頁面想要的結果,你需要所有 3 個查詢結果,但每個結果都需要 1 條不一樣的 SQL 語句。
一般你會這樣寫:
- $data1 = querySQL1();
- $data2 = querySQL2();
- $data3 = querySQL3();
- $result = computeResult($data1, $data2, $data3);
好,實際上,除非你在明確的做一些牛逼的東西,PHP 通常是在一個請求里面單線程跑的。 這意味著服務器會首先給第一條查詢執行一條 SQL,等待結果,然后再執行第二條 SQL,接著再執行第三條。
這有什么問題呢?這里的問題是,計算最終結果所需的時間是執行 query1、query2 和 query3 三者的時間之和。
但大多數數據庫都是多線程,且可以并行執行操作的。如果在此之上,你的 DB 在 SSD 上而不是在機械硬盤上執行,你就可以利用上 DB 的多核處理器和并行處理能力...
如果你在查詢多個 DB 或者多個不同的服務,或是請求多個 API,對你來說這一特性也可以發揮優勢。
我們怎么來解決呢? 使用 async/await:
- list($data1, $data2, $data3) = await\HH\Asio\v(array(
- querySQL1(),
- querySQL2(),
- querySQL3(),
- ));
是這種方式,3 條查詢一次性發送并等待結果。現在獲取 3 塊數據的時間就是執行耗時最長那條查詢的時間,因為 3 條都在并行處理。
Hack 使用圖表的方式更好地對 async 做了解釋:https://docs.hhvm.com/hack/async/introduction
Hack 提供了對 MySQL, memcache 和 Curl 的 async 實現,所以你可以只需用它們的庫替換掉你的調用就能立即利用到這一優勢。
Collections:
PHP 數據,有時候是一個向量,有時候是一個字典,有時候兩者都是。
即便你知道它里面包括什么,其他的工程師很可能認為自己也知道,但卻在里面放進了錯誤的數據類型。
如果你曾經使用過像 C#, Java 或 C++ 這樣的語言,你可能對 Generics 和 Collections 會感到熟悉。
Hack 引入了 Collections, 它讓你指定 Collections 里面的數據類型。 這意味著你只是盲目寄望于數組包含了你想要的值,現在你知道這一結構包括了你想要的數據類型(字符串、整型等等)。
在這之上,如果你仍舊想使用 PHP 的數組,你只需要對代碼做一點點重構,你就可以對數組內容的類型進行這樣的約束:
- class Bar { array
- $vector_of_ints = array();
- array
- $dictionary_with_string_keys = array();
- }
然后你只要在數組里放置了錯誤類型的變量,或者給數組指定一個字符串鍵,類型檢查器就會拋出錯誤。
2. HHVM
Hack 帶有它自己的運行環境,如你預料的,它無法直接運行于 Zend 的 PHP 環境。
HHVM 指 HipHop 虛擬機,是在 Facebook 開發的旨在極大改進 PHP 規模化的執行復雜度問題。
HHVM 運行了整個 Facebook 和一些其他主要站點,比如現在的維基百科,隨著時間推移,越來越證明它所帶來的許多性能收益。
由于 HHVM 無需 Hack 提示符也可以運行常規的 PHP,且同樣可以加速代碼執行效率,所以不使用 HHVM 作為你默認的 PHP 運行環境就是在浪費錢。
例如,當維基百科切換至 HHVM 后,平均單頁加載時間減少了超過一半,CPU 的平均使用率從 70% 減少至 12%,這還是在 2 年前。自那時起, HHVM 團隊持續提升其性能表現,所以你可以想象它現在表現更好了。
HHVM 在生產環境需要一個像 Apache 或 nginx 這樣的 HTTP 服務器作為前端支撐,但是在開發環境,它也可以獨立作為服務器運行。
3. XHP
如果有一件事是我憎惡的,就是 PHP/HTML 混編。這樣的代碼讓我吐:
- $user_name = 'Fred';
- $output = "Hello $user_name";
更早的是,有人自作聰明,不在一個地方開閉 HTML 標簽,像這樣:
- $user_name = 'Fred'; $output = "
- Hello $user_name"; // some call to a function that takes in $output and is supposed to close the div tag $output = addTheRestOfTheSoup($output);
于是你維護起來就...
XHP 讓 HTML 作為 PHP 的一級公民,因此你可以在字符串外編寫 HTML,像 XHP 一樣解析。
比如:
- $user_name ='Fred'; $output =
- Hello $user_name; addTheRestOfTheDivContentsTo($output);
- //...
- function addTheRestOfTheDivContentsTo(:div $div): :div { $div->appendChild("We come in peace"); return $div; }
如你所見, XHP 同樣強制標簽匹配,也就是說開標簽有相應的閉標簽,且以合適的順序進行開閉。
XHP 同樣處理字符串變量的 escape,避免 HTML/JS 進入頁面的用戶內容中,防御網站受到該攻擊矢量的攻擊。
你還可以為你自己創建自定義的 XHP 類,比如“自定義的HTML標簽”來復用你的代碼庫,比如實現可以自動在 Facebook 頁面添加鏈接的功能,甚至用一個標簽來渲染整個頁面頭部。
更多關于 XHP 的文檔:https://docs.hhvm.com/hack/XHP/introduction
還有更多 ...
以上介紹了 HHVM、Hack 和 XHP 的基礎,下次我希望介紹一下設置 HHVM 的開發環境,基于 HHVM 的類自動加載、函數和常量,還有基礎的控制器框架,路由 web 請求。