PHP 7.2 在 2017年十一月30號釋出了
趕緊用 phpbrew 裝在 Mac 上測試玩玩
來看看有哪些新功能吧
New objec type
這個新特性加入了 Object 的 type hitting
現在可以指定傳入的參數為 object (stdClass)
也可以指定回傳的參數是 object
直接看執行結果的差異摟
我用 7.1 跟 7.2 做比較
$ php -v
PHP 7.2.0 (cli) (built: Dec 30 2017 22:48:12) ( NTS )
Copyright (c) 1997-2017 The PHP Group
Zend Engine v3.2.0, Copyright (c) 1998-2017 Zend Technologies
$ php -a
Interactive shell
php > function test(object $obj) : object
php > {
php { var_dump($obj);
php { return $obj;
php { }
php > test(new StdClass());
object(stdClass)#1 (0) {}
$ php -v
PHP 7.1.3 (cli) (built: Sep 4 2017 23:40:15) ( NTS DEBUG )
Copyright (c) 1997-2017 The PHP Group
Zend Engine v3.1.0, Copyright (c) 1998-2017 Zend Technologies
with Zend OPcache v7.1.3, Copyright (c) 1999-2017, by Zend Technologies
with Xdebug v2.5.5, Copyright (c) 2002-2017, by Derick Rethans
$ php -a [22:57:45]
Interactive shell
php > function test(object $obj) : object
php > {
php { var_dump($obj);
php { return $obj;
php { }
php > test(new StdClass());
PHP Warning: Uncaught TypeError: Argument 1 passed to test() must be an instance of object, instance of stdClass given, called in php shell code on line 1 and defined in php shell code:1
Stack trace:
#0 php shell code(1): test(Object(stdClass))
#1 {main}
thrown in php shell code on line 1
Warning: Uncaught TypeError: Argument 1 passed to test() must be an instance of object, instance of stdClass given, called in php shell code on line 1 and defined in php shell code:1
Stack trace:
#0 php shell code(1): test(Object(stdClass))
#1 {main}
thrown in php shell code on line 1
在 php 7.1 執行一樣的程式碼
會出現 TypeError
無法支援 object 的 type hitting
Extension loading by name
7.2 版本在 ini 設定 load extension 不再需要副檔名
若是 Linux like 系統
7.2 之前要啟用 extension 都需要在 php.ini or optional.ini 內加入
zend_extension=opcache.so
或是在 windows 內要這樣寫
zend_extension=opcache.dll
在使用自動化部屬的時候就無法共用同一份 config
而現在 7.2 只需要 extension 名稱即可自動載入
zend_extension=opcache
一樣來測試看看
$ cat /Users/herb/.phpbrew/php/php-7.2.0/var/db/opcache.ini
zend_extension=opcache
$ php -v
PHP 7.2.0 (cli) (built: Dec 30 2017 22:48:12) ( NTS )
Copyright (c) 1997-2017 The PHP Group
Zend Engine v3.2.0, Copyright (c) 1998-2017 Zend Technologies
with Zend OPcache v7.2.0, Copyright (c) 1999-2017, by Zend Technologies
7.2版可正常執行
在 7.1版則會出現找不到 extension 的錯誤訊息
cat /Users/herb/.phpbrew/php/php-7.1.3/var/db/opcache.ini [0:01:55]
zend_extension=/Users/herb/.phpbrew/php/php-7.1.3/lib/php/extensions/debug-non-zts-20160303/opcache
$ php -v
Failed loading /Users/herb/.phpbrew/php/php-7.1.3/lib/php/extensions/debug-non-zts-20160303/opcache: dlopen(/Users/herb/.phpbrew/php/php-7.1.3/lib/php/extensions/debug-non-zts-20160303/opcache, 9): image not found
PHP 7.1.3 (cli) (built: Sep 4 2017 23:40:15) ( NTS DEBUG )
Copyright (c) 1997-2017 The PHP Group
Zend Engine v3.1.0, Copyright (c) 1998-2017 Zend Technologies
with Xdebug v2.5.5, Copyright (c) 2002-2017, by Derick Rethans
Abstract method overriding
7.2 版可以複寫 abstract class 的方法
$ php -a
Interactive shell
php > abstract class A
php > {
php { abstract function test(string $s);
php { }
php > abstract class B extends A
php > {
php { // overridden - still maintaining contravariance for parameters and covariance for return
php { abstract function test($s) : int;
php { }
php >
7.1 版則會出現錯誤
$ php -a
Interactive shell
php > abstract class A
php > {
php { abstract function test(string $s);
php { }
php > abstract class B extends A
php > {
php { // overridden - still maintaining contravariance for parameters and covariance for return
php { abstract function test($s) : int;
php { }
PHP Fatal error: Can't inherit abstract function A::test() (previously declared abstract in B) in php shell code on line 5
Fatal error: Can't inherit abstract function A::test() (previously declared abstract in B) in php shell code on line 5
php >
Sodium 從PECL移到主要 extension
主要原因為原本 PHP 的加解密函數 mcrypt_* 已經在 7.2 版被移除(7.1只有加上 Deprecate notice)
根據這篇的描述,mcrypt 是一個阻礙語言發展的絆腳石XD
而且 mcrypt 可做到的"所有事"
openssl 系列方法都可以做到
而 Sodium 則比 openssl 效率高
但要裝不裝都可以吧
Password hashing with Argon2
在 php 裡有個 password_hash 的 function
可以指定多種演算法將字串做 hash
7.2 版則新加入一種叫 Argon2 的演算法
搜尋後發現
這是目前最適合做 password hashing的演算法
根據 OWASP 的介紹
Argon2 is the winner of the password hashing competition and should be considered as your first choice for new applications;
Argon2 應當成為下個專案做密碼雜湊的首選演算法
第二才是PBKDF2
而 7.2 之前最熱門的 bcrypt 僅排最後
既然如此就來試用一下吧
$ php -a
Interactive shell
php > echo password_hash('my_password', PASSWORD_ARGON2I);
PHP Warning: Use of undefined constant PASSWORD_ARGON2I - assumed 'PASSWORD_ARGON2I' (this will throw an Error in a future version of PHP) in php shell code on line 1
Warning: Use of undefined constant PASSWORD_ARGON2I - assumed 'PASSWORD_ARGON2I' (this will throw an Error in a future version of PHP) in php shell code on line 1
PHP Warning: password_hash() expects parameter 2 to be integer, string given in php shell code on line 1
Warning: password_hash() expects parameter 2 to be integer, string given in php shell code on line 1
php >
竟然出現錯誤?
說找不到 PASSWORD_ARGON2I 常數
不是說 7.2 開始支援嗎?
結果發現是在編譯 php 的時候必須要加上 –with-password-argon2 的參數
而且在編譯的環境裡要先裝有 argon2 的 lib and header files
所以我先用 brew 裝 argon2 起來
再重新編譯 php 7.2
$ brew update
$ brew install argon2
$ phpbrew install 7.2.0 +default +mb +mysql +pdo -- --with-password-argon2
重新執行看看
$ php -a
Interactive shell
php > echo password_hash('my_password', PASSWORD_ARGON2I);
$argon2i$v=19$m=1024,t=2,p=2$bUxhSldzZGxvUy5RbTF2cw$hfUVzeiIiMCnoMy/+7G/yT9l2fl2PJcdCFfXDOIvDVU
成功摟!
在編譯的時候看到
Argon2 可以防禦 time-memory trade off attack
也就是俗稱的 Rainbow table
所以再次執行 password_hash 出來的結果會不同
$ php -a
Interactive shell
php > echo password_hash('my_password', PASSWORD_ARGON2I);
$argon2i$v=19$m=1024,t=2,p=2$bUxhSldzZGxvUy5RbTF2cw$hfUVzeiIiMCnoMy/+7G/yT9l2fl2PJcdCFfXDOIvDVU
php > echo password_hash('my_password', PASSWORD_ARGON2I);
$argon2i$v=19$m=1024,t=2,p=2$UUk1Y1EweDVyb1hZeWUxRA$IW7t2YGeMnIhffEeSDPWzPgEqIGnfQ3BMNk5fyW1ZeQ
php > echo password_hash('my_password', PASSWORD_ARGON2I);
$argon2i$v=19$m=1024,t=2,p=2$UzlvdnJhWjBKakdmZ3BqWQ$QuU4DJ5qI564QhnZgobhmFwGpHFSzHGaMwgHP5G+TSY
php > echo password_hash('my_password', PASSWORD_ARGON2I);
$argon2i$v=19$m=1024,t=2,p=2$aTB5U0ZLdWdXYkN2aEVQUQ$oaM8lJh4j8pwqr/CR+KjNn5siFAiKie09XX7KFOOKR8
php >
果然是最適合做密碼雜湊的演算法!
Extended string types for PDO
7.2 版的 PDO 支援 National character type
這我還不是很明白跟一般的 UTF-8 character 有何差別
可能要再做實驗才能了解
新增的常數如下
- PDO::PARAM_STR_NATL
- PDO::PARAM_STR_CHAR
- PDO::ATTR_DEFAULT_STR_PARAM
這要跟一般的 PARAM_STR 搭配
<?php
$db->quote('über', PDO::PARAM_STR ' PDO::PARAM_STR_NATL);
Additional emulated prepares debugging information for PDO
7.2 版的PDOStatement::debugDumpParams() 函數會把原始的SQL給印出
包括取代 bind param 的 SQL 語句
Support for extended operations in LDAP
跟 LDAP 不熟XD
以下是新增的常數跟方法
- ldap_parse_exop()
- ldap_exop()
- ldap_exop_passwd()
- ldap_exop_whoami()
- LDAP_EXOP_START_TLS
- LDAP_EXOP_MODIFY_PASSWD
- LDAP_EXOP_REFRESH
- LDAP_EXOP_WHO_AM_I
- LDAP_EXOP_TURN
Address Information additions to the Sockets extension
PHP 7.2 加入 socket address info 的新函數
主要是對應到 C 的 getaddrinfo
新增的函數有
- socket_addrinfo_lookup()
- socket_addrinfo_connect()
- socket_addrinfo_bind()
- socket_addrinfo_explain()
這項新功能是在 2016-08-08 時提出
並依據當時提案實作出的功能
PHP RFC: Implement socket_getaddrinfo()
IPv4 Example
<?php
$addrinfo = socket_addrinfo_lookup('localhost', 2000, array('ai_family' => AF_INET, 'ai_socktype' => SOCK_STREAM));
$sockaddr = reset($addrinfo);
if (!$sockaddr) die ("No Valid Socket Types");
$sock = socket_addrinfo_bind($sockaddr);
// ^^ $sock is a socket resource that is bound to 127.0.0.1:2000 using TCP/IP ready for reading
var_dump(socket_addrinfo_explain($sockaddr));
/* Outputs:
array(5) {
["ai_flags"]=>
int(0)
["ai_family"]=>
int(2)
["ai_socktype"]=>
int(1)
["ai_protocol"]=>
int(6)
["ai_addr"]=>
array(2) {
["sin_port"]=>
int(2000)
["sin_addr"]=>
string(9) "127.0.0.1"
}
}
*/
IPv6 Example
<?php
$addrinfo = socket_addrinfo_lookup('localhost', 2000, array('ai_family' => AF_INET6, 'ai_socktype' => SOCK_STREAM));
$sockaddr = reset($addrinfo);
if (!$sockaddr) die ("No Valid Socket Types");
$sock = socket_addrinfo_bind($sockaddr);
// ^^ $sock is a socket resource that is bound to [::1]:2000 using TCP/IP ready for reading
var_dump(socket_addrinfo_explain($sockaddr));
/* Outputs:
array(5) {
["ai_flags"]=>
int(0)
["ai_family"]=>
int(10)
["ai_socktype"]=>
int(1)
["ai_protocol"]=>
int(6)
["ai_addr"]=>
array(2) {
["sin6_port"]=>
int(2000)
["sin6_addr"]=>
string(3) "::1"
}
}
*/
Parameter type widening
PHP 7.2 可以在實作 interface 的時候忽略參數的型態
<?php
interface A
{
public function Test(array $input);
}
class B implements A
{
public function Test($input){} // type omitted for $input
}
依據官方的說法
就算忽略參數的型態
依然符合 OOP 的 LSP 規則
其實這樣說也對
LSP 原本就是在描述實作的行為要一致
但沒有說參數的型態一定要一樣
但參數型態不一致
實作的行為要一致也是滿奇怪
但依然可以遵守 LSP 規則是沒錯的
放寬限制讓使用者更自由使用是好事
Allow a trailing comma for grouped namespaces
可以將 use 的 namespace 整合起來
但我記得好像前幾版就有支援了
結果 7.1 真的沒支援
7.2
php > use Foo\Bar\{
php { Foo,
php { Bar,
php { Baz,
php { };
php >
7.1
php > use Foo\Bar\{
php { Foo,
php { Bar,
php { Baz,
php { };
PHP Parse error: syntax error, unexpected '}', expecting identifier (T_STRING) or function (T_FUNCTION) or const (T_CONST) in php shell code on line 5
Parse error: syntax error, unexpected '}', expecting identifier (T_STRING) or function (T_FUNCTION) or const (T_CONST) in php shell code on line 5
proc_nice() support on Windows
就是這個函式可以支援在 Windows 上跑了
pack() and unpack() endian support
Enhancements to the EXIF extension
EXIF extension 開始支援讀取更大範圍的格式
所以增加了以下幾種新 format
- Samsung
- DJI
- Panasonic
- Sony
- Pentax
- Minolta
- Sigma/Foveon
- AGFA
- Kyocera
- Ricoh
- Epson
New features in PCRE
7.2 在 PCRE 加入 J modifier
可允許相同的 Group name
SQLite3 allows writing BLOBs
SQLite3::openBlob() 可以在寫入模式開啟Blob
過去只能在讀取模式時開啟
Oracle OCI8 Transparent Application Failover Callbacks
加入 Oracle Database Transparent Application Failover (TAF) callbacks
TAF 是一個 Oracle 提供的"高可用性"功能
可以讓 PHP 在斷線的時候重新跟 Oracle 連線
以下是官方的範例
<?php
// Define userspace callback
class MyClass {
public static $retry_count;
public static function TAFCallback($conn, $event, $type)
{
switch ($event) {
case OCI_FO_BEGIN:
printf(" Failing Over ... Please stand by");
printf(" Failover type was found to be %s \n",
(($type==OCI_FO_SESSION) ? "SESSION"
:($type==OCI_FO_SELECT) ? "SELECT"
: "UNKNOWN!"));
self::$retry_count = 0;
break;
case OCI_FO_ABORT:
// The application cannot continue using the database
printf(" Failover aborted. Failover will not take place.\n");
break;
case OCI_FO_END:
// Failover completes successfully. Inform users a failover occurs.
printf(" Failover ended ... resuming services\n");
break;
case OCI_FO_REAUTH:
printf(" Failed over user ... resuming services\n");
// Replay any ALTER SESSION commands associated with this connection
// eg. oci_parse($conn, 'ALTER SESSION ...') ;
break;
case OCI_FO_ERROR:
// Stop retrying if we have already attempted for 20 times.
if (self::$retry_count >= 20)
return 0;
printf(" Failover error received. Sleeping...\n");
sleep(10);
self::$retry_count++;
return OCI_FO_RETRY; // retry failover
break;
default:
printf("Bad Failover Event: %d.\n", $event);
break;
}
return 0;
}
}
$conn = oci_connect('hr', 'welcome', 'localhost/XE');
$fn_name = 'MyClass::TAFCallback';
oci_register_taf_callback($conn, $fn_name); // Register TAFCallback to Oracle TAF
$sql = "SELECT col1 FROM mytab";
$stmt = oci_parse($conn, $sql);
oci_define_by_name($stmt, 'COL1', $col1);
// For example, if a connection loss occurs at this point, oci_execute() will
// detect the loss and failover begins. During failover, oci_execute() will
// invoke the TAF callback function several times. If the failover is successful,
// a new connection is transparently created and oci_execute() will continue as
// usual. The connection session settings can be reset in the TAF callback
// function. If the failover is aborted, oci_execute() will return an error
// because a valid connection is not available.
$e = oci_execute($stmt);
if ($e == false)
{
// do error handling, if oci_execute() fails
// var_dump(oci_error($stmt));
}
while (oci_fetch($stmt))
{
echo "COL1 value is $col1<br>\n";
}
// do other SQL statements with the new connection, if it is valid
// $stmt = oci_parse($conn, . . .);
?>
Enhancements to the ZIP extension
支援讀取或寫入加密的 zip 檔案 (需要 libzip 1.2.0)