PHPのマジックメソッドのコンテキストでの `trigger_error`と` throw Exception`


13

私はtrigger_error魔法のメソッドのコンテキストでの正しい使用法(もしあれば)について同僚と議論しています。まず、この1つの場合を除いて、それtrigger_errorは避けるべきだと思います。

1つのメソッドを持つクラスがあるとします foo()

class A {
    public function foo() {
        echo 'bar';
    }
}

まったく同じインターフェースを提供したいが、すべてのメソッド呼び出しをキャッチするためにマジックメソッドを使用するとします

class B {
    public function __call($method, $args) {
        switch (strtolower($method)) {
        case 'foo':
            echo 'bar';
            break;
        }
    }
}

$a = new A;
$b = new B;

$a->foo(); //bar
$b->foo(); //bar

両方のクラスの応答方法は同じですfoo()が、無効なメソッドを呼び出す場合は異なります。

$a->doesntexist(); //Error
$b->doesntexist(); //Does nothing

私の議論はtrigger_error、未知のメソッドがキャッチされたときに魔法のメソッドを呼び出す必要があるということです

class B {
    public function __call($method, $args) {
        switch (strtolower($method)) {
        case 'foo':
            echo 'bar';
            break;
        default:
            $class = get_class($this);
            $trace = debug_backtrace();
            $file = $trace[0]['file'];
            $line = $trace[0]['line'];
            trigger_error("Call to undefined method $class::$method() in $file on line $line", E_USER_ERROR);
            break;
        }
    }
}

両方のクラスが(ほぼ)同じように動作するように

$a->badMethod(); //Call to undefined method A::badMethod() in [..] on line 28
$b->badMethod(); //Call to undefined method B::badMethod() in [..] on line 32

私のユースケースはActiveRecordの実装です。私が使用して__call、本質的に同じことを行うが、のような修飾子を持つキャッチし、ハンドルメソッドにDistinctまたはIgnore、例えば

selectDistinct()
selectDistinctColumn($column, ..)
selectAll()
selectOne()
select()

または

insert()
replace()
insertIgnore()
replaceIgnore()

以下のような方法where()from()groupBy()などは、ハードコードされています。

誤ってを呼び出すと、私の議論が強調されますinsret()。アクティブレコードの実装がすべてのメソッドをハードコーディングした場合、エラーになります。

優れた抽象化と同様に、ユーザーは実装の詳細を意識せず、インターフェイスのみに依存する必要があります。マジックメソッドを使用する実装の動作が異なるのはなぜですか?両方ともエラーでなければなりません。

回答:


7

同じActiveRecordのインタフェースの2つの実装を取る(select()where()等)

class ActiveRecord1 {
    //Hardcodes all methods
}

class ActiveRecord2 {
    //Uses __call to handle some methods, hardcodes the rest
}

たとえばActiveRecord1::insret()、最初のクラスで無効なメソッドを呼び出すと、デフォルトのPHPの動作はエラーをトリガーします。無効な関数/メソッド呼び出しは、妥当なアプリケーションがキャッチして処理したい条件ではありません。もちろん、エラー例外であるRubyやPythonのような言語でもキャッチできますが、その他(JavaScript /任意の静的言語/その他?)は失敗します。

PHPに戻る-両方のクラスが同じインターフェースを実装している場合、なぜ同じ動作をしてはいけないのでしょうか?

場合__callまたは__callStatic無効なメソッドを検出し、彼らは言語の模倣デフォルトの動作にエラーをトリガする必要があります

$class = get_class($this);
$trace = debug_backtrace();
$file = $trace[0]['file'];
$line = $trace[0]['line'];
trigger_error("Call to undefined method $class::$method() in $file on line $line", E_USER_ERROR);

私はエラーを例外に対して使用すべきかどうか議論していません(100%はすべきではありません)が、PHPの魔法のメソッドは例外であると信じています-しゃれを意図:)- 言語のコンテキストでこの規則


1
申し訳ありませんが、私はそれを購入しません。クラスがPHPで最初に実装された10年以上前にPHPには例外が存在しなかったため、なぜ羊である必要があるの4.somethingでしょうか。
マシューシャーリー

一貫性のための@Matthew。私は(それがない)PHPは、デザインが失敗しているかどうかのエラー対例外を議論(何の議論がありません)かないよ、私はこの非常にユニークな状況では、と主張してい一貫性を保つために、それは言語の模倣行動するのが最善です
chriso

一貫性は常に良いことではありません。一貫しているが貧弱なデザインは、やはり貧弱なデザインであり、1日の終わりに使用するのは苦痛です。
マシューシャーリー

@Matthew trueですが、無効なメソッド呼び出しでエラーをトリガーするIMOの設計は貧弱ではありません1)多くの(ほとんどの?)言語にビルトインされています。無効なメソッド呼び出しをキャッチして処理するには?
chriso

@chriso:宇宙は十分に大きいので、あなたも私も、それが起こることを夢見てもいないユースケースがあります。__call()動的なルーティングを行うために使用していますが、トラックのどこかで誰かが失敗したケースを処理したいと思うのは本当に不合理ですか?とにかく、これは円で起こっているので、これは私の最後のコメントになります。結局のところ、これは判断の呼び出しになります:より良いサポートと一貫性。どちらの方法も、特別な処理を行わない場合、アプリケーションに同じ効果をもたらします。
マシューシャーリー

3

私は自分の意見をそこに捨てるつもりですが、trigger_errorどこでも使うなら、あなたは何か間違ったことをしていることになります。例外は進むべき道です。

例外の利点:

  • 彼らは捕まえることができます。これは大きな利点であり、必要なものはこれだけです。何かがうまくいかない可能性があると予想する場合、人々は実際に別のことを試すことができます。エラーはあなたにこの機会を与えません。カスタムエラーハンドラーを設定するだけでも、単に例外をキャッチすることにはなりません。質問のコメントに関して、「合理的な」アプリケーションとは、アプリケーションのコンテキストに完全に依存します。例外が発生することはないと思う場合、例外をキャッチすることはできません。人々に選択肢を与えないことは、Bad Thing™です。
  • スタックトレース。問題が発生した場合、問題が発生した場所コンテキストを把握できます。コアメソッドの一部からエラーが発生している場所を追跡しようとしたことがありますか?パラメータが少なすぎる関数を呼び出すと、呼び出し中のメソッドの開始を強調する無駄なエラーが発生し、呼び出し元の場所は完全に除外されます。
  • 明快さ。上記の2つを組み合わせると、より明確なコードが得られます。カスタムエラーハンドラを使用してエラーを処理する場合(たとえば、エラーのスタックトレースを生成する場合)、エラー処理はすべて、エラーが実際に生成される場所ではなく、1つの関数内にあります。

懸念に対処するために、存在しないメソッドを呼び出すことは有効な可能性があります。これは、作成しているコードのコンテキストに完全に依存しますが、これが発生する場合があります。正確なユースケースに対処するために、一部のデータベースサーバーは、他のユーザーが許可しない機能を許可する場合があります。使用try/ catch中と例外__call()の機能をチェックする機能対は、まったく別の引数です。

私が使用するために考えられる唯一のユースケースtrigger_errorは、for E_USER_WARNING以下です。E_USER_ERRORただし、トリガーをトリガーすることは、私の意見では常にエラーです。


回答ありがとうございます:)-1)例外はエラーに対してほとんど常に使用されることに同意します-あなたの議論はすべて有効なポイントです。しかし..この文脈では、あなたの議論は失敗すると思う
。.– chriso

2
存在しないメソッドを呼び出すことは有効な可能性があるかもしれません」-私は完全に同意しません。すべての言語(私が使用したことのある言語)で、存在しない関数/メソッドを呼び出すと、エラーが発生します。これは、キャッチして処理する必要がある条件ではありません。静的言語では、無効なメソッド呼び出しでコンパイルすることはできず、動的言語は呼び出しに到達すると失敗します。
クリソ

2回目の編集を読むと、ユースケースが表示されます。同じクラスの2つの実装があるとします。1つは__callを使用し、もう1つはハードコードされたメソッドを使用します。実装の詳細を無視して、同じインターフェイスを実装するときに両方のクラスの動作が異なるのはなぜですか?ハードコードされたメソッドを持つクラスで無効なメソッドを呼び出すと、PHPはエラーをトリガーしますtrigger_error__callまたは__callStaticのコンテキストで使用すると、言語のデフォルトの動作を模倣します
chriso

@chriso:PHPではない動的言語は、キャッチできる例外で失敗ます。たとえば、Rubyは、NoMethodError必要に応じてキャッチできるものをスローします。個人的な意見では、エラーはPHPの大きな間違いです。コアがエラーを報告するために壊れた方法を使用しているからといって、あなた自身のコードがそうすべきだという意味ではありません。
マシューシャーリー

@chrisoコアが引き続きエラーを使用する唯一の理由は、後方互換性のためだと信じたいです。願わくば、PHP6がPHP5のようにさらに飛躍し、エラーが完全になくなることを願っています。一日の終わりには、エラーと例外の両方が同じ結果を生成します:コードの実行が即座に終了します。ただし、例外を除いて、理由と場所を簡単に診断できます。
マシューシャーリー

3

標準のPHPエラーは廃止されたと見なされるべきです。PHPは、完全な適切なスタックトレースを使用して、エラー、警告、通知を例外に変換するための組み込みクラスErrorExceptionを提供します。次のように使用します。

function errorToExceptionHandler($errNo, $errStr, $errFile, $errLine, $errContext)
{
if (error_reporting() == 0) return;
throw new ErrorException($errStr, 0, $errNo, $errFile, $errLine);
}
set_error_handler('errorToExceptionHandler');

それを使用すると、この質問は意味がなくなります。組み込みエラーは例外を発生させるようになったため、独自のコードも例外を発生させる必要があります。


1
これを使用する場合は、E_NOTICEsを例外にしないように、エラーのタイプを確認する必要があります。それは悪いだろう。
マシューシャーリー

1
いいえ、E_NOTICESを例外に変えることは良いことです!すべての通知はエラーと見なされる必要があります。それらを例外に変換するかどうかにかかわらず、それは良い習慣です。
ウェイン

2
通常、私はあなたに同意しますが、サードパーティのコードを使用し始めた2番目の場合、これはすぐに表面化する傾向があります。
マシューシャーリー

ほとんどすべてのサードパーティライブラリはE_STRICTです。E_ALLセーフ。使用していないコードを使用している場合は、それを修正します。何も問題なくこの方法で仕事をしてきました。
ウェイン

明らかに以前はDualを使用したことがありません。コアはこのように非常に優れていますが、多くの多くのサードパーティのモジュールはそうではありません
マシューシャーリー

0

IMO、これは完全に有効なユースケースですtrigger_error

function handleError($errno, $errstring, $errfile, $errline, $errcontext) {
    if (error_reporting() & $errno) {
        // only process when included in error_reporting
        return handleException(new \Exception($errstring, $errno));
    }
    return true;
}

function handleException($exception){
    // Here, you do whatever you want with the generated
    // exceptions. You can store them in a file or database,
    // output them in a debug section of your page or do
    // pretty much anything else with it, as if it's a
    // normal variable

    switch ($code) {
        case E_ERROR:
        case E_CORE_ERROR:
        case E_USER_ERROR:
            // Make sure script exits here
            exit(1);
        default:
            // Let script continue
            return true;
    }
}

// Set error handler to your custom handler
set_error_handler('handleError');
// Set exception handler to your custom handler
set_exception_handler('handleException');


// ---------------------------------- //

// Generate warning
trigger_error('This went wrong, but we can continue', E_USER_WARNING);

// Generate fatal error :
trigger_error('This went horrible wrong', E_USER_ERROR);

この戦略を使用すると、 $errcontext$exception->getTrace()、関数内で行う場合パラメーターますhandleException。これは、特定のデバッグ目的に非常に役立ちます。

残念ながら、これは以下を使用する場合にのみ機能します trigger_errorコンテキストから直接する機能します。つまり、ラッパー関数/メソッドを使用して関数のエイリアスを作成するtrigger_errorことはできません(したがってfunction debug($code, $message) { return trigger_error($message, $code); }、トレースにコンテキストデータが必要な場合などはできません)。

私はより良い代替物を探していましたが、今のところ見つけられませんでした。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.