PHP
rfc
PHP7

却下されたPHP RFCを見てみる その2

その1 / その2 / その3

Declined RFC

続いた。
以下は全てPHP7.xでの採用を目指し、そして敗れた兵どもの跡地です。

''var'' Deprecation

賛成31/反対23で却下。

class HOGE{
    public $a;
    var $b; // ←これ
}

いいかげんvarとか削除しようぜ、という提案。
PHP7.1でE_DEPRECATE、PHP8にて削除、というロードマップです。
Packagistの上位10000パッケージを調査したところ未だvar使ってるのは4~8%くらいしかいないぞ、という調査まで行ってご苦労ではありましたが、驚いたことに却下されました。
今時varとか使ってるソースなんぞ、そもそもPHP7では動かんだろうに。

Precise Session Management

賛成15/反対11で却下。

現在のセッション管理は色々と問題があるので、もっとどうにかしようという提案。

    // Cookieの有効期限
    ini_set('session.cookie_lifetime', 1);
    // サーバに保存されているセッション情報の有効期限
    ini_set('session.gc_maxlifetime', 1);

    session_start();

    $_SESSION[time()] = 1;
    var_dump(session_id(), $_SESSION); // リロード毎にセッションIDが変わる

Cookieの有効期限はsession.cookie_lifetimeで指定します。
0にするとブラウザを閉じるまでになります。
今回は1秒、要するに連打しない限り毎回期限切れになる設定にしました。
この画面をリロードするとsession_id()は毎回変わり、前回登録したセッション内容は表示されません。
めでたしめでたし。

実はこれ、単にブラウザが有効期限の切れたCookieを送らないだけです。
Cookieを直接指定してリクエストすると、普通に前回のデータが見えてしまいます。
session.gc_maxlifetimeで有効期限を指定しているのに何故?

実はセッションデータ自体には、有効期限を管理するような機能は全くありません。
そしてsession.gc_maxlifetimeは、「サーバに保存されてるセッションデータは、これを過ぎたら削除してもいいよ」と言ってるだけです。
実際はサーバにリクエストが来たときに1/100の確率1でGCが起動し、GCでsession.gc_maxlifetimeより古いセッションデータが初めて削除される、という処理になっています。
毎回GCが起動しないのはもちろん重くなるからです。
ということで、一見存在しなくなったセッションデータは、実はしばらくサーバ側に存在するし、そこにアクセスすることも可能です。

また、session_regenerate_id()についての問題点も列挙されています。
session_regenerate_id(true)は古いセッションを即座に削除しますが、そのレスポンスを受け取れなかった際などにセッション情報が消失します。
決してレアな話ではなく、Ajaxをたくさん使ってたり、ブラウザバックとか進むとか使っているとわりとすぐ発生し、勝手にログアウトさせられたりするためかなり不便です。

他にもsession_regenerate_id(false)がデフォルトのせいでとか色々書いてあるけど読むの疲れたから割愛。

結論としては、色々な問題点を改善すべく、現在のセッション構造を大幅に書き換える提案がなされています。
$_SESSION['__PHP_SESSION__']という隠しパラメータを新設し、セッションの有効期限や、session_regenerate_id()でのセッションID変更履歴などを正確に管理する。
・18時間毎(session.regenerate_idで設定可能)に自動的にsession_regenerate_id(true)を実行し、セッションID盗難の被害を減らす。
$_SESSION['__PHP_SESSION__']の詳細を確認できる関数session_info()を追加。
・即座にGCを走らせる関数session_gc()を追加。
・session_destroy()にデータを即削除するオプションを追加。
・これらを実現するためのphp.ini設定 session.ttl / session.ttl_update / session.ttl_destroy / session.regenerate_id / session.num_sidssといったオプションを追加。
・session.use_strict_mode=1をデフォルトにする。
・session.hash_function=1、session.hash_bits_per_characters=5をデフォルトにする。

不用意に他人にセッション情報が渡ることをなんとしても阻止したいという執念が垣間見えます。
が、しかしまあなんというか、一気に変えすぎな感がありますね。
今のサーバ側セッション管理がわかりにくいのはそのとおりだと思うので、どうにかしてほしいのは確かですが、さすがに根幹部分がここまで一気に入れ替わると問題も多そうです。

一気に変更する提案は却下されましたが、それを受けて個々を切り出したAdd session_gc()Add session_create_id()Session ID without hashingsession.use_strict_modeなどのRFCが提出されています。
Session ID without hashingは受理され、そして他も2016/08/19時点で賛成8反対1とかなのでおそらく受理されるでしょう。
一方session.use_strict_modeは却下されました。

Number Format Separator

賛成20/反対18で却下。

    var_dump(9803026723432457239 === 980302673432457239);

ぱっと見わかりにくくありません?わかりにくいですよね?
よし、数値リテラルのセパレータを導入しよう。

    var_dump(9_803_026_723_432_457_239 === 980_302_673_432_457_239);

これで異なる値であることが一目瞭然です。

    1_000_000; // 1000000
    3.141_592; // 3.141592
    0x02_56_12; // 0x025612
    0b0010_1101; // 0b00101101
    0267_3432; // 02673432
    1_123.456_7e2 // 1123.4567e2

    _10; // NG
    1__0; // NG

いや、うん、これはないわ。
と思ったんだけどJava/Ruby/C#など、対応してる言語は意外とあるみたい。
そんな中シングルクォートという孤高の道を選択したC++に幸がありますように。

JSON numeric as string

賛成1/反対5で却下。

    $json = [1234567890123456789, 1.234567890123456789];
    var_dump(
        json_decode($json, true, null, JSON_BIGINT_AS_STRING)
    );

前者はstring('1234567890123456789')になりますが、後者はfloat(1.2345678901235)になり情報が欠落してしまいます。
そこで浮動小数を文字列として返す第四引数JSON_FLOAT_AS_STRINGを追加するという提案。

    $json = [1234567890123456789, 1.234567890123456789];
    var_dump(
        json_decode($json, true, null, JSON_BIGINT_AS_STRING | JSON_FLOAT_AS_STRING)
    );

これで後者もstring('1.234567890123456789')になります。

というかこの問題は、そもそもjson_decode()JSON内の数値を勝手にPHPのintやfloat型にしてしまうのが原因です。
これがunserialize()session_decode()であればエンコードもデコードもPHPなので問題ないのですが、JSONの場合は他言語とやりとりするのが主なので、言語間の有効桁数の違いなどで問題が発生するというわけです。
従って、常に全ての値を文字列として返してくれるのが一番なのですが、そういう設定は現状ありません。
JSON_FLOAT_AS_STRINGなんかではなく、JSON_AS_STRINGがほしいところですね。

In Operator

賛成21/反対26で却下。

算術演算子inを追加する、という提案。

    // だいたい同じ
    $contains = strpos('foobar', 'foo'); // 0
    $contains = 'foo' in 'foobar'; // true

    // だいたい同じ
    $contains = in_array(1 ['1', '2', '3']); // true
    $contains = 1 in ['1', '2', '3']; // false

    // コールバックとかも置ける
    $contains = 'a' in function(){
        yield 'a'; yield 'b'; yield 'c';
    } // true
    $contains = ['a'] in [['a'], ['b']] // true

だから一単語に複数の意味を持たせるのはやめろとあれほど。

特徴としては==ではなく===で比較するので、値が同じでも型が異なる場合はfalseになります。
逆にstrpos()===0でもinは正しくtrueとなります。
むしろstrpos()やin_array()の判定を===にしてくれないか。

Coercive Scalar Type Hints

賛成27/反対44で却下。

スカラ型タイプヒンティングの提案です。
なのですが、受理されたScalar Type Declarationsとの違いがいまいちはっきりしません。
受け入れ可能な値が微妙に異なるのと、Strictモードが今後の予定になっているくらいでしょうか。

function hoge(int $a){
    var_dump($a);
}
hoge(1.0); // PHP7、Coercive共に1
hoge(1.5); // PHP7では1、Coerciveではエラー

というか具体的なコードが書いてないから、実際どういうものなのかよくわかんないんですよね。
本文に『Scalar Type Declarationsなんかよりこっちの方がいいんだからね!』的なことが書いてはあるのですが、それでも負けたのは、プレゼン力が足りなかったのが原因でしょうか。

Make empty() a Variadic

賛成26/反対26で却下。

empty()の文法を拡張しようという提案。

if(empty($_REQUEST['hoge']) || empty($_REQUEST['fuga'])){
  // めんどくさい
}

if(empty($_REQUEST['hoge'], $_REQUEST['fuga'])){
    // かんたん!
}

if(!isset($_REQUEST['hoge']) || !$_REQUEST['hoge'] || !isset($_REQUEST['fuga']) || !$_REQUEST['fuga']){
  // emptyの無い世界
}

元よりempty()は関数ではなく言語構造なので、echoと同じように,区切り表記ができてもよいのではないかと思われましたが、賛否同数で却下となりました。
私はそもそも全くempty()を使わないので関係ないのですが、あればあったで便利な機能だと思うだけに、どうして却下されたのかよくわかりません。

Allow error_handler callback parameters to be passed by reference

賛成4/反対16で却下。名前長い。

set_error_handler()の第一引数error_handlerコールバックの引数をリファレンス渡しにしようという提案。

set_error_handler(function($errno, &$errstr, $errfile, $errline) {
  $errstr = 'エラーメッセージ変更';
  return false;
});

echo $undefined; // E_NOTICEだが"Undefined variable: undefined"ではなく"エラーメッセージ変更"が出る

エラーメッセージを書き換え可能になります。
ぱっと見何となく便利そうだという気はしますが、知らないうちにどこかのライブラリで書き換えられてたりしたら阿鼻叫喚間違いなしです。
$errstrに賛成したのが3人、$errstrだけではなく$errno$errfile$errlineをも&にしてしまおうという過激派が一人いましたが、幸いなことに却下となりました。

pecl_http

賛成9/反対23で却下。

pecl_httpをPHPコアに入れよう、という提案。

pecl_httpはPSR-7: HTTP message interfacesに従って実装されているモダンなライブラリで、グローバル空間を汚染しない今時な書き方ができるようになります。
現状のpecl_httpは色々依存があったりして導入が面倒なようなので、デフォルトでインストールできるのであれば便利かと思われましたが却下。

まあなんというか、大抵の場合はfile_get_contents()だけで事足りるし、特殊な用途ではCurl使っとけばいいので、取り入れられたところであまり出番はないのではないかという気がしないでもない。

Skipping optional parameters

賛成17/反対27で却下。

デフォルト値のある引数で、明示的にデフォルト値を指定したいという提案。
引数が4個ある関数で4個目の引数だけを変更したい場合など、これまで面倒でした。

function hoge($var1 = null, $var2 = '', $var3 = 0, $var4 = false){
    var_dump($var1, $var2, $var3, $var4);
}

hoge(null, '', 0, true); // デフォルト値を調べて合わせないといけない
hoge(default, default, default, true); // こうしたい

けっこう便利な気がするのですが、何故か反対多数で却下されました。
予約語を増やすところが気に入らなかったのでしょうか。

hoge( , , , true); // もうこれでよくね?

まとめ

前回拒否られてたCallable PrototypesのFuture ScopeにあったNamed Callable Typesが、Typesafe Callableとして再び提案されてた。
あとTyped PropertiesProperty Type-hintsで復活してた。

互換性のなくなる変更については非常に慎重な姿勢であり、もはや使う人のいないvarですら拒否される。
一方、互換性の問題のない便利そうな機能でも、わりかしさくっと拒否られることもよくある。
やはり重要なのは袖の下か。


  1. 正確にははsession.gc_probability/session.gc_divisor。デフォルトが1/100