PHP7.4の新機能

[PHP7.4](https://qiita.com/rana_kualu/items/50f9f5735321fe995ff5) / [PHP7.4.0その2](https://qiita.com/rana_kualu/items/993fff9662726a004660) / [PHP7.4.0その1](https://qiita.com/rana_kualu/items/38c9917f634bc7767f1b) / [PHP7.3.0](https://qiita.com/rana_kualu/items/48e6e4baaeb265ab9fa7) / [PHP7.3.0α1](https://qiita.com/rana_kualu/items/a7c6be77e165bca0f3fc)


PHP7.4で実装される[新機能](https://wiki.php.net/rfc#php_74)を紹介してみます。
[7.3](https://qiita.com/rana_kualu/items/a7c6be77e165bca0f3fc)以上の大きな変更が複数導入されることになっていて、大丈夫なのかこれ。

# RFC

## [Foreign Function Interface](https://wiki.php.net/rfc/ffi)

賛成24、反対15で受理。

```php
$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には存在していた](https://pecl.php.net/package/ffi)のですが、最終更新が15年前で死亡確認済です。
今回はdstogovによる[dstogov/php-ffi](https://github.com/dstogov/php-ffi)をPHP本体に入れるという提案です。
半数以上の賛成で可決されるという投票ルールであったため、[多数の懸念](https://externals.io/message/103613)にもかかわらず可決されました。

両言語に精通していないといけないため、なかなか使いこなすのが難しそうな機能です。
私はもちろん使えません。


## [Typed Properties 2.0](https://wiki.php.net/rfc/typed_properties_v2)

賛成70、反対1で受理。
[プロパティ型指定](https://qiita.com/rana_kualu/items/9b4502d229d19635ef34)です。

```php
class User {
    public int $id;
    public string $name;

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

このRFC、受理されたあと一度[Pending](https://wiki.php.net/rfc#pending_implementation)行きになって先行きが不安視されていたのですが、どうやら問題は解決されたようで、無事[masterにmerge](https://github.com/php/php-src/commit/e219ec144ef6682b71e135fd18654ee1bb4676b4)されました。


## [Null Coalescing Assignment Operator](https://wiki.php.net/rfc/null_coalesce_equal_operator)

賛成37、反対4で受理。
[NULL合体代入演算子](https://qiita.com/rana_kualu/items/e15a9b7c12f175380244)です。

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


## [Preloading](https://wiki.php.net/rfc/preload)

賛成48、反対0で受理。
[プリローディング](https://qiita.com/rana_kualu/items/afe544b0f5680e81fabc)です。

```text:php.ini
opcache.preload="path/to/preload.php"
```

```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](https://wiki.php.net/rfc/permanent_hash_ext)

賛成30、反対0で受理。

[HASHエクステンション](https://php.net/manual/ja/book.hash.php)を常に有効にしよう、という提案。
このエクステンションには`hash_equals`や`hash_hmac`といったハッシュ関連で重要な関数が含まれています。
これまではインストール時に`--disable-hash`で[無効化できた](https://secure.php.net/manual/ja/hash.installation.php)のですが、今後は無効化することができなくなります。

この要請はどこから来たのかというと、[MySQLの認証系で必要になったから](https://github.com/php/php-src/commit/d6e81f0bfd0cb90586dd83d4fd47a4302605261a)みたいです。


## [Password Hash Registry](https://wiki.php.net/rfc/password_registry)

賛成21、反対0で受理。

上のRFCと同じHashなんたらという名前ですが、こちらは`password_hash`に関するもので、特に関係ありません。
エクステンションが独自のハッシュアルゴリズムを追加できるようにするRFCです。

```c
  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`関数がひとつ追加され、現在有効なハッシュアルゴリズムの一覧を取得することができるようになります。

```php
  > print_r(password_algos());
  Array (
      [0] => "2y" // Ident for "bcrypt"
      [1] => "argon2i"
      [2] => "argon2id"
  )
```

またPASSWORD_DEFAULTなどの定数は現在int型ですが、文字列型に変更されます。
[正しい実装](https://qiita.com/rana_kualu/items/3ef57485be1103362f56)をしているかぎりは、何も変更する必要はありません。


## [Improve openssl_random_pseudo_bytes](https://wiki.php.net/rfc/improve-openssl-random-pseudo-bytes)

賛成30、反対0で受理。

[openssl_random_pseudo_bytes](http://php.net/manual/ja/function.openssl-random-pseudo-bytes.php)を改善する提案。
openssl_random_pseudo_bytesはfalseを返すことがあり、さらに第二引数`$crypto_strong`もfalseを返すことがあります。
つまり、openssl_random_pseudo_bytesの正しい使い方は以下のようになります。

```php
    $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](http://php.net/manual/ja/function.random-bytes.php)と同じように失敗時はExceptionを出すようになります。

ただし第二引数`$crypto_strong`をDeprecateにする提案は賛成12反対12で却下されました。
こっちも例外でいいと思うんですけどね。


## [mb_str_split() Split multibyte string](https://wiki.php.net/rfc/mb_str_split)

賛成10、反対1で受理。
[str_split](http://php.net/manual/ja/function.str-split.php)のマルチバイト版、[mb_str_split](http://php.net/manual/ja/function.mb-str-split.php)を導入する提案。
これでうっかり[str_splitに日本語を突っ込む事故](https://3v4l.org/8aTif)を防げますね。

それにしても[mb_split](http://php.net/manual/ja/function.mb-split.php)とか[preg_split](http://php.net/manual/ja/function.preg-split.php)とか[split](http://php.net/manual/ja/function.split.php)とか似たような関数が多くてつらい。


## [Reflection for references](https://wiki.php.net/rfc/reference_reflection)

賛成30、反対1で受理。

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

たとえば[symfony/VarCloner](https://github.com/symfony/symfony/blob/master/src/Symfony/Component/VarDumper/Cloner/VarCloner.php)は、リファレンスか否かの判定を以下のように行っています。

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

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

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

```php
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](http://php.net/manual/ja/function.spl-object-hash.php)のようなものです。
複数の参照が同じオブジェクトを指しているかを判別するなどの用途に使うようです。

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


# その他

## [Abolish Narrow Margins](https://wiki.php.net/rfc/abolish-narrow-margins)

FFIが可決されてしまった衝撃は大きかったらしく、提案されたまま長らく放置されていた、あらゆる変更に2/3の賛成を必要とする[RFC](https://wiki.php.net/rfc/abolish-narrow-margins)が速攻で投票され賛成30反対2の賛成多数で可決されました。

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

もしこのRFCがもっと早く受理されていたら、[array_key_first](https://qiita.com/rana_kualu/items/48e6e4baaeb265ab9fa7#array_key_first-array_key_last)や[PDOStatement::activeQueryString](https://qiita.com/rana_kualu/items/40ebed78742bfdbd1065#debugging-pdo-prepared-statement-emulation)も実装されていなかったかもしれませんね。


# 感想

FFI、プロパティ型指定、`??=`、プリローディングと重量級の変更が幾つも入ってきていて、スケジュールとか大丈夫なのか心配になりますね。
特にFFIは反対も多いだけに(導入自体に反対ではなく、時期尚早という意見が多い)先行き不透明で、PHP7.4が来ても使用は待った方がよさそうです。

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