PHP 7.1 對 list() 語法做了一些調整,讓 list() 操作使用起來更簡便。
原本的 list() 語法:
list($a, $b) = [1, 2];
// $a = 1, $b = 2
在 7.1 以後可以使用陣列來表示:
[$a, $b] = [1, 2];
這個語法稱為 Symmetric Array Destructuring。
有了這個 feature 以後,不用另外宣告暫存變數也可以對調變數 (swap):
[$b, $a] = [$a, $b];
軟體開發和生活瑣事
PHP 7.1 對 list() 語法做了一些調整,讓 list() 操作使用起來更簡便。
原本的 list() 語法:
list($a, $b) = [1, 2];
// $a = 1, $b = 2
在 7.1 以後可以使用陣列來表示:
[$a, $b] = [1, 2];
這個語法稱為 Symmetric Array Destructuring。
有了這個 feature 以後,不用另外宣告暫存變數也可以對調變數 (swap):
[$b, $a] = [$a, $b];
新增 element 到 PHP array 的寫法有二種,一種是用 array_push(),一種是使用 $list[] 直接新增。
跑個迴圈塞一千萬筆資料進 array,看看哪一種寫法的速度比較快。
這個是 array_push() 的:
$list = [];
$start = microtime(true);
for ($a = 0; $a < 10000000; $a++ ) {
array_push($list, 1);
}
$end = microtime(true);
var_dump($end - $start); // double(7.2292509078979)
同上,若將語法換掉的話:
$list = [];
$start = microtime(true);
for ($a = 0; $a < 10000000; $a++ ) {
$list[] = 1;
}
$end = microtime(true);
var_dump($end - $start); // double(2.0703480243683)
小量資料的話,其實二種作法在速度上不會差很多,但資料量變大時,速度差異就會變得很明顯。
看了 PHPUnit 的 source code,發現有個很有趣的寫法:
function assertSame($expected, $actual, string $message = ''): void
{
Assert::assertSame(...func_get_args());
}
最初以為「…」只能用在定義 variable-length function argument/param,重新看了文件才發現之前文件沒有看完,也可以拿來把陣列分別當作 argument/param 傳入:
function add($a, $b) {
return $a + $b;
}
add(...[1, 2]); // $a = 1, $b = 2
// or ...
$args = [1, 2];
add(...$args);
複習 PHPUnit 順手寫了小程式做實驗。
PHPUnit 為了讓各個不同的 test case 不會因為 super globals 變數而互相影響,設計了 「backupGlobals」這個功能。先來看範例程式:
class MyTest extends TestCase
{
public function testOne()
{
$GLOBALS['test'] = 123;
$this->assertArrayHasKey('test', $GLOBALS);
}
public function testTwo()
{
$this->assertArrayHasKey('test', $GLOBALS);
}
}
範例中 testOne() 修改了 $GLOBALS 變數的內容,如果在不使用「backupGlobals」的情況下,在 testTwo() 中的 $GLOBALS 也會保留 testOne() 修改過的內容,使 testTow() 抓得到「test」這個 key:
$ phpunit MyTest.php
PHPUnit 7.0.2 by Sebastian Bergmann and contributors.
.. 2 / 2 (100%)
Time: 40 ms, Memory: 4.00MB
OK (2 tests, 2 assertions)
也就表示 testOne() 影響到了 testTwo() 的測試結果,這在測試時是不樂見的,各個測試應該有獨立的測試環境,不應該互相影響。
使用「backupGlobals」功能,PHPUnit 會在 test case 執行之前,使用 serialize() 對 super globals 做備份,並在測試結束以後 unserialize() 復原,所以 testOne() 中做的修改就不會影響到 testTwo() 了:
$ phpunit --globals-backup MyTest.php
PHPUnit 7.0.2 by Sebastian Bergmann and contributors.
.F 2 / 2 (100%)
Time: 52 ms, Memory: 4.00MB
There was 1 failure:
1) MyTest::testTwo
Failed asserting that an array has the key 'test'.
/home/johnroyer/tmp/MyTest.php:16
FAILURES!
Tests: 2, Assertions: 2, Failures: 1.
重點來了,因為是使用 serialize() 和 unserialize(),所以只能針對 primitive type 進行備份和還原,如果使用了 object 或是 singleton 時,「backupGlobals」就派不上用場了。這個時候要使用殺手鐧「runTestsInSeparateProcesses」:
/**
* @runTestsInSeparateProcesses
*/
class MyTest extends TestCase
{
....
}
使用這個 annotation 會讓 PHPUnit 為每個 test case 分別 fork 出新的 proccess 進行測試,因此不同 test case 就可以在完全獨立的環境下執行不受影響:
$ phpunit MyTest.php
PHPUnit 7.0.2 by Sebastian Bergmann and contributors.
.F 2 / 2 (100%)
Time: 303 ms, Memory: 4.00MB
There was 1 failure:
1) MyTest::testTwo
Failed asserting that an array has the key 'test'.
/home/johnroyer/tmp/MyTest.php:19
FAILURES!
Tests: 2, Assertions: 2, Failures: 1.
但使用這個殺手鐧也是要付出一點代價:時間。最前二次執行 PHPUnit 時,跑完二個 test case 只花費了 50ms 左右,而開 proccess 來進行測試則花了 300ms。
前幾天看到國外有人使用 PHP 7.x 和 HHVM 做了效能測試「The Definitive PHP 5.6, 7.0, 7.1, 7.2 & HHVM Benchmarks (2018)」,可以從比較上看到 PHP 7 以後速度有大幅的躍進,到了 7.1 與 7.2 已經和 HHVM 不相上下了。
目前用過跑起來最慢的 project 應該是 Gallery 和 Druple (古早的版本),由上圖可以看到 PHP 7.x 幾乎快了 5.x 一倍,另外在 7.1 的效能也逼近 HHVM 了。
相較於 HHVM 在佈署之前還要經過編譯步驟,我想應該很多人會回來使用 PHP 7.x 吧。