Nikitaをはじめとする一部のstrictぺちぱー達は、PHPのレガシー機能の削除に熱心に取り組んでいます。
まあ実際、初期のPHPには特にですが、いったいなんでこんなものをという思い付きのような機能がたくさんありましたからね。
そのあたりはPHP7からPHP8.0にかけてだいぶ綺麗に片付けられてきたのですが、まだまだおかしなものも残っています。
そんなわけで以下は、それらを削除しようというRFC、Deprecations for PHP 8.1の紹介です。
投票期間は2021/06/30から2021/07/14まで、有権者の2/3の賛成で受理されます。
Deprecations for PHP 8.1
このRFCでは、以下の機能をPHP8.1で非推奨とし、PHP9.0で削除することを提案します。
受理された提案
以下の提案は受理されました。
PHP8.1以降では使用しないようにしましょう。
date_sunrise() and date_sunset()
賛成51反対0で受理。
date_sunriseとdate_sunsetの2関数のシグネチャは以下のようになっています。
function date_sunset(
int $timestamp,
int $format = SUNFUNCS_RET_STRING,
float $latitude = ini_get("date.default_latitude"),
float $longitude = ini_get("date.default_longitude"),
float $zenith = ini_get("date.sunset_zenith"),
float $gmt_offset = 0
): mixed;
経緯度のデフォルト値がini設定に依存しています。
さらに、目的に応じて適切なzenithを使い分ける必要があるため、この関数を正しく使用することは困難です。
これらの関数はdate_sun_infoに取って代わられました。
function date_sun_info(int $time, float $latitude, float $longitude): array;
このRFCでは2関数、およびini設定date.default_latitude
、date.default_longitude
、date.sunset_zenith
を非推奨とし、次のメジャーバージョンで削除します。
議論はhttps://github.com/php/php-src/pull/4423にあります。
key(), current(), next(), prev(), and reset() on objects
賛成48反対0で受理。
配列ポインタを操作するために使用されるkeyなどの配列関数は、実はオブジェクトも受け取れます。
オブジェクトに対する走査はmangled propertyテーブルを使用します。
すなわち、これらの関数の動作はget_mangled_object_varsと同じということです。
多くの人にとって、これは予想外の動作です。
Iteratorインターフェイスが実装されていれば、key($object)
は$object->key()
が呼び出されるだろうと考えるのが普通だからです。
実際には、PHP7.4以前ではkey()
とイテレータが同じように動くオブジェクトはArrayObject
だけでした。
この問題を解決する方法は2つあります。
ひとつめは、単純にオブジェクトの仕様を非推奨とする方法です。
かわりに明示的キャストやget_mangled_object_vars()
の使用を要求します。
もうひとつは、これらの関数の動作をIteratorに揃えることです。
この際問題になるのは、IteratorAggregate::getIterator()や内部get_iterator()
ハンドラは、最初に一回だけ呼び出されなければならないということです。
これは、複数の独立した関数で構成されている配列関数では不可能です。
そのため、このRFCではオブジェクトに対するkey()
、current()
、next()
、prev()
、reset()
を非推奨とします。
mb_check_encoding() without argument
賛成44反対0で受理。
mb_check_encodingは、引数を渡さずに呼び出すことができます。
ドキュメントには、『省略した場合は、 リクエスト開始時からのすべての入力が対象となります。』とあります。
ところで実装はこうなっています。
/* FIXME: Actually check all inputs, except $_FILES file content. */
if (MBSTRG(illegalchars) == 0) {
RETURN_TRUE;
}
RETURN_FALSE;
この関数に対する信頼を失わせるのに十分な記述でしょう。
さらに調べてみると、場合によってはこの関数は正しい動作をすることもあるということがわかりました。
私の推測では、この仕様はencoding_translationと一緒に使われる想定だったのではないかと思われます。
全体としてこの機能は不完全であり、混乱を招きます。
有名なパッケージで引数無しのmb_check_encoding()
を使っているものはありません。
従って、このRFCでは引数なしのmb_check_encoding()
を非推奨とします。
FILE_BINARY and FILE_TEXT constants
賛成42反対1で受理。
PHP6との互換のため、PHP5.2.7において定数FILE_BINARYとFILE_TEXTが導入されましたが、この定数は何の効果もありません。
特に混乱を招きやすい理由として、fopenがb
やt
モードをサポートしているのに、この定数は全く効かないことです。
このRFCでは、定数FILE_BINARYとFILE_TEXTを非推奨とします。
Passing bool for $value argument of IntlCalendar::roll()
賛成38反対0で受理。
IntlCalendar::roll()の引数$amountOrUpOrDown
は、足し引きする量を受け取ります。
ところがここには真偽値を渡すこともでき、しかもtrueなら1、falseなら-1と解釈されます。
これは何の役にも立っていないだけでなく、PHPの型の通常の使い方とも異なっています。
このRFCでは、引数に真偽値を渡すことを非推奨とします。
Accessing static members on traits
賛成40反対0で受理。
現在は、traitのstaticプロパティに直接アクセスすることができます。
trait T {
public static $foo;
}
class C {
use T;
}
var_dump(T::$foo); // アクセス可
これは概念的に正しくないため、様々な問題を引き起こします。
たとえばselfが行方不明になります。
通常selfはtraitではなく自分自身のクラスを表しますが、この文脈においては自分自身のクラスが存在しません。
このRFCでは、trait上のstaticメンバやstaticメソッドへの直接アクセスを禁止します。
https://github.com/php/php-src/pull/4829#issuecomment-542224541に、この機能を使用しているプロジェクトの調査結果があります。
strptime()
賛成35反対3で受理。
strptimeは、日付時刻文字列をパースして配列に変換します。
この関数はWindowsでは動作せず、またOSによっては異なる動作になることがあります。
注意: 内部では、この関数はシステムの C ライブラリ関数 strptime() をコールしています。 このライブラリ関数は、OS によって挙動が異なることがあります。 date_parse_from_format() はこの問題の影響を受けないので、 date_parse_from_format() を使うことを推奨します。
glibcではなくmuslを使用しているAlpineなどのディストリビューションでは予期せぬ挙動になる可能性があります。
またstrptimeはロケールベースなので、別スレッドで実行されているコードの影響を受けます。
注意事項にもあるように、代替手段であるdate_parse_from_formatは環境に関わらず常に同じ動作になります。
またDateTime::createFromFormatは配列のかわりにDateTimeオブジェクトを返し、IntlDateFormatter::parseはローカライズも考慮された強力な代替手段です。
従ってstrptimeを廃止し、移植性の高い代替手段を採用します。
strftime() and gmtstrftime()
賛成29反対9で受理。
strftimeやgmstrftimeも、strptimeと同じくプラットフォームに依存する問題を抱えています。
これらはWindowsでも使用可能ですが、Linuxと異なる動作になる場合があります。
Alpineはタイムゾーン関連のフォーマットを正しく解釈できず、さらにロケールに依存するためスレッドセーフの問題も健在です。
dateやDateTime::format()、IntlDateFormatter::format()がより優れた代替となります。
このRFCでは、strftimeとgmtstrftimeを非推奨とします。
mhash*() function family
賛成38反対0で受理。
mhashは、PHP5.3でHashと互換され、PHP7.0で完全に統合されました。
常に使用可能なhash_*
関数と異なり、mhash*
関数は手動で有効にしないと使用できません。
このRFCでは、mhash()
・mhash_keygen_s2k()
・mhash_count()
・mhash_get_block_size()
・mhash_get_hash_name()
の各関数を廃止し、かわりに標準のHash関数を推奨します。
ctype_*() function family accepts int parameters
賛成34反対2で受理。
ctype_*
関数は、引数として文字列型以外にint型も受け入れます。
そして128-255の数値が渡された場合、文字列ではなく1つの文字のASCII値として解釈されます。
それ以外の整数の場合、その文字列として解釈されます。
これはよくある混乱の元であり、特に整数値をctype_digit()に渡すときに発生します。
ctype_digit(5)
はfalseですがctype_digit("5")
はtrueです。
これは、整数型文字列と整数は同じ動作になるという、PHPの基本原則と異なる結果です。
このRFCでは、ctype_*()
関数に文字型以外の型を渡すことを非推奨にします。
次のメジャーバージョンでは、一般的な関数と同様、数値型から文字列型へと暗黙の型変換がされるようになります。
ASCIIコードポイントをチェックする必要がある場合は、明示的にchr関数を通す必要があります。
Return by reference with void type
賛成39反対1で受理。
現在、返り値がvoidの関数をリファレンスで定義することが可能です。
function &test(): void {}
voidは返り値を持たないため、この定義は矛盾しています。
そこで、このような関数シグネチャについては、コンパイル時に非推奨の警告を出すようにします。
NIL constant defined by the IMAP extension
賛成35反対0で受理。
IMAPで定義されている定数NILはNULLと同じだと思われがちですが、実際の値は0
です。
従って、この定数を非推奨とします。
Calling overloaded pgsql functions without the connection argument
賛成36反対1で受理。
pgsql関数には、引数としてコネクションを渡さずに呼び出すことのできるオーバーロードされた関数が一部存在します。
例としてpg_queryは、どちらの方法でも実行可能です。
pg_query($queryString);
pg_query($connection, $queryString);
PHPはオーバーロードをサポートしておらず、これは理解しがたい動作となります。
このRFCでは、コネクションを渡さない呼び出し方を非推奨とします。
$num_points parameter of image(open|filled)polygon
賛成20反対9で受理。
imagepolygon、imageopenpolygon、imagefilledpolygonは、PHP8.0以降では第三引数$num_points
を省略可能です。
このRFCでは、4引数を受け付ける従来のシグネチャを非推奨とし、3引数を正として採用します。
実際の実装。
以下に修正方法を示します。
そもそも4つの点から三角形を描くことは稀であり、バグの可能性を疑った方がよいでしょう。
$points = [10, 10, 10, 90, 90, 90, 90, 10];
// 全座標を使う
imagepolygon($im, $points, count($points)/2, 0x000000); // before
imagepolygon($im, $points, 0x000000); // after
// 3座標を使う
imagepolygon($im, $points, 3, 0x000000); // before
imagepolygon($im, array_slice($points, 0, 6), 0x000000); // after
mysqli::init()
賛成34反対0で受理。
mysqli::init()はmysqli_init()
と同じです。
が、静的メソッドではなくインスタンスメソッドなので、使い方が紛らわしいです。
引数なしでmysqli::__construct()を読んだ場合、初期化はされているけど接続はされていないmysqliオブジェクトが既に生成されているため、その後mysqli::init()
を呼んでも何の意味もありません。
このメソッドの有用な使用例はただひとつ、ポリモーフィズムだけです。
mysqliを継承した際にparent::__construct()
ではなくparent::init()
を使う場合だけです。
このRFCでは、parent::init()
を非推奨とし、parent::__construct()
を使うことを推奨します。
class test extends mysqli
{
public function __construct($host, $user, $passwd, $db, $port, $socket) {
// parent::init();
// 今後は↑ではなく↓
parent::__construct();
parent::real_connect($host, $user, $passwd, $db, $port, $socket);
}
}
filter.default ini setting
賛成28反対4で受理。
ini設定filter.defaultは、全ての入力値をフィルタリングします。
このフィルタで \$_GET, \$_POST, \$_COOKIE, \$_REQUEST および \$_SERVER のすべてのデータをフィルタリングします。
たとえばfilter.default=add_slashes
を設定すると、PHP5.4で滅ぼしたはずのマジッククォートを復活させることができます。
filter.default=special_chars
を使うとhtmlspecialcharsされます。
マジッククォートは削除されるべくして削除されましたが、filter.default
はそれ以上に問題な機能です。
なぜならば、マジッククォートより幅広いフィルタを提供しており、そしてその存在がほとんど知られていないからです。
このRFCでは、filter.default
がデフォルトのunsafe_raw
以外の値になっていた場合に非推奨の警告を発生します。
filter.default_options
も次のメジャーバージョンで一緒に削除されます。
auto_detect_line_endings ini setting
賛成38反対0で受理。
ini設定auto_detect_line_endingsは、関数fileとfgetsにおいて、"\r"も改行文字として検出するようにします。
これは2001年に販売停止されたクラシックMacOSで使用されていた文字コードであり、今このようなシステムと相互運用する意味は全くありません。
このRFCでは、auto_detect_line_endingsを非推奨とします。
ssl_method option to SoapClient constructor
賛成25反対1で受理。
SoapClient::__constructのオプションssl_methodを非推奨にします。
これはSSL/TLSのバージョンを指定するオプションで、SOAP_SSL_METHOD_TLS・SOAP_SSL_METHOD_SSLv2・SOAP_SSL_METHOD_SSLv3・SOAP_SSL_METHOD_SSLv23の4定数が規定されています。
SOAP_SSL_METHOD_TLSとSOAP_SSL_METHOD_SSLv23は、現在は中身が同じです。
これはデフォルトであり、TLS1.0以上という意味になります。
SOAP_SSL_METHOD_SSLv2は常にエラーとなります。
何故ならば、PHPはSSL2をサポートしていないからです。
SOAP_SSL_METHOD_SSLv3は、サポートされていればSSL3を選択します。
しかしSSL3は既に安全ではないので、これを使うべきではないでしょう。
stream_context_createを使えばあらゆるバージョンのSSL/TLSを指定することができ、contextオプションでSoapClientに渡すことができるので、こちらが上位互換になります。
従ってこのRFCでは、オプションssl_methodを非推奨とし、オプションcontextの使用を推奨します。
FILTER_SANITIZE_STRING
賛成36反対0で受理。
これは非常に胡散臭いフィルタで、使い道はほとんどありません。
nullバイトを除去し、"
と'
をエスケープし、HTMLタグを削除します。
しかしこのコメントで指摘されているように、strip_tagsと同じ機能ではありません。
このフィルタは何に対してのサニタイズなのかが明確にされておらず、誤解を招く危険な機能です。
使用する文脈を考慮することなく、どのような出力に対しても文字列を安全にする魔法のような汎用的フィルタを意図しているようですが、本来は文脈に特化したエスケープ処理を使用するべきです。
またこの名前は、何もしないデフォルトの処理であるかのように受け取られる可能性もあります。
実際のデフォルトはFILTER_UNSAFE_RAW
ですが、これは危険であり使ってはいけなさそうな名前のように感じられます。
実際はFILTER_SANITIZE_STRING
のほうがデータが破損します。
この提案では、FILTER_SANITIZE_STRING
およびその別名であるFILTER_SANITIZE_STRIPPED
を非推奨にします。
oci8.old_oci_close_semantics ini setting
賛成27反対0で受理。
ini設定oci8.old_oci_close_semantics
を有効にするとoci_closeは実際には接続を閉じず、何もしません。
この設定は後方互換性だけのために導入されたもので、その後長期間が経過したので、そろそろ不要です。
この提案では、ini設定oci8.old_oci_close_semantics
を非推奨にします。
odbc_result_all()
賛成34反対0で受理。
odbc_result_allは、クエリの結果をHTMLテーブルとして表示します。
しかもデータはエスケープされません。
従って、この関数の有用性は非常に疑わしく、デバッグに使用する以外で使った場合は直ちに危険となります。
この提案では、関数odbc_result_allを非推奨にします。
却下された提案
以下の提案は却下されました。
PHP8.1に入ることはありません。
get_class(), get_parent_class() and get_called_class() without argument
賛成21反対21で却下。
PHP7.2でget_class()にNULLを渡すことが禁止されました。
しかし、引数なしで呼び出すことは未だ可能であり、これは想定しがたい動作となります。
このRFCでは、引数なしでget_class、get_parent_class、get_called_classを呼び出すことを非推奨とします。
get_called_classは引数なしの構文しかないので、関数自体が非推奨となります。
t fopen mode
賛成13反対17で却下。
fopenには、Windowsでのみ有効なフラグb
とt
が存在します。
b
はデフォルトで、ファイルはそのまま開かれます。
t
を指定するとCRとCELFの自動変換が行われます。
ドキュメントではこのように警告されています。
注意:互換性維持のために、't' モードを使用または依存しているコードを書き直し、 正しい改行コードと 'b' モードを代わりに使用することが、 強く推奨されます。
このRFCでは、t
の使用を非推奨とします。
t
以外のフラグ、およびb
の明示的な表示は引き続きサポートされます。
proc_openのような一部の関数はデフォルトのモードがt
になっています。
t
がデフォルトである関数は、当面は非推奨の対象外です。
取り下げられた提案
この項目は、MLの議論などを経てRFCから取り下げられました。
PHP8.1に入ることはありません。
get_browser() function
関数get_browserは、かつてはユーザランド実装よりはるかに遅いという問題がありました。
PHP7のパッチにおいて、この問題は修正されました。
DatePeriod::__construct()
DatePeriod::__construct()はシグネチャが3種類もあるため、ファクトリーメソッドに置き換えるべきです。
ただし、コンストラクタを非推奨とする前にファクトリーメソッドを導入する必要があるでしょう。
Passing a method name as the first parameter to ReflectionMethod::__construct()
ReflectionMethod::__construct()は、('クラス名', 'メソッド名')
だけではなく('クラス名::メソッド名')
という引数も受け付けます。
先にReflectionMethod::fromMethodName()
メソッドを導入し、その後オーバーロードを非推奨にすべきでしょう。
unserialize_callback_func INI setting
unserializeの際にクラスが見つからないと__PHP_Incomplete_Class
というクラスが生成されますが、ini設定unserialize_callback_func
を使うとかわりに例外を出したりすることができます。
unserialize関数の引数でそのような動作ができるオプションを導入したのち、unserialize_callback_funcを削除すべきです。
Predefined variable $http_response_header
PHPのストリームを使ってHTTPリクエストを送信すると、ローカル変数$http_response_headerが生成されます。
これは追加情報を与えるための凡そ最もひどい方法であり、我々は$php_errormsgをはじめこのような機能をすべて削除してきました。
ユーザランドでストリームラッパーを書き替えることで$http_response_header
を作らないようにする対処も可能ですが、これは同時にhttps://www.php.net/file_get_contentsが使えなくなることを意味します。
// $http_response_headerがつくられる
$url = 'https://example.org';
$response = file_get_contents($url);
$headers = $http_response_header;
// $http_response_headerがつくられない
$url = 'https://example.org';
$f = fopen($url, 'r');
$reponse = stream_get_contents($f);
$headers = stream_get_meta_data($f)['wrapper_data'];
リクエストが失敗する場合、事態はさらに複雑です。
fopen()
が失敗するためstream_get_contents()
が呼べなくなります。
そのためにignore_errors
オプションを設定する必要があります。
$url = 'https://example.org/file_not_found';
$context = stream_context_create([
'http' => [
'ignore_errors' => true,
],
]);
$f = fopen($url, 'r', context: $context);
$response = stream_get_contents($f);
$headers = stream_get_meta_data($f)['wrapper_data'];
これらの代替案はユーザランドで実行可能ですが、良いものではとてもありません。
レスポンスヘッダを返す関数を追加するなどの対策が必要でしょう。
感想
23の提案のうち21件が受理、2件が却下されました。
一見多いですが、ほとんどの機能については現行プログラムには影響がない、どころか存在すら知らなかったレベルの話だと思います。
わざわざ意図して使わないかぎり引っかからないようなものばかりなので、PHP8.0や7.4のDeprecatedと違って、さほど気にする必要はないでしょう。
個人的にはSmartyでstrftimeが使われているくらいですかね。
え?今どきSmartyなんて使わないって?そうですね。