PHP 8的新增功能(功能,改進和JIT編譯器)

PHP 8預計將於2020年12月發布,它將為我們帶來很多強大的功能和出色的語言改進。

#js-mykinsta-video {
背景圖片:url(https://kinsta.com/wp-content/themes/kinsta/images/mykinsta-dashboard-v8@2x.jpg);
}

免費試用

儘管仍有一些提案正在開發中,但許多RFC已經得到批准和實施,因此現在是時候讓我們開始研究一些最令人興奮的補充,這些補充應使PHP更快,更可靠。

請注意,PHP 8仍在開發中,在最終版本發布之前我們可以看到一些變化。無論如何,我們會一直跟蹤這些更改並定期更新此帖子,因此請確保您不會錯過任何有關PHP 8的信息,並不時檢查一下此帖子。

那麼,我們期望PHP 8有哪些功能和改進? PHP 8(該語言的下一個主要版本)最大的特點是什麼?

讓我們潛入吧!

PHP JIT(及時編譯器)

PHP 8附帶的最受讚譽的功能是即時(JIT)編譯器。 JIT的全部意義是什麼?

RFC提案將JIT描述如下:

「 PHP JIT被實現為OPcache的幾乎獨立的部分。它可以在PHP編譯時和運行時啟用/禁用。啟用後,PHP文件的本機代碼存儲在OPcache共享內存的另一個區域中,並且op_array→o​​pcodes[].handler保留指向JIT版本代碼的入口點的指針。」

那麼,我們如何實現JIT?JIT與OPcache有何區別?

為了更好地了解什麼是JIT for PHP,讓我們快速看一下PHP如何從源代碼執行到最終結果。

PHP執行是一個4個階段的過程:

  • Lexing /令牌化:首先,解釋器讀取PHP代碼並構建一組令牌。
  • 解析:解釋器檢查腳本是否與語法規則匹配,並使用標記來構建抽象語法樹(AST),該語法是源代碼結構的分層表示。
  • 編譯:解釋器遍歷樹並將AST節點轉換為低級Zend操作碼,這些操作碼是確定Zend VM執行的指令類型的數字標識符。
  • 解釋:操作碼在Zend VM上解釋並運行。

下圖顯示了基本PHP執行過程的直觀表示。

基本的PHP執行過程

基本的PHP執行過程

那麼,OPcache如何使PHP更快? JIT執行過程中有哪些變化?

OPcache擴展

PHP是一種解釋型語言。這意味著,當運行PHP腳本時,解釋器將在每次請求時一遍又一遍地解析,編譯和執行代碼。這可能會浪費CPU資源和其他時間。

這是OPcache擴展發揮作用的地方:

「 OPcache通過將預編譯的腳本位元組碼存儲在共享內存中來提高PHP性能,從而消除了PHP在每個請求上載入和解析腳本的需要。」

啟用OPcache後,PHP解釋程序僅在腳本首次運行時才經過上述4個階段。由於PHP位元組碼存儲在共享內存中,因此它們可以立即作為低級中間表示形式使用,並且可以立即在Zend VM上執行。

啟用OPcache的PHP執行過程

啟用OPcache的PHP執行過程

從PHP 5.5開始,Zend OPcache擴展默認情況下可用,您可以通過從伺服器上的腳本中調用phpinfo()或檢出php.ini文件(請參閱OPcache配置設置)來檢查是否正確配置了它。

phpinfo頁面中的Zend OPcache部分

phpinfo頁面中的Zend OPcache部分

預裝

最近通過預載入的實現對OPcache進行了改進,預載入是PHP 7.4中新增的新OPcache功能。預載入提供了一種在「運行任何應用程序代碼之前」將一組指定的腳本存儲到OPcache內存中的方法,但是它不會為基於Web的典型應用程序帶來明顯的性能提升。

您可以在我們的PHP 7.4簡介中閱讀有關預載入的更多信息。

通過JIT,PHP向前邁了一步。

JIT —及時編譯器

即使操作碼採用低級中間表示形式,也仍然必須將其編譯為機器代碼。 JIT「沒有引入任何其他IR(中間表示)形式」,而是使用DynASM(用於代碼生成引擎的動態彙編器)直接從PHP位元組碼生成本機代碼。

簡而言之,JIT將中間代碼的熱門部分轉換為機器代碼。繞過編譯,它將能夠顯著提高性能和內存使用率。

PHP JIT提案的合著者Zeev Surasky顯示了使用JIT可以更快地進行多少計算:

但是,JIT是否可以有效地提高WordPress性能?

實時Web應用的JIT

根據JIT RFC,即時編譯器實現應提高PHP性能。但是,我們真的會在WordPress等現實應用中體驗到這種改進嗎?

早期測試表明,JIT可以使CPU密集型工作負載的運行速度大大提高,但是RFC警告:

「…像以前的嘗試一樣,它目前似乎並不能顯著改善WordPress等現實應用(opcache.jit = 1235 326 req / sec與315 req / sec)。

計劃通過性能分析和推測性優化來做出更大的努力,以改善實際應用的JIT。」

啟用JIT後,代碼將不會由Zend VM運行,而是由CPU本身運行,這將提高計算速度。諸如WordPress之類的Web應用程序還依賴於TTFB,資料庫優化,HTTP請求等其他因素。

因此,對於WordPress和類似的應用程序,我們不應該期望PHP的執行速度會大大提高。但是,JIT可以為開發人員帶來一些好處。

根據尼基塔·波波夫(Nikita Popov)的說法:

「 JIT編譯器的好處大致(如RFC中所述):

  • 數字代碼的性能明顯更好。
  • 「典型」 PHP Web應用程序代碼的性能略好。
  • 將更多代碼從C轉移到PHP的潛力,因為PHP現在已經足夠快了。」

因此,儘管JIT幾乎不會給WordPress性能帶來巨大的改善,但它將將PHP升級到一個新的水平,從而使它現在可以直接編寫許多功能的語言。

不過,不利的一面是更大的複雜性,它可能導致維護,穩定性和調試成本增加。根據Dmitry Stogov的說法:

「 JIT非常簡單,但無論如何,它會增加整個PHP複雜性的水平,增加新錯誤的風險以及開發和維護成本。」

將JIT納入PHP 8的提案以50票對2票獲得通過。

PHP 8改進和新功能

除了JIT之外,我們還可以期望PHP 8帶來許多功能和改進。以下是我們精選的即將到來的新增內容和更改,這些內容應使PHP更加可靠和高效。

驗證抽象特徵方法

特性被定義為「一種在單一繼承語言(例如PHP)中代碼重用的機制」。通常,它們用於聲明可在多個類中使用的方法。

特徵也可以包含抽象方法。這些方法只是聲明方法的簽名,但是該方法的實現必須在使用trait的類中完成。

根據PHP手冊,

「特質支持對抽象方法的使用,以便對展出的類強加要求。」

這也意味著方法的簽名必須匹配。換句話說,所需參數的類型和數量必須相同。

無論如何,根據RFC的作者Nikita Popov的說法,簽名驗證目前僅是強制實施的:

  • 在最常見的情況下,如果使用using類提供方法實現,則不強制實施:https://3v4l.org/SeVK3
  • 如果實現來自父類,則將強制實施:https://3v4l.org/4VCIp
  • 如果實現來自子類,則將強制實施:https://3v4l.org/q7Bq2

Nikita的以下示例涉及第一種情況(非強制籤名):

性狀T {
抽象公共功能測試(int $ x);
}
 
C級{
用T;

//允許,但不應由於類型無效而導致。
公共功能測試(字元串$ x){}
}

話雖如此,如果實現方法與抽象特徵方法不兼容,則無論其來源如何,該RFC都建議始終引發致命錯誤:

致命錯誤:第10行的/path/to/your/test.php中的C :: test(string $ x)聲明必須與T :: test(int $ x)兼容

該RFC已獲得一致批准。

使用WordPress,我們的流量增長了1,187%。
我們將向您展示如何。
加入20,000多個其他人,他們每周都會收到有關WordPress內部技巧的新聞!

          現在訂閱
        
        
          
            
            成功!感謝您的訂閱

您將在一周內收到下一期的Kinsta新聞通訊。

訂閱Kinsta新聞通訊
        
  
    
      
    
      
        訂閱
    
  
  
    

我同意條款和條件以及隱私政策

不兼容的方法簽名

在PHP中,由於方法簽名不兼容而導致的繼承錯誤會引發致命錯誤或警告,具體取決於導致錯誤的原因。

如果類正在實現介面,則不兼容的方法簽名將引發致命錯誤。根據對象介面文檔:

「實現介面的類必須使用與LSP(Liskov替換原理)兼容的方法簽名。不這樣做將導致致命錯誤。」

這是一個帶有介面的繼承錯誤的示例:

介面我{
公共函數方法(數組$ a);
}
C類實現了我{
公共函數方法(int $ a){}
}

在PHP 7.4中,上面的代碼將引發以下錯誤:

致命錯誤:C :: method(int $ a)的聲明必須與第7行/path/to/your/test.php中的I :: method(array $ a)兼容

子類中具有不兼容簽名的函數將引發警告。請參閱RFC中的以下代碼:

C1級{
公共函數方法(數組$ a){}
}
C2類擴展了C1 {
公共函數方法(int $ a){}
}

在PHP 7.4中,以上代碼將簡單地發出警告:

警告:C2 :: method(int $ a)的聲明應與第7行/path/to/your/test.php中的C1 :: method(array $ a)兼容

現在,該RFC建議始終為不兼容的方法簽名引發致命錯誤。使用PHP 8,我們在上文中看到的代碼將提示以下內容:

致命錯誤:C2 :: method(int $ a)的聲明必須與第7行/path/to/your/test.php中的C1 :: method(array $ a)兼容

以負索引開頭的數組

在PHP中,如果數組以負索引開頭(start_index <0),則以下索引將從0開始(有關array_fill文檔的更多信息)。看下面的例子:

$ a = array_fill(-5,4,真);
var_dump($ a);

在PHP 7.4中,結果如下:

數組(4){
[-5]=>
布爾值(true)
[0]=>
布爾值(true)
[1]=>
布爾值(true)
[2]=>
布爾值(true)
}

現在,該RFC建議進行更改,以使第二個索引為start_index + 1,無論start_index的值如何。

在PHP 8中,上面的代碼將導致以下數組:

數組(4){
[-5]=>
布爾值(true)
[-4]=>
布爾值(true)
[-3]=>
布爾值(true)
[-2]=>
布爾值(true)
}

使用PHP 8,以負索引開頭的數組會更改其行為。在RFC中閱讀有關向後不兼容的更多信息。

聯合類型2.0

聯合類型接受可以是不同類型的值。目前,除?Type語法和特殊的可迭代類型外,PHP不支持聯合類型。

在PHP 8之前,聯合類型只能在phpdoc批註中指定,如RFC中的以下示例所示:

班級編號{
/ **
* @var int | float $ number
* /
私人$ number;

/ **
* @param int | float $ number
* /
公共功能setNumber($ number){
$ this-> number = $ number;
}

/ **
* @return int | float
* /
公共函數getNumber(){
返回$ this-> number;
}
}

現在,聯合體類型2.0 RFC提議在函數簽名中增加對聯合體類型的支持,這樣我們就不再依賴內聯文檔,而是改為使用T1 | T2 | …語法定義聯合體類型:

班級編號{
private int | float $ number;

公共函數setNumber(int | float $ number):void {
$ this-> number = $ number;
}

公共函數getNumber():int | float {
返回$ this-> number;
}
}

正如Nikita Popov在RFC中所述,

「通過該語言支持聯合類型,我們可以將更多類型信息從phpdoc移到函數簽名中,這具有通常的優點:

  • 類型實際上是強制執行的,因此可以及早發現錯誤。
  • 因為它們是強制性的,所以類型信息不太可能變得過時或遺漏邊緣情況。
  • 在繼承過程中檢查類型,以執行Liskov替換原則。
  • 可通過反射獲得類型。
  • 語法比phpdoc少了很多。」

聯合類型支持所有可用類型,但有一些限制:

  • void類型不能成為並集的一部分,因為void意味著函數不返回任何值。
  • null類型僅在聯合類型中受支持,但不允許將其用作獨立類型。
  • 也可以使用可為空的類型表示法(?T),表示T | null,但不允許在聯合類型中包含?T表示法(不允許使用?T1 | T2,而應使用T1 | T2 | null) 。
  • 由於許多函數(例如strpos(),strstr(),substr()等)在可能的返回類型中包括false,因此也支持false偽類型。

您可以在RFC中閱讀有關聯合類型V2的更多信息。

內部函數的一致類型錯誤

傳遞非法類型的參數時,內部函數和用戶定義函數的行為會有所不同。

用戶定義的函數會引發TypeError,但內部函數會根據多種條件以多種方式運行。無論如何,典型的行為是發出警告並返回null。請參見PHP 7.4中的以下示例:

var_dump(strlen(new stdClass));

這將導致以下警告:

警告:strlen()期望參數1為字元串,第4行的/path/to/your/test.php中給出的對象
空值

如果啟用strict_types或參數信息指定類型,則行為將有所不同。在這種情況下,將檢測到類型錯誤並導致TypeError。

這種情況將導致許多問題,這些問題在RFC的問題部分中得到了很好的解釋。

為了消除這些不一致,此RFC建議使內部參數解析API在參數類型不匹配的情況下始終生成ThrowError。

在PHP 8中,上面的代碼引發以下錯誤:

致命錯誤:未被捕獲的TypeError:strlen():參數#1($ str)必須為字元串類型,對象在/path/to/your/test.php:4中給出
堆棧跟蹤:
#0 {main}
  在第4行的/path/to/your/test.php中拋出

拋出表情

在PHP中,throw是一條語句,因此無法在只允許使用表達式的地方使用它。

該RFC建議將throw語句轉換為表達式,以便可以在允許使用表達式的任何上下文中使用。例如,箭頭函數,空合併運算符,三元和貓王運算符等。

請參閱RFC中的以下示例:

$ callable = fn()=>拋出新的Exception();

// $ value是不可為空的。
$ value = $ nullableValue?拋出新的InvalidArgumentException();
 
// $ value是真實的。
$ value = $ falsableValue?:拋出新的InvalidArgumentException();

弱地圖

弱映射是弱引用鍵的數據(對象)的集合,這意味著不會阻止對它們的垃圾回收。

PHP 7.4增加了對弱引用的支持,這是一種保留對對象的引用的方式,這種引用不會阻止對象本身被銷毀。正如Nikita Popov所指出的,

「原始的弱引用本身僅具有有限的用途,而弱映射在實踐中更為常用。由於未提供註冊銷毀回調的功能,因此不可能在PHP弱引用之上實現有效的弱映射。」

因此,該RFC引入了WeakMap類來創建用作弱映射鍵的對象,如果沒有其他對鍵對象的引用,則可以將其從弱映射中刪除。

在長時間運行的進程中,這將防止內存泄漏並提高性能。請參閱RFC中的以下示例:

$ map =新的WeakMap;
$ obj =新的stdClass;
$地圖[$obj] = 42;
var_dump($ map);

使用PHP 8,以上代碼將產生以下結果(請參見此處的代碼):

object(WeakMap)#1(1){
[0]=>
array(2){
[“key”]=>
object(stdClass)#2(0){
}
[“value”]=>
整數(42)
}
}

如果您取消設置對象,則鍵會自動從弱貼圖中刪除:

unset($ obj);
var_dump($ map);

現在的結果如下:

object(WeakMap)#1(0){
}

要仔細查看弱映射,請參閱RFC。該提案獲得一致通過。

參數列表中的尾部逗號

尾隨逗號是附加到不同上下文中項目列表的逗號。 PHP 7.2在列表語法中引入了結尾逗號,PHP 7.3在函數調用中引入了結尾逗號。

是否需要為您的網站提供快速,安全且對開發人員友好的託管服務? Kinsta在構建時就考慮了WordPress開發人員,並提供了許多工具和功能強大的儀錶板。查看我們的計劃

PHP 8現在在參數列表中以函數,方法和閉包形式引入尾隨逗號,如以下示例所示:

Foo類{
公共功能__construct(
字元串$ x,
int $ y,
float $ z,//逗號結尾
){
// 做一點事
}
}

該提案以58票對1票獲得通過。

在對象上允許:: class語法

為了獲取一個類的名字,我們可以使用Foo Bar :: class語法。該RFC建議將相同的語法擴展到對象,以便現在可以獲取給定對象的類的名稱,如下例所示:

$ object =新的stdClass;
var_dump($ object :: class); //「 stdClass」
 
$ object = null;
var_dump($ object :: class); // TypeError

在PHP 8中,$ object :: class提供的結果與get_class($ object)相同。如果$ object不是對象,則拋出TypeError異常。

這項建議獲得一致通過。

屬性v2

屬性,也稱為注釋,是結構化元數據的一種形式,可用於指定對象,元素或文件的屬性。

在PHP 7.4之前,文檔注釋是將元數據添加到類,函數等的聲明的唯一方法。現在,Attributes v2 RFC引入了PHP的屬性,這些屬性將其定義為結構化的語法元數據的形式,可以將其添加到類,屬性,函數,方法,參數和常量。

將屬性添加到它們所引用的聲明之前。請參閱RFC中的以下示例:

<>
Foo類
{
<>
public const FOO =’foo’;

<>
公開$ x;

<>
公共功能foo(<> $ bar){}
}

$ object =新<> class(){};

<>
函數f1(){}

$ f2 = <>函數(){};

$ f3 = <> fn()=> 1;

可以在文檔塊注釋之前或之後添加屬性:

<>
/ ** docblock * /
<>
函數foo(){}

每個聲明可以具有一個或多個屬性,並且每個屬性可以具有一個或多個關聯值:

<>
<>
<>
函數foo(){}

請參閱RFC,以更深入地了解PHP屬性,用例和替代語法。請注意,屬性v2當前正在等待實施。

新的PHP函數

PHP 8為該語言帶來了幾個新功能:

str_contains

在PHP 8之前,strstr和strpos是開發人員在給定字元串中搜索針的典型選擇。問題是,這兩個功能並不是很直觀,新PHP開發人員對它們的使用可能會感到困惑。請參見以下示例:

$ mystring =’託管的WordPress託管’;
$ findme =「 WordPress」;
$ pos = strpos($ mystring,$ findme);

如果($ pos!== false){
回顯「已找到字元串」;
}其他{
回顯「未找到字元串」;
}

在上面的示例中,我們使用了!==比較運算符,該運算符還檢查兩個值是否屬於同一類型。如果針的位置為0,這可以防止我們出錯:

「此函數可能返回布爾FALSE,但也可能返回非布爾值,其值為FALSE。 […] 使用===運算符測試此函數的返回值。」

此外,一些框架提供了幫助程序功能來搜索給定字元串內的值(請參閱Laravel幫助程序文檔作為示例)。

現在,該RFC建議引入一個新功能,該功能允許在字元串內部進行搜索:str_contains。

str_contains(字元串$ haystack,字元串$ needle):bool

它的用法非常簡單。 str_contains檢查在$ haystack中是否找到$ needle,並相應返回true或false。

因此,感謝str_contains,我們可以編寫以下代碼:

$ mystring =’託管的WordPress託管’;
$ findme =「 WordPress」;

如果(str_contains($ mystring,$ findme)){
回顯「已找到字元串」;
}其他{
回顯「未找到字元串」;
}

這更易讀,更不容易出錯(請參見此處的代碼)。
在撰寫本文時,str_contains區分大小寫,但是將來可能會改變。

str_contains提案以43票對9票獲得通過。

str_starts_with()和str_ends_with()

除了str_contains函數之外,還有兩個新函數允許在給定字元串中搜索指針:str_starts_with和str_ends_with。

這些新函數檢查給定字元串是否以另一個字元串開頭或結尾:

str_starts_with(字元串$ haystack,字元串$ needle):bool
str_ends_with(字元串$ haystack,字元串$ needle):bool

如果$ needle比$ haystack長,則兩個函數均返回false。

根據該RFC的作者Will Hudgins所說,

「 str_starts_with和str_ends_with功能非常普遍,以至許多主要的PHP框架都支持它,包括Symfony,Laravel,Yii,FuelPHP和Phalcon。」

多虧了他們,我們現在可以避免使用次優且不太直觀的功能,例如substr,strpos。這兩個函數都區分大小寫:

$ str =「 WordPress」;
如果(str_starts_with($ str,「 Word」))回顯「 Found!」;

如果(str_starts_with($ str,「 word」))回顯「未找到!」;

您可以在此處查看此代碼的運行情況。

該RFC已以51到4票獲得批准。

get_debug_type

get_debug_type是一個新的PHP函數,它返回變數的類型。新函數的工作方式與gettype函數非常相似,但是get_debug_type返回本機類型名稱並解析類名稱。

這對語言是一個很好的改進,因為gettype()對類型檢查沒有用。

RFC提供了兩個有用的示例,以更好地理解新的get_debug_type()函數和gettype()之間的區別。第一個示例顯示了工作中的gettype:

$ bar = [1,2,3];

如果(!($ bar instanceof Foo)){
拋出新的TypeError(’Expected’。Foo :: class。’,got’。(is_object($ bar)?get_class($ bar):gettype($ bar)));
}

在PHP 8中,我們可以使用get_debug_type來代替:

如果(!($ bar instanceof Foo)){
拋出新的TypeError(’Expected’。Foo :: class。’got’。get_debug_type($ bar));
}

下表顯示了get_debug_type和gettype的返回值:

gettype() get_debug_type()
1個 整數 整型
0.1 浮動
真正 布爾值 布爾
布爾值 布爾
空值 空值 空值
「 WordPress」
[1,2,3] 數組 數組
名稱為「 Foo Bar」的類 賓語 Foo Bar
匿名班 賓語 class @ anonymous

其他RFC

在撰寫本文時,一些針對PHP 8的RFC仍在草擬和/或有待實施中。一旦它們的狀態更改為「已實施」,我們將立即添加它們。

這是PHP 8包含的其他已批准改進的快速列表:

  1. Stringable介面:此RFC引入了Stringable介面,該介面會自動添加到實現__to String()方法的類中。這裡的主要目標是使用string | Stringable聯合類型。
  2. ext / dom中的新DOM Living Standard API:此RFC建議通過引入新的介面和公共屬性,將當前的DOM Living Standard實現為PHP DOM擴展。
  3. 靜態返回類型:PHP 8在self和parent類型旁邊引入了static作為返回類型的用法。
  4. 變數語法調整:此RFC解決了PHP變數語法中的一些殘留不一致之處。

摘要

太好了!在本文中,我們介紹了PHP 8發行版中預期的所有關鍵更改和改進。其中最值得期待的肯定是Just in Time編譯器,但是PHP 8附帶了更多功能。

請確保將此博客帖子添加為書籤,因為我們將在收藏夾被批准後立即將其添加到列表中。 ?

現在輪到您了:您準備好測試即將推出的PHP功能嗎?哪一個是你的最愛?在下面的評論部分添加一行。

如果您喜歡這篇文章,那麼您會喜歡Kinsta的WordPress託管平台。加速您的網站並獲得我們經驗豐富的WordPress團隊的24/7支持。我們基於Google Cloud的基礎架構專註於自動擴展,性能和安全性。讓我們向您展示Kinsta的與眾不同!查看我們的計劃

相關文章