net - كيف يمكنني التقاط خطأ فادح PHP




php net (12)

يمكنني استخدام set_error_handler() للقبض على معظم أخطاء PHP ، لكنه لا يعمل E_ERROR فادحة ( E_ERROR ) ، مثل استدعاء وظيفة غير موجودة. هل هناك طريقة أخرى للقبض على هذه الأخطاء؟

أحاول الاتصال mail() لجميع الأخطاء وأقوم بتشغيل PHP 5.2.3.


العثور على حل لطيف في Zend Framework 2:

/**
 * ErrorHandler that can be used to catch internal PHP errors
 * and convert to an ErrorException instance.
 */
abstract class ErrorHandler
{
    /**
     * Active stack
     *
     * @var array
     */
    protected static $stack = array();

    /**
     * Check if this error handler is active
     *
     * @return bool
     */
    public static function started()
    {
        return (bool) static::getNestedLevel();
    }

    /**
     * Get the current nested level
     *
     * @return int
     */
    public static function getNestedLevel()
    {
        return count(static::$stack);
    }

    /**
     * Starting the error handler
     *
     * @param int $errorLevel
     */
    public static function start($errorLevel = \E_WARNING)
    {
        if (!static::$stack) {
            set_error_handler(array(get_called_class(), 'addError'), $errorLevel);
        }

        static::$stack[] = null;
    }

    /**
     * Stopping the error handler
     *
     * @param  bool $throw Throw the ErrorException if any
     * @return null|ErrorException
     * @throws ErrorException If an error has been catched and $throw is true
     */
    public static function stop($throw = false)
    {
        $errorException = null;

        if (static::$stack) {
            $errorException = array_pop(static::$stack);

            if (!static::$stack) {
                restore_error_handler();
            }

            if ($errorException && $throw) {
                throw $errorException;
            }
        }

        return $errorException;
    }

    /**
     * Stop all active handler
     *
     * @return void
     */
    public static function clean()
    {
        if (static::$stack) {
            restore_error_handler();
        }

        static::$stack = array();
    }

    /**
     * Add an error to the stack
     *
     * @param int    $errno
     * @param string $errstr
     * @param string $errfile
     * @param int    $errline
     * @return void
     */
    public static function addError($errno, $errstr = '', $errfile = '', $errline = 0)
    {
        $stack = & static::$stack[count(static::$stack) - 1];
        $stack = new ErrorException($errstr, 0, $errno, $errfile, $errline, $stack);
    }
}

تسمح لك هذه الفئة بتشغيل ErrorHandler محدد في بعض الأحيان إذا كنت بحاجة إليه. ثم يمكنك أيضًا إيقاف المعالج.

استخدم هذا الصف على سبيل المثال مثل:

ErrorHandler::start(E_WARNING);
$return = call_function_raises_E_WARNING();

if ($innerException = ErrorHandler::stop()) {
    throw new Exception('Special Exception Text', 0, $innerException);
}

// or
ErrorHandler::stop(true); // directly throws an Exception;

رابط إلى رمز الفصل الكامل:
https://github.com/zendframework/zf2/blob/master/library/Zend/Stdlib/ErrorHandler.php


ربما أفضل حل هو ذلك من Monolog :

رابط إلى رمز الفصل الكامل:
https://github.com/Seldaek/monolog/blob/master/src/Monolog/ErrorHandler.php

يمكنه أيضًا التعامل مع FATAL_ERRORS باستخدام الدالة register_shutdown_function . وفقًا لهذه الفئة ، FATAL_ERROR هو أحد array(E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR) التالية array(E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR) .

class ErrorHandler
{
    // [...]

    public function registerExceptionHandler($level = null, $callPrevious = true)
    {
        $prev = set_exception_handler(array($this, 'handleException'));
        $this->uncaughtExceptionLevel = $level;
        if ($callPrevious && $prev) {
            $this->previousExceptionHandler = $prev;
        }
    }

    public function registerErrorHandler(array $levelMap = array(), $callPrevious = true, $errorTypes = -1)
    {
        $prev = set_error_handler(array($this, 'handleError'), $errorTypes);
        $this->errorLevelMap = array_replace($this->defaultErrorLevelMap(), $levelMap);
        if ($callPrevious) {
            $this->previousErrorHandler = $prev ?: true;
        }
    }

    public function registerFatalHandler($level = null, $reservedMemorySize = 20)
    {
        register_shutdown_function(array($this, 'handleFatalError'));

        $this->reservedMemory = str_repeat(' ', 1024 * $reservedMemorySize);
        $this->fatalLevel = $level;
    }

    // [...]
}

أحتاج إلى معالجة الأخطاء الفادحة للإنتاج لإظهار إخراج HTML غير المتوفر للخدمة 503 للخدمة الثابتة بدلاً من ذلك. هذا بالتأكيد نهج معقول "لاصطياد الأخطاء القاتلة". هذا ما فعلته:

لدي وظيفة معالجة الأخطاء المخصصة "error_handler" والتي ستعرض صفحة HTML الخاصة بي "503 خدمة غير متوفرة" على أي E_ERROR ، E_USER_ERROR وما إلى ذلك سيتم الآن استدعاء هذه الدالة إيقاف تشغيل الخطأ الفادح.

function fatal_error_handler() {

    if (@is_array($e = @error_get_last())) {
        $code = isset($e['type']) ? $e['type'] : 0;
        $msg = isset($e['message']) ? $e['message'] : '';
        $file = isset($e['file']) ? $e['file'] : '';
        $line = isset($e['line']) ? $e['line'] : '';
        if ($code>0) error_handler($code,$msg,$file,$line);
    }
}
set_error_handler("error_handler");
register_shutdown_function('fatal_error_handler');

في دالة error_handler المخصصة ، إذا كان الخطأ E_ERROR أو E_USER_ERROR وما إلى ذلك. اتصلت أيضًا بـob_end_clean ()؛ لتفريغ المخزن المؤقت ، وبالتالي إزالة رسالة "خطأ فادح" PHP.

خذ ملاحظة مهمة عن التدقيق الصارم () وفحص الوظائف إسكات لأننا لا نريد مخطوطات error_handler لدينا لإنشاء أي أخطاء.

في حالة التوافق مع keparo ، فإن اكتشاف الأخطاء الفادحة يؤدي إلى التغلب على غرض "خطأ فادح" بحيث لا يقصد به فعلًا إجراء مزيد من المعالجة. لا تقم بتشغيل أية وظائف mail () في عملية إيقاف التشغيل هذه ، حيث ستقوم بالتأكيد بنسخ خادم البريد أو البريد الوارد الخاص بك. بدلا من تسجيل هذه التواقيع إلى ملف وجدولة cron للعثور على ملفات error.log هذه وإرسالها بالبريد إلى المشرفين.


إذا كنت تستخدم php> = 5.1.0 فافعل شيئًا من هذا القبيل باستخدام فئة ErrorException:

<?php
//define an error handler
function exception_error_handler($errno, $errstr, $errfile, $errline ) {
    throw new ErrorException($errstr, $errno, 0, $errfile, $errline);
}
//set ur error handle
set_error_handler("exception_error_handler");

/* Trigger exception */
try
{
  //try to do something like finding the end of the internet
}
catch(ErrorException $e)
{
  //anything you want to do with $e
}

?>

الأخطاء الفادحة أو الأخطاء الفادحة القابلة للاسترداد الآن ترمي مثيلات Error في PHP 7 أو الإصدارات الأحدث . مثل أي استثناءات أخرى ، يمكن اكتشاف كائنات Error باستخدام كتلة try/catch .

مثال:

<?php
$variable = 'not an object';

try {
    $variable->method(); // Throws an Error object in PHP 7 or higger.
} catch (Error $e) {
    // Handle error
    echo $e->getMessage(); // Call to a member function method() on string
}

https://3v4l.org/67vbk

أو يمكنك استخدام واجهة Throwable لالتقاط جميع الاستثناءات.

مثال:

<?php
    try {
        undefinedFunctionCall();
    } catch (Throwable $e) {
        // Handle error
        echo $e->getMessage(); // Call to undefined function undefinedFunctionCall()
    }

https://3v4l.org/Br0MG

لمزيد من المعلومات: http://php.net/manual/tr/language.errors.php7.php


بما أن معظم الإجابات هنا مطبقة بشكل غير معتاد ، فإليك نسخة غير قبيحة من أعلى إجابة صوت:

function errorHandler($errno, $errstr, $errfile = '', $errline = 0, $errcontext = array()) {
    //Do stuff: mail, log, etc
}

function fatalHandler() {
    $error = error_get_last();
    if($error) errorHandler($error["type"], $error["message"], $error["file"], $error["line"]);
}

set_error_handler("errorHandler")
register_shutdown_function("fatalHandler");

حسنا يبدو من الممكن للقبض على أخطاء فادحة بطريقة أخرى :)

ob_start('fatal_error_handler');

function fatal_error_handler($buffer){
    $error=error_get_last();
    if($error['type'] == 1){
        // type, message, file, line
        $newBuffer='<html><header><title>Fatal Error </title></header>
                    <style>                 
                    .error_content{                     
                        background: ghostwhite;
                        vertical-align: middle;
                        margin:0 auto;
                        padding:10px;
                        width:50%;                              
                     } 
                     .error_content label{color: red;font-family: Georgia;font-size: 16pt;font-style: italic;}
                     .error_content ul li{ background: none repeat scroll 0 0 FloralWhite;                   
                                border: 1px solid AliceBlue;
                                display: block;
                                font-family: monospace;
                                padding: 2%;
                                text-align: left;
                      }
                    </style>
                    <body style="text-align: center;">  
                      <div class="error_content">
                          <label >Fatal Error </label>
                          <ul>
                            <li><b>Line</b> '.$error['line'].'</li>
                            <li><b>Message</b> '.$error['message'].'</li>
                            <li><b>File</b> '.$error['file'].'</li>                             
                          </ul>

                          <a href="javascript:history.back()"> Back </a>                          
                      </div>
                    </body></html>';

        return $newBuffer;

    }

    return $buffer;

}

لا توفر PHP الوسائل التقليدية للاصطياد والشفاء من الأخطاء القاتلة. وهذا لأن المعالجة لا يجب استعادتها عادة بعد خطأ فادح. إن السلسلة التي تتطابق مع المخزن المؤقت للإخراج (كما هو مقترح بواسطة المنشور الأصلي ، الأسلوب الموصوف في PHP.net) هي بالتأكيد غير مستنيرة. انها ببساطة غير موثوق بها.

استدعاء دالة mail () من داخل أسلوب معالج الأخطاء يثبت أنه يمثل مشكلة أيضًا. إذا كان لديك الكثير من الأخطاء ، فسيتم تحميل خادم البريد الخاص بك مع العمل ، ويمكنك أن تجد نفسك في صندوق بريد شروي. لتجنب ذلك ، قد تفكر في تشغيل cron لمسح سجلات الأخطاء بشكل دوري وإرسال إعلامات وفقًا لذلك. قد ترغب أيضًا في البحث في برنامج مراقبة النظام ، مثل Nagios .

للتحدث قليلاً عن تسجيل وظيفة إيقاف التشغيل:

صحيح أنه يمكنك تسجيل وظيفة إيقاف التشغيل ، وهذه إجابة جيدة.

النقطة هنا هي أننا يجب ألا نحاول عادةً الاستعادة من الأخطاء الفادحة ، خاصةً من خلال استخدام تعبير عادي ضد المخزن المؤقت للإخراج. كنت أجيب على الإجابة المقبولة ، التي ربطت اقتراحًا على php.net تم تغييره أو إزالته منذ ذلك الحين.

كان هذا الاقتراح هو استخدام تعبير عادي ضد المخزن المؤقت للإخراج أثناء معالجة الاستثناء ، وفي حالة حدوث خطأ فادح (يتم اكتشافه بواسطة المطابقة ضد أي نص خطأ تم تهيئته) ، حاول القيام ببعض الاستعادة أو المعالجة المستمرة. لن تكون هذه ممارسة موصى بها (أعتقد أن هذا هو السبب في أنني لا أستطيع العثور على الاقتراح الأصلي أيضًا. فأنا إما أتعرض له أو قام مجتمع PHP بإطلاقه).

قد يكون من المفيد ملاحظة أن الإصدارات الحديثة من PHP (حوالي 5.1) يبدو أنها تستدعي وظيفة إيقاف التشغيل سابقًا ، قبل أن يتم استرجاع استدعاء التخزين المؤقت للإخراج. في الإصدار 5 والإصدارات السابقة ، كان هذا الترتيب عكسيًا (تبعت استعادة رد التخزين المؤقت وظيفة إيقاف التشغيل). أيضا ، منذ حوالي 5.0.5 (وهو وقت سابق بكثير من إصدار السائل 5.2.3) ، يتم إلغاء تحميل الكائنات بشكل جيد قبل أن يتم استدعاء وظيفة إيقاف التشغيل المسجلة ، لذلك لن تتمكن من الاعتماد على كائنات في الذاكرة الخاصة بك للقيام الكثير من أي شيء.

لذا فإن تسجيل وظيفة إيقاف التشغيل أمر جيد ، لكن نوع المهام التي يجب أن تؤديها وظيفة إيقاف التشغيل ربما تكون محدودة بعدد قليل من إجراءات إيقاف التشغيل اللطيفة.

المفتاح الرئيسي هنا هو مجرد بعض كلمات الحكمة لأي شخص يتعثر في هذا السؤال ويرى النصيحة في الإجابة المقبولة في الأصل. لا regex المخزن المؤقت للإخراج.


لا يمكنك إلقاء استثناء داخل وظيفة إيقاف التشغيل المسجلة مثل ذلك:

<?php
function shutdown() {
    if (($error = error_get_last())) {
       ob_clean();
       throw new Exception("fatal error");
    }
}

try {
    $x = null;
    $x->method()
} catch(Exception $e) {
    # this won't work
}
?>

ولكن يمكنك التقاط وإعادة توجيه الطلب إلى صفحة أخرى.

<?php
function shutdown() {
    if (($error = error_get_last())) {
       ob_clean();
       # raport the event, send email etc.
       header("Location: http://localhost/error-capture");
       # from /error-capture, you can use another redirect, to e.g. home page
    }
}
register_shutdown_function('shutdown');

$x = null;
$x->method()
?>

لدى PHP أخطاء قاتلة قابلة للتحويل. يتم تعريفها على أنها E_RECOVERABLE_ERROR. يصف دليل PHP E_RECOVERABLE_ERROR بأنه:

خطأ قاتلة Catchable. يشير ذلك إلى حدوث خطأ خطير على الأرجح ، ولكنه لم يترك المحرك في حالة غير مستقرة. إذا لم يتم set_error_handler() الخطأ بواسطة مؤشر محدد من قبل المستخدم (راجع أيضًا set_error_handler() ) ، set_error_handler() إحباط التطبيق لأنه E_ERROR.

يمكنك "التقاط" هذه الأخطاء "الفادحة" باستخدام set_error_handler() والتحقق من E_RECOVERABLE_ERROR. أجد أنه من المفيد رمي استثناء عندما يتم اكتشاف هذا الخطأ ، بعد ذلك يمكنك استخدام try / catch.

يقدم هذا السؤال والإجابة مثالاً مفيدًا: كيف يمكنني التقاط "خطأ فادح قابل للفحص" على تلميح نوع PHP؟

ومع ذلك ، يمكن معالجة أخطاء E_ERROR ، ولكن لا يمكن استردادها من حيث أن المحرك في حالة غير مستقرة.


لقد قمت بتطوير هذه الوظيفة لجعلها ممكنة لرمز "sandbox" الذي قد يتسبب في خطأ فادح. نظرًا لأن الاستثناءات التي يتم طرحها من إغلاق register_shutdown_function لا تنبعث من مجموعة مكالمات الخطأ قبل فادحًا ، فأنا مضطر للخروج بعد هذه الوظيفة لتوفير طريقة موحدة لاستخدامها.

function superTryCatchFinallyAndExit( Closure $try, Closure $catch = NULL, Closure $finally )
{
    $finished = FALSE;
    register_shutdown_function( function() use ( &$finished, $catch, $finally ) {
        if( ! $finished ) {
            $finished = TRUE;
            print "EXPLODE!".PHP_EOL;
            if( $catch ) {
                superTryCatchFinallyAndExit( function() use ( $catch ) {
                    $catch( new Exception( "Fatal Error!!!" ) );
                }, NULL, $finally );                
            } else {
                $finally();                
            }
        }
    } );
    try {
        $try();
    } catch( Exception $e ) {
        if( $catch ) {
            try {
                $catch( $e );
            } catch( Exception $e ) {}
        }
    }
    $finished = TRUE;
    $finally();
    exit();
}

ليس صحيحا. تسمى الأخطاء القاتلة ذلك لأنها قاتلة. لا يمكنك التعافي منها.


مجرد خدعة لطيفة للحصول على الأسلوب error_handler الحالي =)

<?php
register_shutdown_function('__fatalHandler');
function __fatalHandler()
{
    $error      = error_get_last();

    //check if it's a core/fatal error, otherwise it's a normal shutdown
    if($error !== NULL && $error['type'] === E_ERROR) {
        //Bit hackish, but the set_exception_handler will return the old handler
        function fakeHandler() { }
        $handler = set_exception_handler('fakeHandler');
        restore_exception_handler();
        if($handler !== null) { 
            call_user_func($handler, new ErrorException($error['message'], $error['type'], 0, $error['file'], $error['line']));
        }
        exit;
    }
}
?>

كما لا أريد أن أشير إلى أنه إذا اتصلت

<?php
ini_set('display_errors', false);
?>

توقف Php عن عرض الخطأ ، وإلا سيتم إرسال نص الخطأ إلى العميل قبل معالج الأخطاء الخاص بك





fatal-error