PHP Exceptionのpropertyにあるcodeはuser-defined error code

  • php

Introduction

仕事でPHPStanの修正をしている時、 Exception classのconstructorでcodeを指定しているコードに出くわした。 Exception におけるcodeの扱いについて記述している記事がなかったので php/php-src のコードにあたってみた。

簡易的な調査

Interface定義

php.netのUser Contributed Notesに以下のような記述がある。 https://www.php.net/manual/en/language.exceptions.php#91159

<?php

interface IException
{
    /* Protected methods inherited from Exception class */
    public function getMessage();                 // Exception message
    public function getCode();                    // User-defined Exception code
    public function getFile();                    // Source filename
    public function getLine();                    // Source line
    public function getTrace();                   // An array of the backtrace()
    public function getTraceAsString();           // Formated string of trace

    /* Overrideable methods inherited from Exception class */
    public function __toString();                 // formated string for display
    public function __construct($message = null, $code = 0);
}

code とは User-defined Exception code と書いてある通り、ユーザー側が任意の数値を入れられる。

PHPStanを実行すると以下のような型として認識されているようだ。

public function __construct(string $message = null, int $code = 0);

php-srcコードリーディング

Exceptionの定義は以下。

Zend/zend_exceptions.c#L303-L330:

/* {{{ Exception constructor */
ZEND_METHOD(Exception, __construct)
{
        zend_string *message = NULL;
        zend_long   code = 0;
        zval  tmp, *object, *previous = NULL;
        zend_class_entry *base_ce;

        object = ZEND_THIS;
        base_ce = i_get_exception_base(Z_OBJ_P(object));

        if (zend_parse_parameters(ZEND_NUM_ARGS(), "|SlO!", &message, &code, &previous, zend_ce_throwable) == FAILURE) {
                RETURN_THROWS();
        }

        if (message) {
                ZVAL_STR(&tmp, message);
                zend_update_property_ex(base_ce, Z_OBJ_P(object), ZSTR_KNOWN(ZEND_STR_MESSAGE), &tmp);
        }

        if (code) {
                ZVAL_LONG(&tmp, code);
                zend_update_property_ex(base_ce, Z_OBJ_P(object), ZSTR_KNOWN(ZEND_STR_CODE), &tmp);
        }

        if (previous) {
                zend_update_property_ex(base_ce, Z_OBJ_P(object), ZSTR_KNOWN(ZEND_STR_PREVIOUS), previous);
        }
}
/* }}} */

C言語的には codelong long で定義されている。

Zend/zend_long.h#L32:

typedef int64_t zend_long;

Exceptionが提供しているmethodは getCode のみ。

Zend/zend_exceptions.c#L441-L452:

/* {{{ Get the exception code */
ZEND_METHOD(Exception, getCode)
{
        zval *prop, rv;

        ZEND_PARSE_PARAMETERS_NONE();

        prop = GET_PROPERTY(ZEND_THIS, ZEND_STR_CODE);
        ZVAL_DEREF(prop);
        ZVAL_COPY(return_value, prop);
}
/* }}} */

使用例

以下のように取得できる。 プロジェクト内でエラーコードをintegerで統一できているなら使い道がありそう。

const ERROR_CODE = 10;

try {
    throw new \Exception(null, ERROR_CODE);
} catch (\Exception $e) {
    var_dump($e->getCode());
}
int(10)

PHPUnitでcodeを元にexcepionする関数 TestCase#expectExceptionCode が生えているのでテスト時に使えそう。 https://github.com/sebastianbergmann/PHPUnit/blob/main/src/Framework/TestCase.php#L430-L433