20
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【PHP8.4】Deprecatedを公式に書けるようになる

Last updated at Posted at 2024-11-11

普段ドキュメントに@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以前でも動作するので、互換性という点でも優れていますね。

20
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?