【PHP8.1】PHP8.1で削除される機能

Nikitaをはじめとする一部のstrictぺちぱー達は、PHPのレガシー機能の削除に[熱心に](https://qiita.com/rana_kualu/items/db1ce401df627c46544a)[取り](https://wiki.php.net/rfc/deprecations_php_7_4)[組ん](https://wiki.php.net/rfc/deprecations_php_7_3)[でい](https://wiki.php.net/rfc/deprecations_php_7_2)[ます](https://wiki.php.net/rfc/remove_deprecated_functionality_in_php7)。
まあ実際、初期のPHPには特にですが、いったいなんでこんなものをという思い付きのような機能がたくさんありましたからね。

そのあたりはPHP7からPHP8.0にかけてだいぶ綺麗に片付けられてきたのですが、まだまだおかしなものも残っています。
そんなわけで以下は、それらを削除しようというRFC、[Deprecations for PHP 8.1](https://wiki.php.net/rfc/deprecations_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](https://www.php.net/manual/ja/function.date-sunrise.php)と[date_sunset](https://www.php.net/manual/ja/function.date-sunset.php)の2関数のシグネチャは以下のようになっています。

```php
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](https://www.php.net/manual/ja/function.date-sun-info.php)に取って代わられました。

```php
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](https://github.com/php/php-src/pull/4423)にあります。


## key(), current(), next(), prev(), and reset() on objects

賛成48反対0で受理。

配列ポインタを操作するために使用される[key](https://www.php.net/manual/ja/function.key)などの配列関数は、実はオブジェクトも受け取れます。
オブジェクトに対する走査はmangled propertyテーブルを使用します。
すなわち、これらの関数の動作は[get_mangled_object_vars](https://www.php.net/manual/ja/function.get-mangled-object-vars.php)と同じということです。

多くの人にとって、これは予想外の動作です。
Iteratorインターフェイスが実装されていれば、`key($object)`は`$object->key()`が呼び出されるだろうと考えるのが普通だからです。
実際には、PHP7.4以前では`key()`とイテレータが同じように動くオブジェクトは`ArrayObject`だけでした。

この問題を解決する方法は2つあります。
ひとつめは、単純にオブジェクトの仕様を非推奨とする方法です。
かわりに明示的キャストや`get_mangled_object_vars()`の使用を要求します。

もうひとつは、これらの関数の動作をIteratorに揃えることです。
この際問題になるのは、[IteratorAggregate::getIterator()](https://www.php.net/manual/ja/iteratoraggregate.getiterator)や内部`get_iterator()`ハンドラは、最初に一回だけ呼び出されなければならないということです。
これは、複数の独立した関数で構成されている配列関数では不可能です。

そのため、このRFCではオブジェクトに対する`key()`、`current()`、`next()`、`prev()`、`reset()`を非推奨とします。



## mb_check_encoding() without argument

賛成44反対0で受理。

[mb_check_encoding](https://www.php.net/manual/ja/function.mb-check-encoding.php)は、引数を渡さずに呼び出すことができます。
ドキュメントには、『省略した場合は、 リクエスト開始時からのすべての入力が対象となります。』とあります。

ところで実装はこうなっています。

```c
/* FIXME: Actually check all inputs, except $_FILES file content. */
if (MBSTRG(illegalchars) == 0) {
    RETURN_TRUE;
}
RETURN_FALSE;
```

この関数に対する信頼を失わせるのに十分な記述でしょう。

さらに調べてみると、場合によってはこの関数は正しい動作をすることもあるということがわかりました。
私の推測では、この仕様は[encoding_translation](https://www.php.net/manual/ja/mbstring.http.php)と一緒に使われる想定だったのではないかと思われます。

全体としてこの機能は不完全であり、混乱を招きます。
有名なパッケージで引数無しの`mb_check_encoding()`を使っているものはありません。

従って、このRFCでは引数なしの`mb_check_encoding()`を非推奨とします。


## FILE_BINARY and FILE_TEXT constants

賛成42反対1で受理。

PHP6との互換のため、PHP5.2.7において定数[FILE_BINARYとFILE_TEXT](https://www.php.net/manual/ja/filesystem.constants.php)が導入されましたが、この定数は何の効果もありません。
特に混乱を招きやすい理由として、[fopen](https://www.php.net/manual/ja/function.fopen)が`b`や`t`モードをサポートしているのに、この定数は全く効かないことです。

このRFCでは、定数FILE_BINARYとFILE_TEXTを非推奨とします。


## Passing bool for $value argument of IntlCalendar::roll()

賛成38反対0で受理。

[IntlCalendar::roll()](https://www.php.net/manual/ja/intlcalendar.roll.php)の引数`$amountOrUpOrDown`は、足し引きする量を受け取ります。
ところがここには真偽値を渡すこともでき、しかもtrueなら1、falseなら-1と解釈されます。
これは何の役にも立っていないだけでなく、PHPの型の通常の使い方とも異なっています。

このRFCでは、引数に真偽値を渡すことを非推奨とします。


## Accessing static members on traits

賛成40反対0で受理。

現在は、traitのstaticプロパティに直接アクセスすることができます。

```php
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](https://github.com/php/php-src/pull/4829#issuecomment-542224541)に、この機能を使用しているプロジェクトの調査結果があります。


## strptime()

賛成35反対3で受理。

[strptime](https://www.php.net/manual/ja/function.strptime.php)は、日付時刻文字列をパースして配列に変換します。
この関数はWindowsでは動作せず、またOSによっては異なる動作になることがあります。

:::note warn
注意: 内部では、この関数はシステムの C ライブラリ関数 strptime() をコールしています。 このライブラリ関数は、OS によって挙動が異なることがあります。 date_parse_from_format() はこの問題の影響を受けないので、 date_parse_from_format() を使うことを推奨します。
:::

glibcではなくmuslを使用しているAlpineなどのディストリビューションでは予期せぬ挙動になる可能性があります。
またstrptimeはロケールベースなので、別スレッドで実行されているコードの影響を受けます。

注意事項にもあるように、代替手段である[date_parse_from_format](https://www.php.net/manual/ja/function.date-parse-from-format.php)は環境に関わらず常に同じ動作になります。
また[DateTime::createFromFormat](https://www.php.net/manual/ja/datetime.createfromformat.php)は配列のかわりにDateTimeオブジェクトを返し、[IntlDateFormatter::parse](https://www.php.net/manual/ja/intldateformatter.parse.php)はローカライズも考慮された強力な代替手段です。

従ってstrptimeを廃止し、移植性の高い代替手段を採用します。


## strftime() and gmtstrftime()

賛成29反対9で受理。

[strftime](https://www.php.net/manual/ja/function.strftime.php)や[gmstrftime](https://www.php.net/manual/ja/function.gmstrftime.php)も、strptimeと同じくプラットフォームに依存する問題を抱えています。
これらはWindowsでも使用可能ですが、Linuxと異なる動作になる場合があります。
Alpineはタイムゾーン関連のフォーマットを正しく解釈できず、さらにロケールに依存するためスレッドセーフの問題も健在です。

[date](https://www.php.net/manual/ja/function.date)や[DateTime::format()](https://www.php.net/manual/ja/datetime.format.php)、[IntlDateFormatter::format()](https://www.php.net/manual/ja/intldateformatter.format.php)がより優れた代替となります。

このRFCでは、strftimeとgmtstrftimeを非推奨とします。


## mhash*() function family

賛成38反対0で受理。

[mhash](https://www.php.net/manual/ja/book.mhash.php)は、PHP5.3で[Hash](https://www.php.net/manual/ja/book.hash.php)と互換され、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()](https://www.php.net/manual/ja/function.ctype-digit.php)に渡すときに発生します。
`ctype_digit(5)`はfalseですが`ctype_digit("5")`はtrueです。
これは、整数型文字列と整数は同じ動作になるという、PHPの基本原則と異なる結果です。

このRFCでは、`ctype_*()`関数に文字型以外の型を渡すことを非推奨にします。
次のメジャーバージョンでは、一般的な関数と同様、数値型から文字列型へと暗黙の型変換がされるようになります。

ASCIIコードポイントをチェックする必要がある場合は、明示的に[chr](https://www.php.net/manual/ja/function.chr)関数を通す必要があります。


## Return by reference with void type

賛成39反対1で受理。

現在、返り値がvoidの関数をリファレンスで定義することが可能です。

```php
function &test(): void {}
```

voidは返り値を持たないため、この定義は矛盾しています。

そこで、このような関数シグネチャについては、コンパイル時に非推奨の警告を出すようにします。


## NIL constant defined by the IMAP extension

賛成35反対0で受理。

IMAPで定義されている定数[NIL](https://www.php.net/manual/ja/imap.constants.php)はNULLと同じだと思われがちですが、実際の値は`0`です。

従って、この定数を非推奨とします。


## Calling overloaded pgsql functions without the connection argument

賛成36反対1で受理。

pgsql関数には、引数としてコネクションを渡さずに呼び出すことのできるオーバーロードされた関数が一部存在します。
例として[pg_query](https://www.php.net/pg_query)は、どちらの方法でも実行可能です。

```php
pg_query($queryString);
pg_query($connection, $queryString);
```

PHPはオーバーロードをサポートしておらず、これは理解しがたい動作となります。

このRFCでは、コネクションを渡さない呼び出し方を非推奨とします。


## $num_points parameter of image(open|filled)polygon

賛成20反対9で受理。


[imagepolygon](https://www.php.net/manual/ja/function.imagepolygon.php)、[imageopenpolygon](https://www.php.net/manual/ja/function.imageopenpolygon)、[imagefilledpolygon](https://www.php.net/manual/ja/function.imagefilledpolygon)は、PHP8.0以降では第三引数`$num_points`を省略可能です。

このRFCでは、4引数を受け付ける従来のシグネチャを非推奨とし、3引数を正として採用します。
[実際の実装](https://github.com/php/php-src/pull/6789)。

以下に修正方法を示します。
そもそも4つの点から三角形を描くことは稀であり、バグの可能性を疑った方がよいでしょう。

```php
$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()](https://www.php.net/manual/ja/mysqli.init.php)は`mysqli_init()`と同じです。
が、静的メソッドではなくインスタンスメソッドなので、使い方が紛らわしいです。

引数なしで[mysqli::__construct()](https://www.php.net/manual/ja/mysqli.construct.php)を読んだ場合、初期化はされているけど接続はされていないmysqliオブジェクトが既に生成されているため、その後`mysqli::init()`を呼んでも何の意味もありません。
このメソッドの有用な使用例はただひとつ、ポリモーフィズムだけです。
mysqliを継承した際に`parent::__construct()`ではなく`parent::init()`を使う場合だけです。

このRFCでは、`parent::init()`を非推奨とし、`parent::__construct()`を使うことを推奨します。


```php
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](https://www.php.net/manual/ja/filter.configuration.php)は、全ての入力値をフィルタリングします。

:::note warn
このフィルタで \$_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](https://www.php.net/manual/ja/filesystem.configuration.php#ini.auto-detect-line-endings)は、関数[file](https://www.php.net/manual/ja/function.file.php)と[fgets](https://www.php.net/manual/ja/function.fgets.php)において、"\r"も改行文字として検出するようにします。
これは2001年に販売停止されたクラシックMacOSで使用されていた文字コードであり、今このようなシステムと相互運用する意味は全くありません。

このRFCでは、auto_detect_line_endingsを非推奨とします。


## ssl_method option to SoapClient constructor

賛成25反対1で受理。

[SoapClient::__construct](https://www.php.net/manual/ja/soapclient.construct.php)のオプション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](https://www.php.net/manual/ja/function.stream-context-create.php)を使えばあらゆるバージョンのSSL/TLSを指定することができ、contextオプションでSoapClientに渡すことができるので、こちらが上位互換になります。

従ってこのRFCでは、オプションssl_methodを非推奨とし、オプションcontextの使用を推奨します。


## FILTER_SANITIZE_STRING

賛成36反対0で受理。

これは非常に胡散臭いフィルタで、使い道はほとんどありません。
nullバイトを除去し、`"`と`'`をエスケープし、HTMLタグを削除します。
しかし[このコメント](https://www.php.net/manual/en/filter.filters.sanitize.php#118186)で指摘されているように、[strip_tags](https://www.php.net/manual/ja/function.strip-tags.php)と同じ機能ではありません。

このフィルタは何に対してのサニタイズなのかが明確にされておらず、誤解を招く危険な機能です。
使用する文脈を考慮することなく、どのような出力に対しても文字列を安全にする魔法のような汎用的フィルタを意図しているようですが、本来は文脈に特化したエスケープ処理を使用するべきです。

またこの名前は、何もしないデフォルトの処理であるかのように受け取られる可能性もあります。
実際のデフォルトは`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](https://www.php.net/manual/ja/function.oci-close.php)は実際には接続を閉じず、何もしません。
この設定は後方互換性だけのために導入されたもので、その後長期間が経過したので、そろそろ不要です。

この提案では、ini設定`oci8.old_oci_close_semantics`を非推奨にします。

## odbc_result_all()

賛成34反対0で受理。

[odbc_result_all](https://www.php.net/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を渡すことが禁止](https://qiita.com/rana_kualu/items/40ebed78742bfdbd1065#get_class-disallow-null-parameter)されました。
しかし、引数なしで呼び出すことは未だ可能であり、これは想定しがたい動作となります。

このRFCでは、引数なしで[get_class](https://www.php.net/manual/ja/function.get-class.php)、[get_parent_class](https://www.php.net/manual/ja/function.get-parent-class.php)、[get_called_class](https://www.php.net/manual/ja/function.get-called-class.php)を呼び出すことを非推奨とします。
get_called_classは引数なしの構文しかないので、関数自体が非推奨となります。


## t fopen mode

賛成13反対17で却下。

[fopen](https://www.php.net/manual/ja/function.fopen.php)には、Windowsでのみ有効なフラグ`b`と`t`が存在します。
`b`はデフォルトで、ファイルはそのまま開かれます。
`t`を指定するとCRとCELFの自動変換が行われます。
ドキュメントではこのように警告されています。

:::note warn
注意:互換性維持のために、't' モードを使用または依存しているコードを書き直し、 正しい改行コードと 'b' モードを代わりに使用することが、 強く推奨されます。
:::

このRFCでは、`t`の使用を非推奨とします。
`t`以外のフラグ、および`b`の明示的な表示は引き続きサポートされます。

[proc_open](https://www.php.net/manual/ja/function.proc-open.php)のような一部の関数はデフォルトのモードが`t`になっています。
`t`がデフォルトである関数は、当面は非推奨の対象外です。



# 取り下げられた提案

この項目は、MLの議論などを経てRFCから取り下げられました。
PHP8.1に入ることはありません。

## get_browser() function

関数[get_browser](https://www.php.net/manual/ja/function.get-browser.php)は、かつてはユーザランド実装よりはるかに遅いという問題がありました。
PHP7の[パッチ](https://github.com/php/php-src/pull/2242)において、この問題は修正されました。

## DatePeriod::__construct()

[DatePeriod::__construct()](https://www.php.net/manual/ja/dateperiod.construct.php)はシグネチャが3種類もあるため、ファクトリーメソッドに置き換えるべきです。
ただし、コンストラクタを非推奨とする前にファクトリーメソッドを導入する必要があるでしょう。

## Passing a method name as the first parameter to ReflectionMethod::__construct()

[ReflectionMethod::__construct()](https://www.php.net/manual/ja/reflectionmethod.construct.php)は、`('クラス名', 'メソッド名')`だけではなく`('クラス名::メソッド名')`という引数も受け付けます。
先に`ReflectionMethod::fromMethodName()`メソッドを導入し、その後オーバーロードを非推奨にすべきでしょう。

## unserialize_callback_func INI setting

[unserialize](https://www.php.net/manual/ja/function.unserialize.php)の際にクラスが見つからないと`__PHP_Incomplete_Class`というクラスが生成されますが、ini設定`unserialize_callback_func`を使うとかわりに例外を出したりすることができます。
unserialize関数の引数でそのような動作ができるオプションを導入したのち、[unserialize_callback_func](https://www.php.net/manual/ja/var.configuration.php)を削除すべきです。


## Predefined variable $http_response_header

PHPのストリームを使ってHTTPリクエストを送信すると、ローカル変数[$http_response_header](https://www.php.net/manual/ja/reserved.variables.httpresponseheader.php)が生成されます。
これは追加情報を与えるための凡そ最もひどい方法であり、我々は[$php_errormsg](https://www.php.net/manual/ja/reserved.variables.phperrormsg.php)をはじめこのような機能をすべて削除してきました。

ユーザランドでストリームラッパーを書き替えることで`$http_response_header`を作らないようにする対処も可能ですが、これは同時に[https://www.php.net/file_get_contents](https://www.php.net/manual/ja/function.file-get-contents.php)が使えなくなることを意味します。

```php
// $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`オプションを設定する必要があります。

```php
$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](https://www.smarty.net/docs/ja/)で[strftime](https://www.smarty.net/docs/ja/language.modifier.date.format.tpl)が使われているくらいですかね。
え？今どきSmartyなんて使わないって？そうですね。