[譯]PHP7簡介:新特性與移除的特性
/** * 原文出處:https://www.toptal.com/php/php-7-performance-features * @author dogstar.huang 2016-03-13 */
在PHP世界裡,2015年最振奮人心的訊息之一是PHP 7的版本發布,距離上一次主要版本PHP 5長達10年之久。 向前邁進了巨大的一步後,PHP 7引入了大量的新特性以及效能升級。
然而,由於採用了一些相容性中斷,它把老的、棄用的功能也移除了,這使得老應用遷移到新版本更為困難。 如果你計劃把已有的應用移到PHP 7之上或者構建新應用的話,此篇指南應該作為期待什麼會發生的的快速嚮導。
但是等一等,PHP 6哪裡去了?
如果近期你沒在工作中一直使用PHP的話,你可能會疑惑PHP 6發生了什麼,為什麼會直接從PHP 5跳到PHP 7? 好吧,長話短說,PHP 6失敗了。由於PHP主要用於網站開發而網站需要Unicode,版本6的主要特性是對於 Unicode的本地支援,所以把Unicode帶到PHP這一舉動是有意義的。
對此的想法就是把對於Unicode的完整支援帶到本身的核心中去。這本來可以把擴充能力帶到語言,從使用滑稽的emoji 作為變數和函數名字的能力,到強大的國際化字元功能。例如,當另一個有別於英文的語言使用大寫和小寫字元時, 或者當一個中文字元的名字需要轉換成英語時。
PHP 6雄心勃勃,但很糟糕。這就是我們為什麼在這個過程以PHP 7來結束,直接跳過6的原因。
不幸的是,雄心勃勃的計劃證明是一個比預期還要大的問題。大部分的程式碼程式庫需要移植以便在核心和重要擴充中都能支援Unicode, 這證明是繁瑣而複雜的。這緩慢了語言中其他特性的開發進度,並在這個過程中使得很多PHP開發人員 感到沮喪。額外障礙的出現,讓開發本地Unicode支援的興趣大大減少,最終導致了這個項目被拋棄。
由於像書和文章這樣的資源已經為PHP 6和它的Unicode支援編著好了,為了避免混淆新的版本將會命名為PHP 7。
不管怎樣,往事不堪回首,讓我們來看下PHP 7帶來了什麼。
效能之戰,PHP 7 vs PHP 5
幾乎所有的更新,輕微的效能升級是可以預期的。然而,這一次PHP帶來了比以往早期的版本更重要的提升,使得純粹效能成為了PHP 7 最迷人的特性。這來自於“PHPNG”項目的一部分,這抓住了Zend Engine本身的內部結構。
通過重構內部資料結構和添加一個中間步驟到在抽象文法樹(AST)表單的代碼編譯,結果是優越的效能和更高效的記憶體配置。 數字本身看起來來非常讓人滿懷希望:在真實世界應用完成的基準測試表明PHP 7平均比PHP 5.6快兩倍,並且在請求期間減少了 50%的記憶體消耗,使得PHP 7成為了臉書 HHVM JIT 編譯器強勁的對手。可以看下來自Zend為一些通用CMS和架構 描繪的圖表資訊。
PHP 7看起來和感覺都熟悉,但是它為效能作了調整。精製的Zend引擎和得到的結果效能產生了巨大的變化。
隨著圍繞PHP構建微服務的機遇,記憶體消耗的減少也使得更小的機器可以更好處理請求。內部的變化,尤其是AST實現,也開啟了 可以把效能推向更遠的未來最佳化的可能性。一個新的,在內部的JIT實現正在為未來的版本而被考慮。
PHP 7文法糖
伴隨PHP 7而來的是新的文法特性。當沒有在擴充語言本身的功能時,他們提供了一個更好的,或者更容易的方式讓你的代碼更令人愉悅 地編寫以及看起來更暢心。
組匯入聲明
現在,我們可以把來源於相同命名空間的類組匯入聲明分組簡化成use一行。這應該能在一種意義豐富的方式或簡單地在檔案中節 省一些位元組協助對齊聲明。
use Framework\Module\Foo;use Framework\Module\Bar;use Framework\Module\Baz;
使用PHP 7我們可以使用:
use Framework\Module\{Foo, Bar, Baz};
或者,如果你更傾向多行的風格:
use Framework\Module{ Foo, Bar, Baz};
Null合并運算子
這解決了在PHP編程中一個通用的問題,就是我們想把一個變數的值賦給另一個變數時,如果後者實際上有設定的話,否則為它提供一個不同的值。 通常用於我們處理使用者提供的輸入時。
在PHP 7前:
if (isset($foo)) { $bar = $foo;} else { $bar = 'default'; // we would give $bar the value 'default' if $foo is NULL}
PHP 7後:
$bar = $foo ?? 'default';
也可以這樣串聯多個變數:
$bar = $foo ?? $baz ?? 'default';
飛船操作符
飛船操作符<=>允許有三種方式來比較兩個值,不僅意味著如果他們相等,還包括哪一個更大,通過返回1,0或-1的不等式。
我們可以根據值的不同做出不同的動作:
switch ($bar <=> $foo) { case 0: echo '$bar and $foo are equal'; case -1: echo '$foo is bigger'; case 1: echo '$bar is bigger';}
對比的值可以是整型int,浮點數float,字串string甚至數組array。關於不同的值是如何相互對比的更多資訊請查看文檔:https://wiki.php.net/rfc/combined-comparison-operator
PHP 7中的新特性
當然PHP 7也帶來了新的令人興奮的功能。
標量參數類型 & 傳回型別提示
通過添加這四個標量類型:;整型(int),浮點數(float),布爾值(bool)和字串(string)和儘可能的參數類型, PHP 7擴充了先前在方法裡(類,介面和數組)的參數型別宣告。
更進一步,我們可以可選地指定方法和函數返回哪些類型。支援的類型有 bool,int,float,string,array,callable, 類名或介面名,self和parent(針對類方法) 。
class Calculator{ // We declare that the parameters provided are of type integer public function addTwoInts(int $x, int $y): int { return $x + $y; // We also explicitly say that this method will return an integer }}
型別宣告有助於構建更強壯的應用並且避免從函數傳遞或返回錯誤的值。其他好外包括靜態程式碼分析和當缺少文檔塊時提供更好的程式碼程式庫提示的IDE。
既然PHP是一個弱類型語言,參數和傳回型別的值基於上下文投射。如果在一個函數中傳遞的值“3”並且聲明參數類型為int,解譯器會把它作為 一個整數來接收而不會拋出任何錯誤。如果你不想要這點,可以通過添加一個declare指示開啟strict mode。
declare(strict_types=1);
這設定在每個檔案的基礎上,作為一個全域的選項劃分程式碼程式庫為和全域嚴格開啟進行構建和全域嚴格開關關閉時構建,導致當我們從兩者合并代碼時出 現了非期望的行為。
引擎異常
通過添加的引擎異常,由指令碼終端導致的致命錯誤可以輕鬆捕捉並處理。
例如調用一個不存在的方法的錯誤不會終止指令碼,取而代之的是他們會拋出一個可以被try catch塊處理的異常,這點提高了你應用的錯誤處理。 這對於諸如應用、服務和進程是重要的因為致命錯誤需要他們重新啟動。在PHPUNit的測試也會變得更可用因為致命錯誤之前會丟掉整個測試套件。 異常,而不是錯誤,會在每一個測試案例基礎上處理。
PHP 7看起來和感覺都熟悉,但是它為效能作了調整。精製的Zend引擎和得到的結果效能產生了巨大的變化。
PHP 7根據可能觸發的錯誤類型添加了若干新的異常類。為了維護版本之間的相容性,添加了一個新的介面Throwable,可以被引擎異常和使用者異 常這兩者實現。為了避免引擎異常擴充基礎異常類這是有需要的,導致更老的代碼捕捉當時還沒有的異常。
在PHP 7前這樣會以一個致命錯誤終止指令碼:
try { thisFunctionDoesNotEvenExist();} catch (\EngineException $e) { // Clean things up and log error echo $e->getMessage();}
匿名類
匿名類是匿名函數的表兄弟,你可以在一個簡短的執行個體中使用。匿名類建立簡單並且像一個正常的對象那樣使用即可。以下是來自文檔的一個樣本。
PHP 7前
class MyLogger { public function log($msg) { print_r($msg . "\n"); }}$pusher->setLogger( new MyLogger() );
使用匿名類:
$pusher->setLogger(new class { public function log($msg) { print_r($msg . "\n"); }});
匿名類在單元測試中很有用,特別在類比測試對象和服務時。這有利於我們通過建立一個簡單的對象並提供我們想要類比的介面來避免重量級的類比庫和架構。
CSPRNG函數
添加了兩個產生加密安全字串和整數的新函數。
random_bytes(int $len);
返回一個長度為$len的隨機字串。
random_int(int $min, int $max);
返回一個$min和$max之間的數字。
Unicode代碼點轉義文法
在PHP 7之前,不像其他很多語言,PHP沒有一種逸出字元語中Unicode代碼點的方式。這個功能添加了序列\u以產生使用他們UTF-8代碼點的字元。 這樣相比直接插入字元更好,使得更好處理不可見的字元和有相同圖表展示但意義不同的字元。
echo "\u{1F602}"; // outputs ??
注意這會使用\u斷開已有的代碼因為它改變了行為。
產生器升級
在PHP的產生器也得到了一些優雅額外的特性。現在,產生器有一個可用於跟在迭代後允許輸出一個最終值的返回聲明。這點可用於檢測產生器已經執行且沒有錯誤 並且允許調用產生的程式碼處理大量適當的情境。
再進一步,產生器可以從其他產生器返回和yield運算式。這使得他們可以把複雜的操作劃分為更簡單和模組化的單元。
function genA() { yield 2; yield 3; yield 4;}function genB() { yield 1; yield from genA(); // 'genA' gets called here and iterated over yield 5; return 'success'; // This is a final result we can check later}foreach (genB() as $val) { echo "\n $val"; // This will output values 1 to 5 in order}$genB()->getReturn(); // This should return 'success' when there are no errors.
期望
期望是在維護向後相容性時對assert()的一個增強。他們允許在生產代碼中零消耗的斷言,並提供當宣告失敗時拋出自訂異常的能力,這一點在開發中很有協助。
assert()成為了PHP 7中的一個語言構造。 斷言應該只在開發與測試環境中用於調試目的。為了配置它的行為,提供了兩個新的標題給我們。
-
- 1:產生並執行代碼(開發模式)(預設值)
- 0:產生代碼但在運行時圍繞它跳轉
- -1:不產生代碼,零消耗(生產模式)
-
- 1:當宣告失敗時拋出,通過拋出作為異常而提供的對象或者如果異常未提供時拋出一個新的 AssertionError 對象
- 0:如上面描述一樣使用或者產生一個 Throwable ,但只產生一個基於那個對象的警告而不是拋出它(相容PHP 5)
準備好把PHP 5移到PHP 7
這次主發布的介紹帶來了改變/升級老功能的一個機遇,或者甚至如果被認為過於古老或者已廢棄有一段時間的話就移除他們。這樣的改變會在老的應用引入相容性的中斷。
另外從這次版本跳躍而引發的一個問題是你所依賴的重要類庫和架構可能還沒升級到支援最新的版本。PHP團隊已嘗試著把這些新的改變儘可能向後相容並且 讓遷移到新版本儘可能少點痛苦。更新和更持續更新的應用可以發現轉移到新版本更容易,而老的應用則不得不決定是否收益大於成本,並且可能選擇不作升級。
大部分中斷都是輕微的並且可以輕鬆遷移,而另外一些可能要更大的努力的更多的時間。基本上來講,如果在安裝PHP 7前在你的應用中警告,很可能會有中斷應用直到修複的錯誤。 你有被警告過了,對嗎?
老的SAPI和擴充
最重要地,諸如mysql擴充(但你應該不會在第一個地方使用這個,是嗎?)這樣老的和棄用的SAPI已被移除。完整的擴充和特性移除可以在這裡 和這裡查看這個RFC。
此外,其他的SAPI正在移植到PHP 7。
從PHP 7起很多老的SAPI和擴充已被丟棄。我們在想他們應該不會有人想起。
統一變數文法
這個更新為變數-變數結構的一致性做了一些修改。這允許了更先進帶變數的運算式但也在其他一些情境中引入了變化,如下所示。
// old meaning // new meaning$$foo['bar']['baz'] ${$foo['bar']['baz']} ($$foo)['bar']['baz']$foo->$bar['baz'] $foo->{$bar['baz']} ($foo->$bar)['baz']$foo->$bar['baz']() $foo->{$bar['baz']}() ($foo->$bar)['baz']()Foo::$bar['baz']() Foo::{$bar['baz']}() (Foo::$bar)['baz']()
這會中斷應用訪問像這些變數的行為。另一方面,你可以做一些像這樣奇妙的東西:
// Nested ()foo()(); // Calls the return of foo()$foo->bar()();// IIFE syntax like JavaScript(function() { // Function body})();// Nested ::$foo::$bar::$baz
老風格標籤的移除
<% ... %>,<%= ... %>,這類開/關標籤已被移除並不再有效。用有效替換這些很容易,但是你用這些來幹嘛,怪咖?
類、介面和特徵的無效名稱
由於諸如參數和傳回型別這樣的附加項,類,介面和特徵不再允許有以下名稱:
- bool
- int
- float
- string
- null
- true
- false
對於用到他們的已有應用和類庫會引起中斷,但修複很容易。同樣,儘管不會引起任何錯誤並且是有效,以下這些也不應該使用因為他們為將來使用而保留:
- resource
- object
- mixed
- numeric
克制使用他們能讓你免於在未來再作調整。
對於會中斷相容的改變的完全列表,請查看這份文檔。
你也可以使用php7cc,它會檢查你的代碼並可以發現如果你轉稱到PHP 7可能會出來的任何潛在的問題。 但當然,沒有比安裝PHP 7並且自己親自看一下更好的方式了。
潛在的PHP 7相容性問題
PHP 7基礎結構相容性
大量託管的服務已開始著手添加對PHP 7的支援。這對於共用託管供應商是一個好訊息,因為所得的效能允許他們在現有硬體提高網站客戶數量, 降低運營費用,提高利潤率。而對於用戶端本身,他們期望在這樣的條件下不應該有太大的提高,但出於公平而,無論如何共用託管不是一個面向效能的選擇。
另一方面,提供虛擬私人服務商或專用服務商的服務會從這次效能跳躍中收益頗豐。一些支援PHP 7的PaaS服務如Heroku早期已上線,但其他的服務,像AWS Beanstalk 和Oracle’s OpenShift,正在滯後。檢查你的PaaS供應商網站看下是否PHP 7已經被支援,或者是否即將支援。
當然,IaaS允許你掌控硬體和安裝PHP 7(或者你喜歡也可以進行編譯)。對於主流IaaS環境的PHP 7的包已經可用。
PHP 7軟體相容性
除了基礎結構相容性,你還需要留意潛在的軟體相容性問題。流行的內容管理系統(CMS)如WordPress,Joomla和Drupal已經在他們最新的版本呂添加了對PHP 7的支援。 主流的架構如Symfony和Laravel也已完善支援。
然而,是時候提到謹慎這個詞了。此支援沒有以附加組件,外掛程式,包,或者不管你的CMS和架構如何調用他們的形式擴充到第三方代碼。他們也許會在相容性問題中 飽受折磨並且確保所有東西都已為PHP 7準備好是你的責任。
PHP的未來
PHP 7的發布移除了舊的、過時的代碼並且為未來新特性和效能升級鋪好了道路。所以,PHP有望很快得到更多的效能最佳化。儘管在過去發布中有一些相容性中斷, 大部分的問題都是很容易解決的。
類庫和架構現在正遷移到他們的代碼到PHP 7所以請擷取最新的版本。我鼓勵你去試一下並親自看下結果。也許你的應該已經相容並且等待使用,以及從PHP 7中受益。
參考:Before Debugging PHP That’s Not Working, Consult This List of the 10 Most Common Mistakes That PHP Developers Make