世界變化真快,突然聽聞 PHP 都到 7.3 版本了,7.2 還沒仔細了解過呢。看到我司面試時會問到php新版本有什么特性,美名其曰考察其學習新技術的能力,我有點汗顏,自己都沒有主動去了解過,實在不應該。因此,在這里立下一貼,用於記錄新版本的PHP的變化,以及對實際工作的影響。
PHP 7.0
PHP7.0 號稱是性能提升上革命性的一個版本。面對 Facebook 家的 HHVM 引擎帶來的壓力,開發團隊重寫了底層的 Zend Engine,名為 Zend Engine 2。
雖然是大版本的更新(直接從PHP5.6跳到了7,中間省略了不存在的6),但是幾乎不會遇到兼容性的問題,不會像 Python 那樣陷入 2.7 或 3.7 的選擇困境。我們自己在評估測試了實際項目運行情況之后,直接升到了 7.1。
下面講一講主要的變化:
新增
標量類型聲明
類型聲明也叫 type hints,即聲明參數的類型。現在可以聲明參數為標量類型了,包括:string,int,float,bool。擴充了原來的范圍,原來只支持:類名,接口名,''array'' 和 ''callable'' 這五種類型。
<?php
function sum(int $a, int $b)
{
return $a + $b;
}
var_dump(sum(1, 2));
// output: int(3)
返回值類型聲明
函數可以添加返回值類型聲明了,聲明返回值的類型。可以聲明的類型范圍與參數聲明類型相同。
function sum(int $a, int $b): int
{
return $a . $b;
}
var_dump(sum(1, 2));
// output: int(12)
function sum(int $a, int $b): string
{
return $a . $b;
}
var_dump(sum(1, 2));
// output: string(2) "12"
?? 操作符
'''??'''操作符簡化了'''isset()'''函數的使用,如果第一個操作數存在且不為NULL,則返回之,否則返回第二個操作數。
<?php
// 這種場景,判斷是否存在,如果不存在則賦值默認一個值
$username = isset($_GET['user']) ? $_GET['user'] : 'nobody';
// 使用 ?? 獲得相同效果,代碼卻簡潔很多
$username = $_GET['user'] ?? 'nobody';
// ?? 可以鏈式使用,第一個不存在,則判斷第二個,第二個不存在再使用默認值
$username = $_GET['user'] ?? $_POST['user'] ?? 'nobody';
<=> 操作符
'''<=>''' 操作符用於比較兩個表達式。可以把這個操作符拆開來理解,'''<=>''' 一次就能判斷出 小於<,等於=,大於> 這三種情況。
<?php
// Integers
echo 1 <=> 1; // 0
echo 1 <=> 2; // -1
echo 2 <=> 1; // 1
// Floats
echo 1.5 <=> 1.5; // 0
echo 1.5 <=> 2.5; // -1
echo 2.5 <=> 1.5; // 1
// Strings
echo "a" <=> "a"; // 0
echo "a" <=> "b"; // -1
echo "b" <=> "a"; // 1
?>
使用 define() 定義常量數組
現在可以使用 define() 來定義常量數組了,而之前只能用 const 定義。
<?php
define('ANIMALS', [
'dog',
'cat',
'bird',
]);
echo ANIMALS[1];
匿名類
現在支持匿名類了。
<?php
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());
// output:
// class class@anonymous#2 (0) {
// }
匿名類至少方便了以下場景:(1)測試更方便,為接口創建實時實現,而不用專門去建立一個只用一次的類。
Unicode 轉義語法
語法如下例所示,可以直接通過16進制的代碼來輸出 Unicode 字符了。
echo "\u{5201}";
echo "\u{2200}";
// output: 刁
// output: ∀
Closure::call()
Closure::call() 是一種更加簡潔的方式,來綁定對象到閉包並調用它。
<?php
class A
{
private $x = 1;
}
$getX = function () {
return $this->x;
};
// pre PHP7
$getXCB = $getX->bindTo(new A, 'A');
echo $getXCB();
// PHP7
echo $getX->call(new A);
過濾的 unserialize()
增加了反序列化對象時的安全性。開發者可以通過第二個參數來設置一個允許反序列化的類的白名單。
<?php
// 轉換所有數據為 __PHP_Incomplete_Class 對象
$data = unserialize($foo, ["allowed_classes" => false]);
// 轉換所有數據為 __PHP_Incomplete_Class 對象,除了 MyClass 和 MyClass2
$data = unserialize($foo, ["allowed_classes" => ["MyClass", "MyClass2"]]);
// 默認行為,相當於忽略了第二個參數,允許所有的類
$data = unserialize($foo, ["allowed_classes" => true]);
IntlChar
增加了新的類 '''IntlChar''',它增加了國際化相關的功能,該類定義了大量的靜態方法和靜態常量,用於控制 Unicode 字符。
使用該類,前提是安裝了 Intl 擴展。
Expectations
Expectations 是 assert() 的向后兼容的增強。在php.ini中增加了 assert.expectation 指令以控制 assert() 的行為。
use 聲明分組
可以將多個use聲明合並為一個use
<?php
// pre PHP7
use some\namespace1\ClassA;
use some\namespace1\ClassB;
use some\namespace1\ClassC as C;
// PHP7+
use some\namespace1\{ClassA, ClassB, ClassC as C};
生成器可以返回表達式
現在生成器可以用return來返回最后一次的表達式,這個值可以用新的 Generator::getReturn() 方法獲取,但是這個方法只能在 yield 值結束之后,使用一次。
<?php
$gen = (function () {
yield 1;
yield 2;
return 3;
})();
foreach ($gen as $val) {
echo $val;
}
echo $gen->getReturn();
// output: 123
這是一個很方便的功能,客戶端使用生成器時可以用它來判斷 yield 值是否完成。
生成器委托
現在,只需在最外層生成器中使用 yield from, 就可以把一個生成器自動委托給其他的生成器、, Traversable 對象或者 array。
<?php
function gen1()
{
yield 1;
yield 2;
yield from gen2();
yield from [5, 6];
}
function gen2()
{
yield 3;
yield 4;
}
foreach (gen1() as $val) {
echo $val, PHP_EOL;
}
/* output:
1
2
3
4
5
6
*/
不了解生成器?可以參考:《Modern_PHP》#Generators 或 Generators_(PHP)
使用 intdiv() 進行整數除法
var_dump(intdiv(10, 3));
// output: int(3)
session 選項
現在 session_start()
可以接受一個 options 數組,以重寫 php.ini 中的 session 設置。
// 設置 session.cache_limiter 為私有,並且讀取完就關閉session
session_start([
'cache_limiter' => 'private',
'read_and_close' => true,
]);
CSPRNG 函數
新增兩個生成加密整數和字符串的函數:random_bytes()
和 random_int()
。
PHP 7.1
新增
Nullable 類型
現在可以在參數類型聲明和返回值類型聲明的類型前面加一個問號(?),來表示參數可以是NULL或者可以返回NULL值。
<?php
function fun1(?int $i) :?string
{
if ($i ### null) {
return null;
} else {
return $i;
}
}
var_dump(fun1(null));
var_dump(fun1(1));
// output: NULL
// output: string(1) "1"
// PHP Fatal error: Uncaught ArgumentCountError: Too few arguments to function fun1(), 0 passed
Void 函數
新增函數的 void 返回值。void函數要么沒有return語句,要么空return語句,返回NULL是錯誤的。
Symmetric array destructuring
短數組語法([])可以作為list()
語法的另一種形式,用來給數組賦值。
$data = [
[1, 'Tom'],
[2, 'Fred']
];
// list() style
list($id1, $name1) = $data[0];
// [] style
[$id2, $name2] = $data[1];
// list() style
foreach ($data as list($id, $name)) {
# code...
}
// [] style
foreach ($data as [$id, $name]) {
# code...
}
類常量可見性
增加了對類常量可見性的支持。
class ConstDemo
{
const PUBLIC_CONST_A = 1;
public const PUBLIC_CONST_B = 2;
protected const PROTECTED_CONST = 3;
private const PRIVATE_CONST = 4;
}
iterable 偽類
增加了新的偽類:iterable,可以用於參數,或返回值類型,表示接受數組或實現了 Traversable 接口的對象。
function iterator(iterable $iter)
{
foreach ($iter as $val) {
//
}
}
多重 catch 捕獲
可以在一個catch中捕獲多種異常對象了。
function triError(bool $i)
{
try {
if ($i) {
throw new Exception('exception!');
} else {
throw new ErrorException('error exception!');
}
} catch(Exception | ErrorException $e) {
echo $e->getMessage(), PHP_EOL;
}
}
triError(true);
triError(false);
// output: exception!
// output: error exception!
list() 可以指定鍵名
現在可以在 list() 或短數組語法([])中指定鍵名,這樣就可以支持非數字索引的數組賦值了。
$data = [
["id" => 1, "name" => "Tom"],
["id" => 2, "name" => "Fred"],
];
list("id" => $id1, "name" => $name1) = $data[0];
var_dump($id1);
var_dump($name1);
// int(1)
// string(3) "Tom"
["id" => $id2, "name" => $name2] = $data[1];
var_dump($id2);
var_dump($name2);
// int(2)
// string(4) "Fred"
支持負的字符串下標
字符串函數以及字符串下標現在可以為負數。負數表示從字符串末尾開始執行相關操作。
var_dump("abcde"[-1]);
var_dump(strpos("abcdeb", "b", -1));
var_dump(strpos("abcdeb", "b", 1));
// string(1) "e"
// int(5)
// int(1)
Closure::fromCallable()
增加了一個靜態方法 fromCallable()
,用於方便地轉換 callable 為 Closure 對象。
class Test
{
public function exposeFunction()
{
return Closure::fromCallable([$this, 'privateFunction']);
}
private function privateFunction($param)
{
var_dump($param);
}
}
$privFunc = (new Test)->exposeFunction();
$privFunc('some value');
// string(10) "some value"
PHP 7.2
新增
object 類型
參數類型和返回值類型聲明現在支持 object
類型了,該類型表示接受任何對象。
function test(object $obj): object
{
return new stdClass();
}
test(new stdClass());
通過名字加載擴展
加載擴展不需要文件擴展名了(.so或.dll),直接用名字即可,在php.ini或者dl()函數都有效。
抽象方法重寫
當一個抽象類繼承另一個抽象類時,它可以重寫父類(抽象類)的方法。
abstract class A
{
abstract public function test(string $s);
}
abstract class B extends A
{
abstract public function test($s): int;
}
Sodium 成為核心擴展
現代 Sodium 加密庫已經成為PHP核心擴展。
使用 Argon2 進行密碼哈希
Argon2 已經被添加到 password hash API中,通過以下常量使用:
*PASSWORD_ARGON2I
*PASSWORD_ARGON2_DEFAULT_MEMORY_COST
*PASSWORD_ARGON2_DEFAULT_TIME_COST
*PASSWORD_ARGON2_DEFAULT_THREADS
擴展PDO字符串類型
新增這些常量,擴展了PDO字符串的使用
*PDO::PARAM_STR_NATL
*PDO::PARAM_STR_CHAR
*PDO::ATTR_DEFAULT_STR_PARAM
$db->quote('über', PDO::PARAM_STR | PDO::PARAM_STR_NATL);
Socket擴展新增地址信息獲取
新增下列方法,可以在連接時、綁定時或解釋時查看地址信息:
*socket_addrinfo_lookup()
*socket_addrinfo_connect()
*socket_addrinfo_bind()
*socket_addrinfo_explain()
參數類型擴展了
重寫方法的參數類型現在被忽略了,so?
interface A
{
public function Test(array $input);
}
class B implements A
{
public function Test($input){} // type omitted for $input
}
分組的命名空間末尾允許逗號
use Foo\Bar\{
Foo,
Bar,
Baz,
};
ZIP擴展增強
支持讀寫加密的壓縮文件了(需要 libzip 1.2.0)
現在 ZipArchive 實現了 Countable 接口。
zip:// 流接受一個 password 上下文選項
PHP 7.3
References
- https://wiki.php.net/rfc/anonymous_classes
- https://en.wikipedia.org/wiki/Code_point
- http://www.avelx.co.uk/unicode-codepoint-escape-syntax/
PS - 個人博客原文:PHP新版本變化