`trigger_error` vs` throw Exception` dalam konteks metode ajaib PHP

13

Saya berdebat dengan seorang kolega tentang penggunaan yang benar (jika ada) trigger_errordalam konteks metode sihir . Pertama, saya pikir itu trigger_errorharus dihindari kecuali untuk kasus yang satu ini.

Katakanlah kita memiliki kelas dengan satu metode foo()

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

Sekarang katakan kita ingin memberikan antarmuka yang sama persis tetapi menggunakan metode ajaib untuk menangkap semua panggilan metode

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

Kedua kelas sama dalam cara mereka merespons foo()tetapi berbeda ketika memanggil metode yang tidak valid.

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

Argumen saya adalah bahwa metode ajaib harus memanggil trigger_errorketika metode yang tidak diketahui ditangkap

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;
        }
    }
}

Sehingga kedua kelas berperilaku (hampir) identik

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

Kasing saya adalah implementasi ActiveRecord. Saya menggunakan __calluntuk menangkap dan menangani metode yang pada dasarnya melakukan hal yang sama tetapi memiliki pengubah seperti Distinctatau Ignore, misalnya

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

atau

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

Metode seperti where(), from(), groupBy(), dll keras-kode.

Argumen saya disorot ketika Anda secara tidak sengaja menelepon insret(). Jika implementasi rekaman aktif saya hardcoded semua metode maka itu akan menjadi kesalahan.

Seperti halnya abstraksi yang bagus, pengguna harus tidak mengetahui detail implementasi dan hanya mengandalkan antarmuka. Mengapa implementasi yang menggunakan metode sihir berperilaku berbeda? Keduanya harus menjadi kesalahan.

chriso
sumber

Jawaban:

7

Ambil dua implementasi dari interface ActiveRecord yang sama ( select(), where(), dll)

class ActiveRecord1 {
    //Hardcodes all methods
}

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

Jika Anda memanggil metode yang tidak valid pada kelas pertama, misalnya ActiveRecord1::insret(), perilaku PHP default adalah untuk memicu kesalahan . Fungsi / metode panggilan yang tidak valid bukanlah suatu kondisi yang ingin ditangkap dan ditangani oleh aplikasi yang masuk akal. Tentu, Anda dapat menangkapnya dalam bahasa seperti Ruby atau Python di mana kesalahan merupakan pengecualian, tetapi yang lain (JavaScript / bahasa statis / lebih?) Akan gagal.

Kembali ke PHP - jika kedua kelas mengimplementasikan antarmuka yang sama, mengapa mereka tidak menunjukkan perilaku yang sama?

Jika __callatau __callStaticmendeteksi metode yang tidak valid, mereka harus memicu kesalahan untuk meniru perilaku default bahasa

$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);

Saya tidak berdebat apakah kesalahan harus digunakan atas pengecualian (100% tidak seharusnya), namun saya percaya bahwa metode magis PHP adalah pengecualian - pun intended :) - untuk aturan ini dalam konteks bahasa

chriso
sumber
1
Maaf, tapi saya tidak membelinya. Mengapa kita menjadi domba karena pengecualian tidak ada di PHP lebih dari satu dekade yang lalu ketika kelas pertama kali diimplementasikan dalam PHP 4.something?
Matthew Scharley
@ Matius untuk konsistensi. Saya tidak memperdebatkan pengecualian vs kesalahan (tidak ada perdebatan) atau apakah PHP memiliki desain gagal (memang), saya berpendapat bahwa dalam keadaan yang sangat unik ini, untuk konsistensi , yang terbaik adalah meniru perilaku bahasa
chriso
Konsistensi tidak selalu merupakan hal yang baik. Desain yang konsisten namun buruk masih merupakan desain yang buruk sehingga sulit digunakan pada akhir hari.
Matthew Scharley
@ Matius benar, tetapi IMO memicu kesalahan pada panggilan metode yang tidak valid bukan desain yang buruk 1) banyak (kebanyakan?) Bahasa sudah built-in, dan 2) Saya tidak bisa memikirkan satu kasus di mana Anda ingin untuk menangkap panggilan metode yang tidak valid dan menanganinya?
chriso
@ chriso: Alam semesta cukup besar sehingga ada kasus penggunaan yang tidak pernah Anda atau saya impikan terjadi itu terjadi. Anda menggunakan __call()untuk melakukan routing dinamis, apakah benar-benar tidak masuk akal untuk mengharapkan bahwa di suatu tempat di jalur seseorang mungkin ingin menangani kasus di mana itu gagal? Bagaimanapun, ini berputar-putar, jadi ini akan menjadi komentar terakhir saya. Lakukan apa yang Anda mau, pada akhirnya hari ini datang ke panggilan penilaian: Dukungan yang lebih baik vs konsistensi. Kedua metode akan menghasilkan efek yang sama pada aplikasi jika tidak ada penanganan khusus.
Matthew Scharley
3

Saya akan membuang pendapat saya di luar sana, tetapi jika Anda menggunakan di trigger_errormana saja, maka Anda melakukan sesuatu yang salah. Pengecualian adalah caranya.

Keuntungan pengecualian:

  • Mereka bisa ditangkap. Ini adalah keuntungan besar, dan harus menjadi satu-satunya yang Anda butuhkan. Orang benar-benar dapat mencoba sesuatu yang berbeda jika mereka berharap ada kemungkinan ada sesuatu yang salah. Kesalahan tidak memberi Anda kesempatan ini. Bahkan mengatur penangan kesalahan kustom tidak memegang lilin untuk hanya menangkap pengecualian. Mengenai komentar Anda dalam pertanyaan Anda, aplikasi apa yang 'masuk akal' tergantung sepenuhnya pada konteks aplikasi. Orang tidak dapat menangkap pengecualian jika mereka pikir itu tidak akan pernah terjadi dalam kasus mereka. Tidak memberi orang pilihan adalah Bad Thing ™.
  • Jejak tumpukan. Jika terjadi kesalahan, Anda tahu di mana dan dalam konteks apa masalahnya terjadi. Pernahkah Anda mencoba melacak di mana kesalahan berasal dari beberapa metode inti? Jika Anda memanggil suatu fungsi dengan terlalu sedikit parameter Anda mendapatkan kesalahan yang tidak berguna yang menyoroti awal metode yang Anda panggil, dan benar-benar meninggalkan dari mana ia memanggil.
  • Kejelasan. Gabungkan kedua di atas dan Anda mendapatkan kode yang lebih jelas. Jika Anda mencoba menggunakan penangan kesalahan khusus untuk menangani kesalahan (mis. Untuk menghasilkan jejak stack untuk kesalahan), semua penanganan kesalahan Anda berada dalam satu fungsi, bukan tempat kesalahan sebenarnya dihasilkan.

Mengatasi masalah Anda, memanggil metode yang tidak ada mungkin merupakan kemungkinan yang valid . Ini sepenuhnya tergantung pada konteks kode yang Anda tulis, tetapi ada beberapa kasus di mana ini mungkin terjadi. Mengatasi kasus penggunaan yang tepat, beberapa server database mungkin mengizinkan beberapa fungsi yang tidak dimiliki orang lain. Menggunakan try/ catchdan pengecualian dalam __call()vs. fungsi untuk memeriksa kapabilitas adalah argumen yang berbeda sama sekali.

Satu-satunya use case yang dapat saya pikirkan untuk digunakan trigger_erroradalah untuk E_USER_WARNINGatau lebih rendah. Memicu E_USER_ERRORmeskipun selalu merupakan kesalahan menurut pendapat saya.

Matthew Scharley
sumber
Terima kasih atas tanggapannya :) - 1) Saya setuju bahwa pengecualian hampir selalu digunakan atas kesalahan - semua argumen Anda adalah poin yang valid. Namun .. Saya pikir dalam konteks ini argumen Anda gagal ..
chriso
2
" Memanggil metode yang tidak ada mungkin merupakan kemungkinan yang valid " - Saya benar-benar tidak setuju. Di setiap bahasa (yang pernah saya gunakan), memanggil fungsi / metode yang tidak ada akan menghasilkan kesalahan. Ini bukan kondisi yang harus ditangkap dan ditangani. Bahasa statis tidak akan membiarkan Anda mengkompilasi dengan panggilan metode yang tidak valid dan bahasa dinamis akan gagal setelah mereka mencapai panggilan.
chriso
Jika Anda membaca edit kedua saya, Anda akan melihat kasus penggunaan saya. Katakanlah Anda memiliki dua implementasi dari kelas yang sama, satu menggunakan __call dan satu dengan metode hardcoded. Mengabaikan detail implementasi, mengapa kedua kelas harus berperilaku berbeda ketika mereka mengimplementasikan antarmuka yang sama? PHP akan memicu kesalahan jika Anda memanggil metode yang tidak valid dengan kelas yang memiliki metode hardcoded. Menggunakan trigger_errordalam konteks __call atau __callStatic meniru perilaku default bahasa
chriso
@ chriso: Bahasa dinamis yang bukan PHP akan gagal dengan pengecualian yang dapat ditangkap . Ruby misalnya melempar sesuatu NoMethodErroryang bisa Anda tangkap jika diinginkan. Kesalahan adalah kesalahan besar dalam PHP menurut pendapat pribadi saya. Hanya karena inti menggunakan metode yang rusak untuk melaporkan kesalahan tidak berarti kode Anda sendiri harus.
Matthew Scharley
@ chriso Saya ingin percaya satu-satunya alasan core masih menggunakan kesalahan adalah untuk kompatibilitas. Mudah-mudahan PHP6 akan menjadi lompatan ke depan seperti PHP5 dan menghapus semua kesalahan. Pada akhirnya, kesalahan dan pengecualian menghasilkan hasil yang sama: penghentian segera kode eksekusi. Dengan pengecualian Anda dapat mendiagnosis mengapa dan di mana jauh lebih mudah.
Matthew Scharley
3

Kesalahan PHP standar harus dianggap usang. PHP menyediakan ErrorException kelas bawaan untuk mengubah kesalahan, peringatan, dan pemberitahuan menjadi pengecualian dengan jejak stack yang tepat dan tepat. Anda menggunakannya seperti ini:

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

Dengan menggunakan itu, pertanyaan ini menjadi diperdebatkan. Kesalahan bawaan sekarang meningkatkan pengecualian dan kode Anda juga harus demikian.

Wayne
sumber
1
Jika Anda menggunakan ini, Anda perlu memeriksa jenis kesalahan, jangan sampai Anda berubah E_NOTICEmenjadi pengecualian. Itu akan buruk.
Matthew Scharley
1
Tidak, mengubah E_NOTICES menjadi Pengecualian itu bagus! Semua pemberitahuan harus dianggap sebagai kesalahan; itu praktik yang baik apakah Anda mengubahnya menjadi pengecualian atau tidak.
Wayne
2
Biasanya saya setuju dengan Anda, tetapi begitu Anda mulai menggunakan kode pihak ketiga, ini dengan cepat cenderung jatuh pada wajahnya.
Matthew Scharley
Hampir semua perpustakaan pihak ke-3 adalah E_STRICT | E_ALL aman. Jika saya menggunakan kode yang bukan, saya akan masuk dan memperbaikinya. Saya telah bekerja seperti ini selama bertahun - tahun tanpa masalah.
Wayne
Anda jelas tidak pernah menggunakan Dual sebelumnya. Intinya sangat bagus seperti ini tetapi banyak, banyak modul pihak ketiga tidak
Matthew Scharley
0

IMO, ini adalah kasus penggunaan yang valid untuk 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);

Dengan menggunakan strategi ini, Anda mendapatkan $errcontextparameter jika Anda melakukannya $exception->getTrace()di dalam fungsi handleException. Ini sangat berguna untuk keperluan debugging tertentu.

Sayangnya, ini hanya berfungsi jika Anda menggunakan trigger_errorlangsung dari konteks Anda, yang berarti Anda tidak dapat menggunakan fungsi pembungkus / metode untuk alias trigger_errorfungsi (jadi Anda tidak dapat melakukan sesuatu seperti function debug($code, $message) { return trigger_error($message, $code); }jika Anda ingin data konteks dalam jejak Anda).

Saya telah mencari alternatif yang lebih baik, tetapi sejauh ini saya belum menemukan.

John Slegers
sumber