普段ドキュメントに@deprecated
って書いてるんですが、これは公式ではなくphpDocumentorの方言に過ぎません。
しかしまあ普及率も高いし公式でも使いたいよねってことと、あと標準関数のE_DEPRECATEDと動きを合わせたいということで、PHP8.4でアトリビュートとして実装されることになりました。
以下はPHP RFC: #[\Deprecated] Attributeの紹介です。
#[\Deprecated] Attribute
Introduction
PHPの内部関数やクラス定数には非推奨マークを設定することができます。
これによってそれらを使うとE_DEPRECATEDの警告が発生し、Reflectionから利用することができます。
しかし、ユーザランドからは同じことができません。
非推奨関数を呼び出すときにtrigger_error()を呼び出してE_USER_DEPRECATEDエラーを発生させるか、/** @deprecated */
コメントをReflectionで読み取ることで似たようなことはできますが、これらは内部関数と同じ処理ではないため内部関数かユーザランドかによって処理の分岐が必要です。
ReflectionFunctionAbstract::isDeprecated()
はユーザランド関数には常にfalseを返し、逆にドキュメントコメントは内部関数に付けられません。
クラス定数については同等の機能そのものがありません。
Proposal
新しいアトリビュート#[\Deprecated]
を導入します。
#[Attribute(Attribute::TARGET_METHOD|Attribute::TARGET_FUNCTION|Attribute::TARGET_CLASS_CONSTANT)]
final class Deprecated
{
public readonly ?string $message;
public readonly ?string $since;
public function __construct(?string $message = null, ?string $since = null) { /* … */ }
}
ユーザランド関数、メソッド、クラス定数にこのアトリビュートを設定すると、要素に内部フラグZEND_ACC_DEPRECATEDが設定され、内部関数のE_DEPTECATEDと一致する動作になります。
すなわち、以下のようになります。
・関数またはメソッドであれば、ReflectionFunctionAbstract::isDeprecated()
がtrueになる。
・クラス定数であればReflectionClassConstant::isDeprecated()
がtrueになる。
・関数を呼び出すとE_USER_DEPRECATEDが発生する。
・クラス定数を呼び出すとE_USER_DEPRECATEDが発生する。
$message
は、関数やクラス定数を呼び出したときのエラーメッセージに入ります。
もちろん他のパラメータと同様、静的解析ツールからも使用可能です。
$since
もエラーメッセージに含まれ、この要素がいつから非推奨になったを表します。
この中身は、検証されていない自由形式文字列である可能性があります。
バージョン番号、日付、その他ライブラリが適切であると見做すその他の値が含まれます。
ENUMのcaseは、内部的にはクラス定数として実装されています。
caseに#[\Deprecated]
を追加すると、期待通りに動作します。
Examples
様々な例。
<?php
#[\Deprecated]
function test() {
}
#[\Deprecated("use test() instead")]
function test2() {
}
#[\Deprecated("use test() instead", since: "2.4")]
function test3() {
}
#[\Deprecated(since: "2024-05-07")]
function test4() {
}
class Clazz {
#[\Deprecated]
public const OLD_WAY = 'foo';
#[\Deprecated]
function test() {
}
#[\Deprecated("use test() instead")]
function test2() {
}
}
enum MyEnum {
#[\Deprecated]
case OldCase;
}
test(); // Deprecated: Function test() is deprecated in test.php on line 37
test2(); // Deprecated: Function test2() is deprecated, use test() instead in test.php on line 38
test3(); // Deprecated: Function test2() is deprecated since 2.4, use test() instead in test.php on line 39
test4(); // Deprecated: Function test4() is deprecated since 2024-05-07 in test.php on line 40
call_user_func("test"); // Deprecated: Function test() is deprecated in test.php on line 41
$cls = new Clazz();
$cls->test(); // Deprecated: Method Clazz::test() is deprecated in test.php on line 44
$cls->test2(); // Deprecated: Method Clazz::test2() is deprecated, use test() instead in test.php on line 45
Clazz::OLD_WAY; // Deprecated: Constant Clazz::OLD_WAY is deprecated in test.php on line 46
MyEnum::OldCase; // Deprecated: Enum case MyEnum::OldCase is deprecated in test.php on line 48
call_user_func([$cls, "test"]); // Deprecated: Method Clazz::test() is deprecated in test.php on line 50
<?php
#[\Deprecated]
function test() {
}
$r = new ReflectionFunction('test');
var_dump($r->isDeprecated()); // bool(true)
<?php
class Clazz {
#[\Deprecated]
public const OLD_WAY = 'foo';
}
$r = new ReflectionClassConstant(Clazz::class, 'OLD_WAY');
var_dump($r->isDeprecated()); // bool(true)
<?php
#[\Deprecated]
function test1() {
}
#[\Deprecated()]
function test2() {
}
#[\Deprecated("use test() instead")]
function test3() {
}
#[\Deprecated("use test() instead", since: "2.4")]
function test4() {
}
#[\Deprecated(since: "2024-05-07")]
function test5() {
}
$reflection = new ReflectionFunction('test1');
var_dump($reflection->getAttributes()[0]->newInstance());
/*
object(Deprecated)#3 (2) {
["message"]=>
NULL
["since"]=>
NULL
}
*/
$reflection = new ReflectionFunction('test2');
var_dump($reflection->getAttributes()[0]->newInstance());
/*
object(Deprecated)#2 (2) {
["message"]=>
NULL
["since"]=>
NULL
}
*/
$reflection = new ReflectionFunction('test3');
var_dump($reflection->getAttributes()[0]->newInstance());
/*
object(Deprecated)#1 (2) {
["message"]=>
string(18) "use test() instead"
["since"]=>
NULL
}
*/
$reflection = new ReflectionFunction('test4');
var_dump($reflection->getAttributes()[0]->newInstance());
/*
object(Deprecated)#3 (2) {
["message"]=>
string(18) "use test() instead"
["since"]=>
string(3) "2.4"
}
*/
$reflection = new ReflectionFunction('test5');
var_dump($reflection->getAttributes()[0]->newInstance());
/*
object(Deprecated)#2 (2) {
["message"]=>
NULL
["since"]=>
string(10) "2024-05-07"
}
*/
プルリクエストからさらなるテストケースを確認できます。
Backward Incompatible Changes
下位互換性のない変更。
Deprecated
は、グローバル空間のクラス名として使用できなくなります。
GitHubを軽く検索したところでは173件の一致が見つかりましたが、その大半は名前空間内で定義されているようです。
Proposed PHP Version(s)
PHP8.4。
Open Issues
少しだけ指摘されています。
Future Scope
今後の展望であり、このRFCには含まれていません。
・クラスなど、今は非推奨をサポートしていないその他のターゲットにも#[\Deprecated]
を使えるようにする。
・メッセージ以外に、IDEが使用できるヒントなどその他のメタデータを追加する。
Proposed Voting Choices
賛成23反対6で受理されました。
Patches and Tests
Implementation
・https://github.com/php/php-src/commit/72c874691b99a88feada151fca337ff9e471c31e
・https://github.com/php/php-src/commit/29f98e748568ebd66aaae061c0dcefbba92ca058
References
感想
@deprecated
わりと使うので助かる。
@deprecated
はただのマーカーであり、強制力は特にありませんでした。
といってわざわざ手動でE_USER_DEPRECATEDを発生させるのも面倒だしなあと思っていたのですが、今後はいいかんじに気軽にDeprecatedを出して行けそうですね。
アトリビュートは未対応のバージョンではただのコメントなので、#[\Deprecated]
と書いてあってもPHP8.3以前でも動作するので、互換性という点でも優れていますね。