PHP7新特性介紹

PHP7-New-Features

本文檔只介紹PHP7相關的新特性以及功能修改等, 對PHP7的性能和源碼結構不做分析.

新增功能

常用的語法糖

  • 合併比較運算符: <=>
    // PHP 7之前的寫法:比較兩個數的大小
    function order_func($a, $b) {
        return ($a < $b) ? -1 : (($a > $b) ? 1 : 0);
    }
    // PHP新增的操作符 <=>,perfect
    function order_func($a, $b) {
        return $a <=> $b;
    }
  • 運算符: ??
    $a = $_GET['a'] ?? 1;
    //它相當於:
    $a = isset($_GET['a']) ? $_GET['a'] : 1;
  • Unicode字符格式支持(\u{xxxxx})
    // 打印unicode
    echo "\u{1F602}"; 
    echo "\u{0000aa}";
    echo "\u{9999}";
  • 命名空間引用
    // PHP7以前語法的寫法
    use FooLibrary\Bar\Baz\ClassA;
    use FooLibrary\Bar\Baz\ClassB;
    use FooLibrary\Bar\Baz\ClassC;
    use FooLibrary\Bar\Baz\ClassD as Fizbo;
    // PHP7新語法寫法
    use FooLibrary\Bar\Baz\{ ClassA, ClassB, ClassC, ClassD as Fizbo };
  • 聲明數組常量
   define('ANIMALS', [
       'dog',
       'cat',
       'bird'
   ]);
   echo ANIMALS[1]; // 輸出: "cat"
  • 匿名類
   interface Logger {
       public function log(string $msg);
   }
   class Application {
       private $logger;
       public function getLogger(): Logger {
           return $this->logger;
       }
       public function setLogger(Logger $logger) {
           $this->logger = $logger;
       }
   }
   $app = new Application;
   $app->setLogger(new class implements Logger {
       public function log(string $msg) {
           echo $msg;
       }
   });
   var_dump($app->getLogger());
  • Closure::call()
    class A {private $x = 1;}
    // PHP 7 之前
    $getXCB = function() {return $this->x;};
    $getX = $getXCB->bindTo(new A, 'A'); // intermediate closure
    echo $getX();
    // PHP 7 之後
    $getX = function() {return $this->x;};
    echo $getX->call(new A);
  • 迭代器
    // 例子1
    function g() {
        yield 1;
        yield from [2, 3, 4];
        yield 5;
    }
    $g = g();
    foreach ($g as $yielded) {
        var_dump($yielded);
    }
    // 例子2
    $gen = (function() {
        yield 1;
        yield 2;
        return 3;
    })();
    foreach ($gen as $val) {
        echo $val, PHP_EOL;
    }
    echo $gen->getReturn(), PHP_EOL;
    // 例子3
    function gen() {
        yield 1;
        yield 2;
        yield from gen2();
    }
    function gen2() {
        yield 3;
        yield 4;
    }
    foreach (gen() as $val) {
        echo $val, PHP_EOL;
    }

標量類型和返回類型聲明

PHP語言一個非常重要的特點就是"弱類型", 它讓PHP的程序變得非常容易編寫, 新手接觸PHP能夠快速上手, 不過, 它也伴隨着一些爭議.

支持變量類型的定義, 可以說是革新性質的變化, PHP開始以可選的方式支持類型定義.

除此之外, 還引入了一個開關指令declare(strict_type=1); 當這個指令一旦開啓, 將會強制當前文件下的程序遵循嚴格的函數傳參類型和返回類型.

// 可以在程序中這樣寫, 但這還是會做隱式轉化
function add(int $a, int $b): int {
    return $a + $b;
}
// 下面不會再做隱式轉化
declare(strict_types=1);
function add(int $a, int $b): int {
    return $a + $b;
}
var_dump(add(1, 2));
var_dump(add(1.5, 2.6));
// 如果沒有開啓declare(strict_types=1); 那麼第二條打印語句會打印浮點型數據. 

如果不開啓strict_type, PHP將會嘗試幫你轉換成要求的類型, 而開啓之後, 會改變PHP就不再做類型轉換, 類型不匹配就會拋出錯誤.

然而, PHP7有了這個玩意並不代表就是強類型了, 只是在傳參和返回值上不允許隱式類型轉化了. 在使用內置函數的時候還是會轉化, 並且在參數聲明的時候也不是強類型:

declare(strict_types=1);
// 這裏不會報錯
$name = 'kevin';
$name = 18; 

參考: http://hansionxu.blog.163.com/blog/static/241698109201522451148440/

錯誤處理機制修改

  1. 現在有兩個異常類: Exception and Error 
    這兩個類都實現了一個新的接口: Throwable.

  2. 更多的Error變爲Exception, 一些致命錯誤和可恢復致命錯誤改爲拋出Error對象 
    有一些致命錯誤和可恢復致命錯誤現在改爲報出Error對象. Error對象是和Exception獨立的,它們無法被常規的try/catch撲獲. 
    對於這些已經轉爲異常的可恢復致命錯誤, 已經無法通過error handler靜默的忽略掉. 尤其是無法忽略類型暗示錯誤. 
    PHP7進一步方便開發者處理, 讓開發者對程序的掌控能力更強. 因爲在默認情況下, Error會直接導致程序中斷, 而PHP7則提供捕獲並且處理的能力, 讓程序繼續執行下去, 爲程序員提供更靈活的選擇.

  3. 語法錯誤會拋出一個ParseError對象 
    語法錯誤會拋出一個ParseError對象, 該對象繼承自Error對象. 之前處理eval()的時候, 對於潛在可能錯誤的代碼除了檢查返回值或者error_get_last()之外, 還應該捕獲ParseError對象.

  4. 內部對象的構造方法如果失敗的時候總會拋出異常 
    內部對象的構造方法如果失敗的時候總會報出異常. 之前的有一些構造方法會返回NULL或者一個無法使用的對象.

  5. 一些E_STRICT錯誤的級別調整了

  6. 參考資料 
    https://wiki.php.net/rfc/engine_exceptions_for_php7 
    https://wiki.php.net/rfc/throwable-interface 
    https://wiki.php.net/rfc/internal_constructor_behaviour 
    https://wiki.php.net/rfc/reclassify_e_strict

AST

AST: Abstract Syntax Tree, 抽象語法樹

AST在PHP編譯過程作爲一箇中間件的角色, 替換原來直接從解釋器吐出opcode的方式, 讓解釋器(parser)和編譯器(compliler)解耦, 可以減少一些Hack代碼, 同時, 讓實現更容易理解和可維護.

PHP5 : PHP代碼 -> Parser語法解析 -> OPCODE -> 執行
PHP7 : PHP代碼 -> Parser語法解析 -> AST -> OPCODE -> 執行

參考: https://wiki.php.net/rfc/abstract_syntax_tree

Native TLS

Native Thread local storage, 原生線程本地存儲

PHP在多線程模式下(例如, Web服務器Apache的woker和event模式, 就是多線程), 需要解決"線程安全"的問題, 因爲線程是共享進程的內存空間的, 所以每個線程本身需要通過某種方式構建私有的空間來保存自己的私有數據, 避免和其他線程相互污染.

而PHP5採用的方式, 就是維護一個全局大數組, 爲每一個線程分配一份獨立的存儲空間, 線程通過各自擁有的key值來訪問這個全局數據組.

而這個獨有的key值在PHP5中需要傳遞給每一個需要用到全局變量的函數, PHP7認爲這種傳遞的方式並不友好, 並且存在一些問題. 因而, 嘗試採用一個全局的線程特定變量來保存這個key值.

參考: https://wiki.php.net/rfc/native-tls

字符串處理機制修改

含有十六進制字符的字符串不再視爲數字 
含有十六進制字符的字符串不再視爲數字, 也不再區別對待. 比如下面的代碼: 

var_dump("0x123" == "291");     // bool(false)     (previously true)
var_dump(is_numeric("0x123"));  // bool(false)     (previously true)
var_dump("0xe" + "0x1");        // int(0)          (previously 16)
var_dump(substr("foo", "0x1")); // string(3) "foo" (previously "oo")
// Notice: A non well formed numeric value encountered


可以使用filter_var函數來檢查一個字符串是否包含十六進制字符或者是否可以轉成一個整型 

$str = "0xffff";
$int = filter_var($str, FILTER_VALIDATE_INT, FILTER_FLAG_ALLOW_HEX);
if (false === $int) {
    throw new Exception("Invalid integer!");
}
var_dump($int); // int(65535)

\u{後面如果包含非法字符會報錯 
雙引號和heredocs語法裏面增加了unicode 碼點轉義語法, “\u{”後面必須是utf-8字符. 如果是非utf-8字符, 會報錯: 

$str = "\u{xyz}"; // Fatal error: Invalid UTF-8 codepoint escape sequence
// 可以通過對第一個\進行轉義來避免這種錯誤
 $str = "\\u{xyz}"; // Works fine
// “\u”後面如果沒有{, 則沒有影響:
 $str = "\u202e"; // Works fine

參考資料: 
https://wiki.php.net/rfc/remove_hex_support_in_numeric_strings 
https://wiki.php.net/rfc/unicode_escape

整型處理機制修改

Int64支持, 統一不同平臺下的整型長度, 字符串和文件上傳都支持大於2GB. 64位PHP7字符串長度可以超過2^31次方字節.

無效八進制數字會報編譯錯誤 
無效的八進制數字(包含大於7的數字)會報編譯錯誤, 比如下面的代碼會報錯: 

$i = 0781; // 8 is not a valid octal digit!
// 老版本的PHP會把無效的數字忽略

位移負的位置會產生異常 

 var_dump(1 >> -1);
 // ArithmeticError: Bit shift by negative number

左位移如果超出位數返回0 

var_dump(1 << 64); // int(0)
// 老版本的PHP運行結果和cpu架構有關係. 比如x86會返回1

右位移超出會返回0或者-1 

var_dump(1 >> 64);  // int(0)
var_dump(-1 >> 64); // int(-1)

參考 
https://wiki.php.net/rfc/integer_semantics

參數處理機制修改

重複參數命名不再支持 

// 重複的參數命名不再支持, 比如下面的代碼執行的時候會報錯:
public function foo($a, $b, $unused, $unused) {
          // ...
}

func_get_arg和func_get_args()調整 

// func_get_arg()和func_get_args()這兩個方法返回參數當前的值, 而不是傳入時的值, 當前的值有可能會被修改   
function foo($x) 
{
    $x++;
    var_dump(func_get_arg(0));
}
foo(1);
//上面的代碼會打印2,而不是1. 如果想打印原始的值, 調用的順序調整下即可

同樣在打印異常回溯信息的時候也是顯示修改後的值 

function foo($x) 
{
    $x = 42;
    throw new Exception;
}
foo("string");
// PHP7的運行結果:
Stack trace:
#0 file.php(4): foo(42)
#1 {main}
// PHP5的運行結果:
Stack trace:
#0 file.php(4): foo('string')
#1 {main}


這個調整不會影響代碼的行爲, 不過在調試的時候需要注意這個變化. 其他和參數有關的函數都是同樣的調整,比如debug_backtrace().

參考: 
https://wiki.php.net/phpng

foreach修改

foreach()循環對數組內部指針不再起作用 

$array = [0, 1, 2];
foreach ($array as &$val) 
{
    var_dump(current($array));
}


PHP7運行的結果會打印三次int(0), 也就是說數組的內部指針並沒有改變. 
之前運行的結果會打印int(1), int(2)和bool(false) 

按照值進行循環的時候, foreach是對該數組的拷貝操作 
foreach按照值進行循環的時候(by-value), foreach是對該數組的一個拷貝進行操作. 這樣在循環過程中對數組做的修改是不會影響循環行爲的. 

$array = [0, 1, 2];
$ref =& $array; // Necessary to trigger the old behavior
foreach ($array as $val) {
    var_dump($val);
    unset($array[1]);
}
//上面的代碼雖然在循環中把數組的第二個元素unset掉,但PHP7還是會把三個元素打印出來:(0 1 2)
//之前老版本的PHP會把1跳過, 只打印(0 2).

按照引用進行循環的時候, 對數組的修改會影響循環 

如果在循環的時候是引用的方式, 對數組的修改會影響循環行爲. 不過PHP7版本優化了很多場景下面位置的維護. 比如在循環時往數組中追加元素.

$array = [0];
foreach ($array as &$val) {
    var_dump($val);
    $array[1] = 1;
}
// 上面的代碼中追加的元素也會參與循環,這樣PHP7會打印"int(0) int(1)",老版本只會打印"int(0)"

對簡單對象plain (non-Traversable)的循環 
對簡單對象的循環, 不管是按照值循環還是按引用循環, 和按照引用對數組循環的行爲是一樣的. 不過對位置的管理會更加精確. 
對迭代對象(Traversable objects)對象行爲和之前一致

參考 
https://wiki.php.net/rfc/php7_foreach

list()修改

list()不再按照相反的順序賦值 

list($array[], $array[], $array[]) = [1, 2, 3];
var_dump($array);
// 上面的代碼會返回一個數組:$array == [1, 2, 3] 而不是之前的 [3, 2, 1]
// 注意: 只是賦值的順序發生變化, 賦的值還是和原來一樣的
list($a, $b, $c) = [1, 2, 3];
 // $a = 1; $b = 2; $c = 3; 
// 和原來的行爲還是一樣的

空的list()賦值不再允許 

list() = $a;
list(,,) = $a;
list($x, list(), $y) = $a;
// 上面的這些代碼運行起來會報錯了

list()不在支持字符串拆分功能 

$string = "xy";
list($x, $y) = $string;
//這段代碼最終的結果是: $x == null and $y == null (不會有提示)
//PHP5運行的結果是: $x == "x" and $y == "y".

除此之外, list()現在也適用於數組對象 

list($a, $b) = (object) new ArrayObject([0, 1]);
// PHP7結果: $a == 0 and $b == 1.
// PHP5結果: $a == null and $b == null.

參考資料 
https://wiki.php.net/rfc/abstract_syntax_tree#changes_to_list 
https://wiki.php.net/rfc/fix_list_behavior_inconsistency

變量處理機制修改

間接變量, 屬性和方法引用都按照從左到右的順序進行解釋: 

 $$foo['bar']['baz'] // interpreted as ($$foo)['bar']['baz']
 $foo->$bar['baz']   // interpreted as ($foo->$bar)['baz']
 $foo->$bar['baz']() // interpreted as ($foo->$bar)['baz']()
 Foo::$bar['baz']()  // interpreted as (Foo::$bar)['baz']()


如果想改變解釋的順序, 可以使用大括號: 

${$foo['bar']['baz']}
$foo->{$bar['baz']}
$foo->{$bar['baz']}()
Foo::{$bar['baz']}()

global關鍵字現在只能引用簡單變量 

global $$foo->bar;    // 這種寫法不支持
global ${$foo->bar};  // 需用大括號來達到效果

用括號把變量或者函數括起來沒有用了 

function getArray() { return [1, 2, 3]; }
$last = array_pop(getArray());
// Strict Standards: Only variables should be passed by reference
$last = array_pop((getArray()));
// Strict Standards: Only variables should be passed by reference
// 注意第二句的調用, 是用圓括號包了起來, 但還是報這個嚴格錯誤. 之前版本的PHP是不會報這個錯誤的.

引用賦值時自動創建的數組元素或者對象屬性順序和以前不同了 

$array = [];
$array["a"] =& $array["b"];
$array["b"] = 1;
var_dump($array);
// PHP7產生的數組: ["a" => 1, "b" => 1]
// PHP5產生的數組: ["b" => 1, "a" => 1]

參考資料 
https://wiki.php.net/rfc/uniform_variable_syntax 
https://wiki.php.net/rfc/abstract_syntax_tree

其他語言層面的修改

在非兼容$this語境中以靜態方式調用非靜態方法將不再支持 
在非兼容$this語境中以靜態方式調用非靜態方法將不再支持. 在這種場景下面, $this不會被定義, 但調用還可以調用, 但會有一個警告提示:

class A {
  public function test() { var_dump($this); }
}
// Note: Does NOT extend A
class B {
  public function callNonStaticMethodOfA() { A::test(); }
}
(new B)->callNonStaticMethodOfA();
// Deprecated: Non-static method A::test() should not be called statically
// Notice: Undefined variable $this
NULL
// 注意這種情況適用於在非兼容語境中調用. 上面代碼的例子中class B和class A沒有關係, 所以調用的時候$this是沒有定義的.
// 但如果class B是從class A繼承的話,該調用是合法的.

下面的這些保留字不能用作類名, 接口名和trait名.

bool
int
float
string
null
false
true


下面這些關鍵字已經被留作將來使用, 目前可以使用, 但不建議

resourceobject
mixed
numeric

yield語法調整
在表達式裏面使用yield語法結構的時候, 不再需要括號了. 它現在是一個右關聯的操作符, 優先級介於"print"和"=>"操作符. 在某些場景下面行爲和之前會不一致.

echo yield -1;
echo (yield) - 1;  // 之前的語法解釋行爲
echo yield (-1);   // 現在的語法解釋行爲
yield $foo or die;
yield ($foo or die);  // 之前的語法解釋行爲
(yield $foo) or die;  // 現在的語法解釋行爲
// 可以通過括號來避免歧義


備註: 關於yield, 大家可以參考鳥哥的這篇文章: http://www.laruence.com/2012/08/30/2738.html

其他的一些調整: 

棄用功能

  • PHP4風格的構造函數將被棄用;(和類名同名的方法視爲構造方法, 這是PHP4的語法.)
  • 靜態調用非靜態方法將被棄用;
  • capture_session_meta選項將被棄用, 可以調用stream_get_meta_data()獲得;

新增函數

  • GMP(The GNU MP Bignum Library)模塊新增了gmp_random_seed()函數;
  • PCRE增加了preg_replace_callback_array方法;
  • 增加了intdiv()函數;
  • 增加了error_clear_last()函數來重置錯誤狀態;
  • 增加了ZipArchive::setComapressionIndex()和ZipArchive::setCompressionName()來設置壓縮方法;
  • 增加了deflate_init(), deflate_add(), inflate_init(), inflate_add();

修改函數

  • parse_ini_file()和parse_ini_string()的scanner_mode參數增加了INI_SCANNER_TYPED選項;
  • unserialize()增加了第二個參數, 可以用來指定接受的類列表;
  • proc_open()打開的最大限制之前是寫死的16, 現在這個限制被移除了, 最大數量取決於PHP可用的內存;
  • array_column() The function now supports an array of objects as well as two-dimensional arrays
  • stream_context_create() windows下面可以接收array("pipe" => array("blocking" => ))參數;
  • dirname()增加了可選項$levels, 可以用來指定目錄的層級. dirname(dirname($foo)) => dirname($foo, 2);
  • debug_zval_dump()打印的時候, 使用int代替long, 使用float代替double;

其他修改

  • NaN和Infinity轉爲整型的時候, 始終爲0;
  • Instead of being undefined and platform-dependent, NaN and Infinity will always be zero when cast to integer;
  • Calling a method on a non-object現在會拋出一個可以撲獲的錯誤, 不再是致命錯誤;
  • zend_parse_parameters的錯誤信息始終使用integer, float, 不再使用long和double;
  • ignore_user_abort設爲真的話, 輸出緩存會繼續工作, 即使鏈接中斷;
  • Zend擴展接口使用zend_extension.op_array_persist_calc()和zend_extensions.op_array_persist();
  • 引入了zend_internal_function.reserved[]數組;
  • 增加了"alpn_protocols"選項;
  • 增加了ReflectionGenerator類, 用於yield from Traces, current file/line等等;
  • 增加了ReflectionType類, 更好的支持新的返回值和標量聲明功能;

參考

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章