`trigger_error` เทียบกับ` Throw Exception` ในบริบทของวิธีการเวทย์มนตร์ของ PHP


13

ฉันมีการอภิปรายกับเพื่อนร่วมงานที่ผ่านการใช้งานที่ถูกต้อง (ถ้ามี) ของtrigger_errorในบริบทของวิธีมายากล ตอนแรกฉันคิดว่าtrigger_errorควรหลีกเลี่ยงยกเว้นกรณีนี้

สมมติว่าเรามีคลาสด้วยวิธีหนึ่ง 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

Use-case ของฉันคือการนำไปใช้งานของ ActiveRecord ฉันใช้__callเพื่อจับและจัดการวิธีการที่ทำสิ่งเดียวกัน แต่มีตัวดัดแปลงเช่นDistinctหรือIgnoreเช่น

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

หรือ

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

วิธีการเช่นwhere(), from(), groupBy()ฯลฯ กำหนดค่าตายตัว

insret()อาร์กิวเมนต์ของฉันเป็นไฮไลต์เมื่อคุณเผลอเรียก หากการบันทึกที่ใช้งานอยู่ของฉันฮาร์ดโค้ดทั้งหมดแล้วมันจะเป็นข้อผิดพลาด

เช่นเดียวกับสิ่งที่เป็นนามธรรมที่ดีผู้ใช้ควรไม่ทราบรายละเอียดของการติดตั้งและใช้อินเทอร์เฟซ แต่เพียงผู้เดียว ทำไมการปรับใช้ที่ใช้วิธีการเวทย์มนตร์จึงมีความแตกต่างกัน? ทั้งสองควรเป็นข้อผิดพลาด

คำตอบ:


7

ใช้เวลาสองการใช้งานของอินเตอร์เฟซ ActiveRecord เดียวกัน ( 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 เมื่อทศวรรษที่แล้วเมื่อชั้นเรียนถูกนำไปใช้ครั้งแรกใน PHP 4.something?
Matthew Scharley

@ Matthew เพื่อความมั่นคง ฉันไม่ได้โต้วาทีข้อยกเว้นกับข้อผิดพลาด (ไม่มีการอภิปราย) หรือว่า PHP มีการออกแบบที่ล้มเหลว (ไม่) ฉันกำลังเถียงว่าในสถานการณ์ที่ไม่ซ้ำกันนี้เพื่อความมั่นคงควรเลียนแบบพฤติกรรมของภาษา
chriso

ความมั่นคงไม่ใช่สิ่งที่ดีเสมอไป การออกแบบที่สอดคล้องกัน แต่ไม่ดียังคงเป็นการออกแบบที่ไม่ดีซึ่งเป็นความเจ็บปวดที่จะใช้ในตอนท้ายของวัน
Matthew Scharley

@ แมทธิวจริง แต่ IMO ทำให้เกิดข้อผิดพลาดในการโทรวิธีที่ไม่ถูกต้องไม่ได้รับการออกแบบที่ไม่ดี 1) หลายภาษา (ส่วนใหญ่?) มีภาษาในตัวและ 2) ฉันไม่สามารถนึกถึงกรณีเดียวที่คุณต้องการ การจับสายเรียกวิธีการที่ไม่ถูกต้องและจัดการกับมันได้หรือไม่
chriso

@chriso: จักรวาลมีขนาดใหญ่พอที่มีกรณีการใช้งานที่ไม่ว่าคุณหรือฉันจะไม่เคยฝันถึงสิ่งที่เกิดขึ้น คุณกำลังใช้__call()เพื่อกำหนดเส้นทางแบบไดนามิกจริงๆแล้วมันไม่มีเหตุผลที่จะคาดหวังว่าจะมีใครบางคนตามรอยคนที่อาจต้องการจัดการกับกรณีที่ล้มเหลว? ไม่ว่าจะด้วยวิธีใดก็ตามนี่จะเป็นวงกลมดังนั้นนี่จะเป็นความคิดเห็นสุดท้ายของฉัน ทำสิ่งที่คุณจะทำในตอนท้ายของวันนี้ลงมาเพื่อการตัดสิน: การสนับสนุนที่ดีกว่าและความมั่นคง ทั้งสองวิธีจะส่งผลเช่นเดียวกันกับแอปพลิเคชันในกรณีที่ไม่มีการจัดการเป็นพิเศษ
Matthew Scharley

3

ฉันจะโยนความเห็นที่มีความเห็นของฉันออกไปที่นั่น แต่ถ้าคุณใช้trigger_errorที่ใดก็ตามคุณก็กำลังทำอะไรผิดไป ข้อยกเว้นเป็นวิธีที่จะไป

ข้อดีของข้อยกเว้น:

  • พวกเขาสามารถถูกจับได้ นี่เป็นข้อได้เปรียบที่ยิ่งใหญ่และควรเป็นสิ่งเดียวที่คุณต้องการ ผู้คนสามารถลองทำสิ่งที่แตกต่างกันได้หากพวกเขาคาดหวังว่ามีโอกาสบางอย่างที่ผิดพลาด ข้อผิดพลาดไม่ให้โอกาสนี้กับคุณ แม้การตั้งค่าตัวจัดการข้อผิดพลาดที่กำหนดเองไม่ได้ถือเทียนเพียงแค่จับข้อยกเว้น เกี่ยวกับความคิดเห็นของคุณในคำถามของคุณแอปพลิเคชัน 'ที่สมเหตุสมผล' นั้นขึ้นอยู่กับบริบทของแอปพลิเคชันทั้งหมด ผู้คนไม่สามารถจับข้อยกเว้นได้หากพวกเขาคิดว่ามันจะไม่เกิดขึ้นในกรณีของพวกเขา การไม่ให้คนอื่นเลือกเป็นสิ่งที่ไม่ดี™
  • ร่องรอยสแต็ก หากมีสิ่งผิดปกติเกิดขึ้นคุณจะรู้ว่าปัญหาเกิดขึ้นที่ไหนและในบริบทใด คุณเคยพยายามติดตามว่าข้อผิดพลาดมาจากวิธีการหลักบางส่วนหรือไม่? หากคุณเรียกใช้ฟังก์ชันที่มีพารามิเตอร์น้อยเกินไปคุณจะได้รับข้อผิดพลาดที่ไร้ประโยชน์ซึ่งจะเน้นจุดเริ่มต้นของวิธีการที่คุณโทรหา
  • ความชัดเจน รวมสองข้างต้นและคุณจะได้รับรหัสที่ชัดเจนยิ่งขึ้น หากคุณพยายามใช้ตัวจัดการข้อผิดพลาดที่กำหนดเองเพื่อจัดการข้อผิดพลาด (เช่นเพื่อสร้างร่องรอยสแต็กสำหรับข้อผิดพลาด) การจัดการข้อผิดพลาดทั้งหมดของคุณอยู่ในฟังก์ชั่นเดียวไม่ใช่ตำแหน่งที่เกิดข้อผิดพลาดขึ้นจริง

อยู่ในความกังวลของคุณเรียกวิธีการที่ไม่อยู่อาจจะมีความเป็นไปได้ที่ถูกต้อง ขึ้นอยู่กับบริบทของรหัสที่คุณเขียน แต่มีบางกรณีที่อาจเกิดขึ้นได้ ที่อยู่กรณีการใช้งานที่แน่นอนของคุณเซิร์ฟเวอร์ฐานข้อมูลบางแห่งอาจอนุญาตการทำงานบางอย่างที่คนอื่นไม่ทำ การใช้try/ catchและข้อยกเว้นใน__call()เมื่อเทียบกับฟังก์ชั่นในการตรวจสอบความสามารถเป็นอาร์กิวเมนต์ที่แตกต่างกันโดยสิ้นเชิง

กรณีใช้งานเดียวที่ฉันนึกได้สำหรับการใช้งานtrigger_errorนั้นมีไว้สำหรับE_USER_WARNINGหรือต่ำกว่า การเรียกสิ่งE_USER_ERRORนั้นเป็นความผิดพลาดในความคิดของฉัน


ขอบคุณสำหรับการตอบกลับ :) - 1) ฉันยอมรับว่าข้อยกเว้นควรจะถูกใช้บ่อยกว่าข้อผิดพลาด - อาร์กิวเมนต์ทั้งหมดของคุณเป็นคะแนนที่ถูกต้อง อย่างไรก็ตาม .. ฉันคิดว่าในบริบทนี้การโต้เถียงของคุณจะล้มเหลว ..
chriso

2
"การเรียกใช้วิธีที่ไม่มีอยู่อาจเป็นไปได้ที่ถูกต้อง " - ฉันไม่เห็นด้วยอย่างสมบูรณ์ ในทุกภาษา (ที่ฉันเคยใช้) การเรียกใช้ฟังก์ชัน / วิธีที่ไม่มีอยู่จะทำให้เกิดข้อผิดพลาด มันเป็นไม่ได้สภาพที่ควรจะจับและจัดการ ภาษาแบบสแตติกจะไม่อนุญาตให้คุณคอมไพล์ด้วยการเรียกเมธอดที่ไม่ถูกต้องและภาษาแบบไดนามิกจะล้มเหลวเมื่อพวกเขามาถึงการโทร
chriso

หากคุณอ่านการแก้ไขครั้งที่สองของฉันคุณจะเห็นกรณีใช้งานของฉัน สมมติว่าคุณมีการใช้งานสองคลาสเดียวกันโดยหนึ่งใช้ __call และอีกหนึ่งคลาสที่มีวิธีฮาร์ดโค้ด ละเว้นรายละเอียดการใช้งานทำไมทั้งสองคลาสควรทำงานต่างกันเมื่อใช้อินเทอร์เฟซเดียวกัน PHP จะเรียกข้อผิดพลาดถ้าคุณเรียกใช้วิธีการที่ไม่ถูกต้องกับคลาสที่มีวิธีการ hardcoded การใช้trigger_errorในบริบทของ __call หรือ __callStatic เลียนแบบพฤติกรรมเริ่มต้นของภาษา
chriso

@chriso: ภาษาไดนามิกที่ไม่ใช่ PHP จะล้มเหลวโดยมีข้อยกเว้นที่สามารถตรวจจับได้ ทับทิมเช่นพ่นNoMethodErrorที่คุณสามารถจับถ้าคุณต้องการ ข้อผิดพลาดเป็นความผิดพลาดครั้งใหญ่ใน PHP ในความเห็นส่วนตัวของฉัน เพียงเพราะแกนกลางใช้วิธีการที่ใช้งานไม่ได้สำหรับการรายงานข้อผิดพลาดไม่ได้หมายความว่ารหัสของคุณควรจะเป็น
Matthew Scharley

@ chriso ฉันชอบที่จะเชื่อว่าเหตุผลเดียวที่ core ยังคงใช้ข้อผิดพลาดคือการเข้ากันได้ย้อนหลัง หวังว่า PHP6 จะเป็นอีกก้าวกระโดดที่ก้าวไปข้างหน้าอย่าง PHP5 และลดข้อผิดพลาดทั้งหมด ในตอนท้ายของวันทั้งข้อผิดพลาดและข้อยกเว้นสร้างผลลัพธ์เดียวกัน: การยกเลิกรหัสการดำเนินการทันที ด้วยข้อยกเว้นคุณสามารถวินิจฉัยสาเหตุและสถานที่ได้ง่ายกว่ามาก
Matthew Scharley

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_NOTICEข้อยกเว้น นั่นจะไม่ดี
Matthew Scharley

1
ไม่การเปลี่ยน E_NOTICES เป็นข้อยกเว้นนั้นดี! ประกาศทั้งหมดควรได้รับการพิจารณาว่าผิดพลาด เป็นการปฏิบัติที่ดีไม่ว่าคุณจะแปลงเป็นข้อยกเว้นหรือไม่
เวย์น

2
ปกติฉันจะเห็นด้วยกับคุณ แต่ครั้งที่สองที่คุณเริ่มใช้รหัสของบุคคลที่สามสิ่งนี้จะเกิดขึ้นได้อย่างรวดเร็ว
Matthew Scharley

ห้องสมุดบุคคลที่สามเกือบทั้งหมดเป็น E_STRICT | E_ALL ปลอดภัย หากฉันใช้รหัสที่ไม่ใช่ฉันจะเข้าไปและแก้ไข ฉันทำงานมานานหลายปีโดยไม่มีปัญหา
Wayne

เห็นได้ชัดว่าคุณไม่เคยใช้ Dual มาก่อน แกนกลางนั้นดีมากเช่นนี้ แต่มีโมดูลบุคคลที่สามจำนวนมากไม่ใช่
Matthew Scharley

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โดยตรงจากบริบทของคุณซึ่งหมายความว่าคุณไม่สามารถใช้ฟังก์ชัน / เมธอด wrapper ในการตั้งชื่อแทนtrigger_errorฟังก์ชัน (ดังนั้นคุณจึงไม่สามารถทำสิ่งต่าง ๆ เช่นfunction debug($code, $message) { return trigger_error($message, $code); }ถ้าคุณต้องการข้อมูลบริบทในการติดตามของคุณ)

ฉันกำลังมองหาทางเลือกที่ดีกว่า แต่จนถึงตอนนี้ฉันยังไม่พบอะไรเลย

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.