Edited at

PHP7.4の新機能

PHP7.4.0その2 / PHP7.4.0その1 / PHP7.3.0 / PHP7.3.0α1

PHP7.4で実装される新機能を紹介してみます。

7.3以上の大きな変更が複数導入されることになっていて、大丈夫なのかこれ。


RFC


Foreign Function Interface

賛成24、反対15で受理。

$ffi = FFI::cdef("

typedef unsigned int time_t;
typedef unsigned int suseconds_t;

struct timeval {
time_t tv_sec;
suseconds_t tv_usec;
};

struct timezone {
int tz_minuteswest;
int tz_dsttime;
};

int gettimeofday(struct timeval *tv, struct timezone *tz);
", "libc.so.6");

$tv = $ffi->new("struct timeval");
$tz = $ffi->new("struct timezone");

var_dump($ffi->gettimeofday(FFI::addr($tv), FFI::addr($tz)));

なんだこの文法は!

phpでマークアップされないぞ!

まあ当然で、これはCのコードです。

FFIとはつまり、PHPで別言語を動かすという機能です。

これによってC言語のネイティブ機能に直接アクセスすることが可能になります。

またPHPで書くより高速になるみたいです。

元々PECLには存在していたのですが、最終更新が15年前で死亡確認済です。

今回はdstogovによるdstogov/php-ffiをPHP本体に入れるという提案です。

半数以上の賛成で可決されるという投票ルールであったため、多数の懸念にもかかわらず可決されました。

両言語に精通していないといけないため、なかなか使いこなすのが難しそうな機能です。

私はもちろん使えません。


Typed Properties 2.0

賛成70、反対1で受理。

プロパティ型指定です。

class User {

public int $id;
public string $name;

public function __construct(int $id, string $name) {
$this->id = $id;
$this->name = $name;
}
}

このRFC、受理されたあと一度Pending行きになって先行きが不安視されていたのですが、どうやら問題は解決されたようで、無事masterにmergeされました。


Null Coalescing Assignment Operator

賛成37、反対4で受理。

NULL合体代入演算子です。

$this->request->data['comments']['user_id'] ??= 'value';


Preloading

賛成48、反対0で受理。

プリローディングです。


php.ini

opcache.preload="path/to/preload.php"



path/to/preload.php

opcache_compile_file('path/to/hoge.php');

opcache_compile_file('path/to/fuga.php');

preload.phpから解決できる関数やクラスが、ネイティブ関数と同じようにいつでもどこでも使えるようになります。


Always available hash extension

賛成30、反対0で受理。

HASHエクステンションを常に有効にしよう、という提案。

このエクステンションにはhash_equalshash_hmacといったハッシュ関連で重要な関数が含まれています。

これまではインストール時に--disable-hash無効化できたのですが、今後は無効化することができなくなります。

この要請はどこから来たのかというと、MySQLの認証系で必要になったからみたいです。


Password Hash Registry

賛成21、反対0で受理。

上のRFCと同じHashなんたらという名前ですが、こちらはpassword_hashに関するもので、特に関係ありません。

エクステンションが独自のハッシュアルゴリズムを追加できるようにするRFCです。

  PHPAPI int php_password_algo_register(const char* ident, const php_password_algo*);

PHPAPI void php_password_algo_unregister(const char* ident);
PHPAPI const php_password_algo* php_password_algo_default();
PHPAPI zend_string *php_password_algo_extract_ident(const zend_string* hash);
PHPAPI const php_password_algo* php_password_algo_find(const zend_string* ident);
PHPAPI const php_password_algo* php_password_algo_get_named(const zend_string* name);
PHPAPI php_password_algo* php_password_algo_identify(const zend_string* hash);

見てのとおりC言語で、PHP側で特にどうこうするものではありません。

もしかしたらFFIでどうにかできるのかも?

PHP側にはpassword_algos関数がひとつ追加され、現在有効なハッシュアルゴリズムの一覧を取得することができるようになります。

  > print_r(password_algos());

Array (
[0] => "2y" // Ident for "bcrypt"
[1] => "argon2i"
[2] => "argon2id"
)

またPASSWORD_DEFAULTなどの定数は現在int型ですが、文字列型に変更されます。

正しい実装をしているかぎりは、何も変更する必要はありません。


Improve openssl_random_pseudo_bytes

賛成30、反対0で受理。

openssl_random_pseudo_bytesを改善する提案。

openssl_random_pseudo_bytesはfalseを返すことがあり、さらに第二引数$crypto_strongもfalseを返すことがあります。

つまり、openssl_random_pseudo_bytesの正しい使い方は以下のようになります。

    $strong = false;

$bytes = openssl_random_pseudo_bytes(32, $strong);
if (false === $bytes || false === $strong) {
throw new \Exception('CSPRNG error');
}
return $bytes;

まあ面倒ですね。

ということでopenssl_random_pseudo_bytesは今後、random_bytesと同じように失敗時はExceptionを出すようになります。

ただし第二引数$crypto_strongをDeprecateにする提案は賛成12反対12で却下されました。

こっちも例外でいいと思うんですけどね。


mb_str_split() Split multibyte string

賛成10、反対1で受理。

str_splitのマルチバイト版、mb_str_splitを導入する提案。

これでうっかりstr_splitに日本語を突っ込む事故を防げますね。

それにしてもmb_splitとかpreg_splitとかsplitとか似たような関数が多くてつらい。


Reflection for references

賛成30、反対1で受理。

リファレンスかどうかを判断するリフレクションがほしい、という提案。

たとえばsymfony/VarClonerは、リファレンスか否かの判定を以下のように行っています。

$array2 = $array1;

$array2[$key] = $unique_cookie;
if ($array1[$key] === $unique_cookie) {
// リファレンスだった
}else{
// リファレンスではなかった
}

なんというかアレですね。

ということでリファレンスを表すReflectionReferenceを追加します。

final class ReflectionReference {

/** @return ?ReflectionReference リファレンスであればReflectionReference、そうでなければnull */
public static function fromArrayElement(array $array, int|string $key): ?ReflectionReference;

/** @return int|string 特定するIDを返す */
public function getId(): int|string;

private function __construct(); // Always throws
private function __clone(); // Always throws
}

getIdはひとつの参照に対して一意なIDを返すメソッドで、spl_object_hashのようなものです。

複数の参照が同じオブジェクトを指しているかを判別するなどの用途に使うようです。

ユーザ側としてはそんなに必要な機能でもありませんが、テストライブラリにとっては非常に役立つものだと思われます。


その他


Abolish Narrow Margins

FFIが可決されてしまった衝撃は大きかったらしく、提案されたまま長らく放置されていた、あらゆる変更に2/3の賛成を必要とするRFCが速攻で投票され賛成30反対2の賛成多数で可決されました。

これまではPHPコアの変更や互換のない変更などは2/3の賛成、それ以外の些細な変更などは50%+1の賛成で受理されていましたが、今後はあらゆるRFCが2/3の賛成を必要とするようになります。

もしこのRFCがもっと早く受理されていたら、array_key_firstPDOStatement::activeQueryStringも実装されていなかったかもしれませんね。


感想

FFI、プロパティ型指定、??=、プリローディングと重量級の変更が幾つも入ってきていて、スケジュールとか大丈夫なのか心配になりますね。

特にFFIは反対も多いだけに(導入自体に反対ではなく、時期尚早という意見が多い)先行き不透明で、PHP7.4が来ても使用は待った方がよさそうです。

いやまあそれ以前に、能力的な問題でFFIを使いきれそうにないですが。