天天看點

PHP7新特性介紹 PHP7-New-Features 新增功能 棄用功能 新增函數 修改函數 其他修改 參考

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

其他的一些調整: 

  • 移除了ASP格式的支援和腳本文法的支援: <% 和 < script language=php >
  • Removed support for assigning the result of new by reference.
  • 移除了在非相容$this語境中對非靜态方法的作用域調用. 參考資料:https://wiki.php.net/rfc/incompat_ctx.
  • http://www.laruence.com/2012/06/14/2628.html
  • ini檔案裡面不再支援#開頭的注釋, 使用";".
  • $HTTP_RAW_POST_DATA 變量被移除, 使用php://input來代替. https://wiki.php.net/rfc/remove_alternative_php_tags

棄用功能

  • 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類, 更好的支援新的傳回值和标量聲明功能;

參考

  • PHP7更新聲明 https://github.com/php/php-src/blob/php-7.0.0alpha1/UPGRADING
  • 官方給出的新特性介紹 http://php.net/manual/en/migration70.new-features.php
php