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

Introduction

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

簡易的な調査

Interface定義

php.netのUser Contributed Notesに以下のような記述がある。(14年前) 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