LoginSignup
54
52

More than 5 years have passed since last update.

PHP で例外を投げるメソッドじゃなく例外を作るメソッドにするただひとつの理由

Posted at

例外の作成にめんどくさい手続きが必要なとき。例えば、HTTP のレスポンスオブジェクトを元に例外を作成するときとか。

<?php
$message = sprintf(
    "HTTP/%s %s %s",
    $response->getVersion(),
    $response->getCode(),
    $response->getMessage()
);

throw new HttpException($message, $response->getCode());

そういうときはそれようのメソッドを作ると便利です。
このとき、例外を作成して投げるメソッド例外を作成して返すメソッド の2通りの実装方法が考えられます。例えば下記の raisecreate です。

<?php
class HttpException extends \Exception
{
    /**
     * 例外を作成して投げるメソッド
     *
     * @param $response HttpResponse
     * @throws HttpException
     */
    public static function raise(HttpResponse $response)
    {
        $message = sprintf(
            "HTTP/%s %s %s",
            $response->getVersion(),
            $response->getCode(),
            $response->getMessage()
        );

        throw new HttpException($message, $response->getCode());
    }

    /**
     * 例外を作成するメソッド
     *
     * @param $response HttpResponse
     * @return HttpException
     */
    public static function create(HttpResponse $response)
    {
        $message = sprintf(
            "HTTP/%s %s %s",
            $response->getVersion(),
            $response->getCode(),
            $response->getMessage()
        );

        return new HttpException($message, $response->getCode());
    }
}

(この例だとコンストラクタでいいじゃんと思わなくもないので例が不適切かも)

個人的には 例外を作成して投げるメソッド の方をよく使っていたのですが、思うところあってなるべく 例外を作成して返すメソッド にしようかと思います。

理由

下記は 例外を作成して投げるメソッド例外を作成して返すメソッド の両方が実装されたクラスです。

<?php
namespace App;

class Exception extends \Exception
{
    /**
     * @param $message
     * @throws Exception
     */
    public static function raise($message)
    {
        throw new static($message);
    }

    /**
     * @param $message
     * @return Exception
     */
    public static function create($message)
    {
        return new static($message);
    }
}
?>

次のように使用します。

<?php
namespace App;

class Sample
{
    public function test1($val)
    {
        if ($val == false) {
            Exception::raise("oops!");
        }
    }

    public function test2($val)
    {
        if ($val == false) {
            throw Exception::create("oops!");
        }
    }
}
?>

これを次のようにテストします。

<?php
namespace Tests;

use App\Sample;
use App\Exception;

class SampleTest extends \PHPUnit_Framework_TestCase
{
    public function test1_ok()
    {
        (new Sample())->test1(true);
    }

    public function test1_err()
    {
        $this->setExpectedException(Exception::class, "oops!");
        (new Sample())->test1(false);
    }

    public function test2_ok()
    {
        (new Sample())->test2(true);
    }

    public function test2_err()
    {
        $this->setExpectedException(Exception::class, "oops!");
        (new Sample())->test2(false);
    }
}
?>

なんということでしょう。

php-exception.png

例外を作成して返すメソッド を使用した場合は例外の次の行がデッドコードと認識されているのに対して、例外を作成して投げるメソッド だと未実行の行と認識されてしまいます。

次のように @codeCoverageIgnore を使えばカバレッジから無視させることもできるのですが、いちいちこれを書いて回るのも面倒です。

<?php
namespace App;

class Sample
{
    public function test1($val)
    {
        if ($val == false) {
            Exception::raise("oops!");
        } // @codeCoverageIgnore
    }

    public function test2($val)
    {
        if ($val == false) {
            throw Exception::create("oops!");
        }
    }
}
?>

さいごに

無理して C0 カバレッジを 100% にしなくてもいいと思うし、そんなに拘るところじゃない気もするので、やっぱどっちでも良いかも。

なお、どちらの方法にしても例外のスタックトレースは1段深くなってしまいます。PHP は例外を new した時点のスタックトレースになるためです。

なにか別の言語(C# だったかな?)だと、new じゃなくて throw した時点のスタックトレースになるらしく、例外を作成して投げるメソッド よりも 例外を作成して返すメソッド で呼び出し元で throw する方が奨励されていたような気がします。

54
52
1

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
54
52