doortry.ru
DoorTry - коллекционер ошибок PHP7
in1 v1.10

Подключение обработчика ошибок/исключений - Connect error/exception handler

Для подключения обработчика ошибок/исключений к сайтам на PHP7-PHP5 следует выполнить всего два шага:
1 шаг. Включить в стартовый файл сайта, например index.php, собственно вызов обработчика.

Вызов обработчика
Что будет происходить?

При запуске сайта вначале загрузится оболочка для обработки ошибок. В представленном нами случае - сценарий DoorTryerPage.php из каталога сайта TDoorTryer.

Затем загрузится и запустится собственно пользовательский сайт (в данном примере Main.php).

Запускаемый сайт укрыт в оболочке try - catch для перехода в "открытую дверь" - DoorTryPage к отработке системных или пользовательских исключений.
2 шаг.  Включить в состав сайта модуль сценария DoorTryerPage.php, реализующего обработку ошибок/исключений.
<?php
// PHP7/HTML5, EDGE/CHROME                            *** DoorTryerPage.php ***
// ****************************************************************************
// * doortry.ru         Собрать и обработать ошибку или исключение PHP5-PHP7, *
// *           сформировать страницу с выводом сообщения и комментария к нему *
// ****************************************************************************
//                                                   Автор:       Труфанов В.Е.
// v1.2                                              Дата создания:  09.04.2019
// Copyright © 2019 tve                              Посл.изменение: 17.02.2020
/**
 * В DoorTryer заложены все типы ошибок: через установленный модуль от 
 * set_error_handler обрабатывается большинство ошибок, остальные ошибки 
 * вылавливаются после завершения работы сценария через 
 * register_shutdown_function, через try-catch-error обрабатываются исключения
 * 
 * Сообщение об ошибке или исключении собирается на основании 5 параметров:
 * $errstr - текст сообщения; $errtype - тип сообщения; $errline - строка 
 * сценария, где произошла ошибка или исключение; $errfile - файл сценария; 
 * $errtrace - трассировка всплывания сообщения
**/
// ------------------------------------------ Используемые регулярные выражения
// "фрагмент с типом ошибки с начала строки до ":"
define ("regErrorType",   "/^[A-Za-z_]{1,}:/u");
// "фрагмент трассировки с начала строки до "thrown"
define ("regThrown",      "/^[\s\S]{1,}thrown/u");
// "фрагмент от "#2" до конца строки
define ("regTrace2",      "/#2[\s\S]{1,}$/u");
// "фрагмент трассировки в сообщении об ошибке"
define ("regTrace",       "/Stack trace:[\s\S]{1,}$/u");
// ------------------------------------------- Массив зарегистрированных ошибок
// 1 - фатальная ошибка во время выполнения
$TypeErrors[E_ERROR]             = "E_ERROR";
// 2 - предупреждение во время выполнения        
$TypeErrors[E_WARNING]           = "E_WARNING"
// 4 - ошибка трансляции
$TypeErrors[E_PARSE]             = "E_PARSE";
// 8 - уведомление о проблеме
$TypeErrors[E_NOTICE]            = "E_NOTICE";
// 16 - фатальная ошибка ядра PHP
$TypeErrors[E_CORE_ERROR]        = "E_CORE_ERROR";
// 32 - предупреждение ядра PHP
$TypeErrors[E_CORE_WARNING]      = "E_CORE_WARNING";
// 64 - фатальная ошибка движка ZEND
$TypeErrors[E_COMPILE_ERROR]     = "E_COMPILE_ERROR";
// 128 - предупреждение движка ZEND
$TypeErrors[E_COMPILE_WARNING]   = "E_COMPILE_WARNING";
// 256 - фатальная ошибка по trigger_error()
$TypeErrors[E_USER_ERROR]        = "E_USER_ERROR";
// 512 - предупреждение по trigger_error()
$TypeErrors[E_USER_WARNING]      = "E_USER_WARNING";
// 1024 - уведомление по trigger_error()
$TypeErrors[E_USER_NOTICE]       = "E_USER_NOTICE";
// 2048 - рекомендация по улучшению кода
$TypeErrors[E_STRICT]            = "E_STRICT"
// 4096 - ошибка с возможностью обработки
$TypeErrors[E_RECOVERABLE_ERROR] = "E_RECOVERABLE_ERROR";
// 8192 - устаревшая конструкция
$TypeErrors[E_DEPRECATED]        = "E_DEPRECATED"
// 16384 - устаревшее по trigger_error()
$TypeErrors[E_USER_DEPRECATED]   = "E_USER_DEPRECATED"
// 32767
$TypeErrors[E_ALL]               = "E_ALL"
// ****************************************************************************
// *    Выбрать из строки подстроку, соответствующую регулярному выражению    *  
// ****************************************************************************
function findes($preg,$string,&$point=0)
{
   
$findes='';
   
$value=preg_match($preg,$string,$matches,PREG_OFFSET_CAPTURE);
   if (
$value>0)
   {
      
$findes=$matches[0][0];
      
$point=$matches[0][1];
   }
   return 
$findes;
}
// ****************************************************************************
// *         Определить: является ли версия текущего PHP-сценария             *
// *                        седьмой или большей                               *
// ****************************************************************************
function isPhp7()
{
   
$Result=False;
   if (
defined('PHP_VERSION_ID')) 
   {
      if (
PHP_VERSION_ID>=70000$Result=True;
   }
   return 
$Result;
}
// ****************************************************************************
// *    Выбрать из строки последнюю подстроку, соответствующую регулярному    *
// *         выражению. При необходимости показать трассировку поиска         * 
// ****************************************************************************
function LastFindes($preg,$string,&$point=0,$say=false)
{
   
$Result='';
   
$x=preg_match_all($preg,$string,$imatches,PREG_OFFSET_CAPTURE);
   if (
$say==true)
   {
      echo 
'<br>'.'$string: '.$string;
      echo 
'<br>'.'$preg: '.$preg;
   }
   if (
$x>0)
   {
      for (
$i=0$i<count($imatches); $i++)
        {
            
$findes=$imatches[$i];    
            for (
$j=0$j<count($findes); $j++)
            {
               if (
$say==true)
               {
                  echo 
'<br>$findes['.$j.'] = '.
                  
$findes[$j][0].' Point = '.
                  
$findes[$j][1];
               } 
               
$Result=$findes[$j][0];
               
$point=$findes[$j][1]; 
            }
         }
      }
   return 
$Result;
}
// ****************************************************************************
// *    Проинициализировать параметры php.ini для управления выводом ошибок   *
// ****************************************************************************
function InisetErrors()
{
   
// Определяем режим вывода ошибок:
   //   если display_errors = on, то в случае ошибки браузер получит html 
   // c текстом ошибки и кодом 200
   //   если же display_errors = off, то для фатальных ошибок код ответа будет 500
   // и результат не будет возвращён пользователю, для остальных ошибок – 
   // код будет работать неправильно, но никому об этом не расскажет
   
ini_set('display_errors','Off');
   
// Определяем режим вывода ошибок при запуске PHP:
   //   если = on, то даже при включённом display_errors возникающие ошибки во 
   // время запуска PHP, не будут отображаться. 
   
ini_set('display_startup_errors','Off');
   
// Определяем ведение журнала, в котором будут сохраняться сообщения об ошибках.
   // Это может быть журнал сервера или error_log. Применимость этой настройки 
   // зависит от конкретного сервера.
   //   При работе на готовых работающих web сайтах следует протоколировать 
   // ошибки там, где они отображаются. Настойчиво рекомендуем включать директиву 
   // display_startup_errors только для отладки.
   
ini_set('log_errors','On');
   
ini_set('error_log','log.txt');
   
// Определяем типы выводимых ошибок
   // (здесь указываем все, кроме устаревающих)
   
error_reporting(E_ALL & ~E_DEPRECATED);
}
// ****************************************************************************
// *      Проверить наличие ключа в массиве зарегистрированных ошибок PHP     *
// ****************************************************************************
function terIsKey($inkey)
{
   global 
$TypeErrors;
   
$result=false;
   foreach(
$TypeErrors as $key => $value
   { 
      if (
$inkey==$key)
      {
         
$result=true;
         break;
      } 
   }
   return 
$result;         
}
// ****************************************************************************
// *               Определить наименование по типу ошибки и                   *
// *                 отловить незафиксированный тип ошибки                    *
// ****************************************************************************
function terGetValue($inkey)
{
   global 
$TypeErrors;
   
$result='E_UNKNOWN';
   foreach(
$TypeErrors as $key => $value
   { 
      if (
$inkey==$key)
      {
         
$result=$value;
         break;
      } 
   }
   return 
$result;         
}
// ****************************************************************************
// *    Выбрать подстроку трассировки из текста принудительного исключения    *
// ****************************************************************************
function terGetTrace2($e)
{
   
// Выбираем из сообщения трассировку, начиная со 2 строки "#2" (для того,
   // чтобы отрезать трассировку, вызванную принудительным исключениеми) и
   // добавляем в хвосте ограничитель для выборки строк трассировки "#999" 
   
$SayTrass='';
   
$value=preg_match_all(regTrace2,$e,$matches,PREG_OFFSET_CAPTURE);
   if (
$value>0)
   {
      
$findes=$matches[0]; 
      
$SayTrass=$findes[0][0].'#999 ';  
   }
   
// Инициируем счетчик выводимых строк трассировки и выбираем первую строку 
   
$i=0;  $Result='';
   
$findes=findes("/#[\s\S]{1,}?#/u",$SayTrass);
   
$findes=substr($findes,0,strlen($findes)-1);
   while (
strlen($findes)>0)
   {
      
// Выделяем остаток трассировки
      
$SayTrass=substr($SayTrass,strlen($findes));
      
// Выделяем фрагмент прежнего счетчика строк
      
$numbers=findes("/#[0-9]{1,}\s/u",$findes);
      
// Формируем и выводим актуальную строку трассировки
      
$Result=$Result.'#'.$i.' '.substr($findes,strlen($numbers));
      
// Выбираем следующую строку 
      
$findes=findes("/#[\s\S]{1,}?#/u",$SayTrass);
      
$findes=substr($findes,0,strlen($findes)-1);
      
$i=$i+1;
   }
   return 
$Result;
}
// ****************************************************************************
// * Сформировать и подготовить для вывода сообщение об ошибке или исключении *
// ****************************************************************************
// Проверить разрешен ли вывод данного типа ошибок error_reporting-ом
function isSay($errtype,$typelast)
{
   
// Выясняем типы выводимых ошибок
   
$errorlevel=error_reporting();
   
// Выделяем в битах разрешенных типов ошибок
   // бит типа текущей ошибки
   
$iz=$errorlevel&$typelast;
   
// Если бит был установлен, то разрешаем вывод
   
if ($iz>0$Result=true;
   else 
$Result=false;
   return 
$Result;
}
// Сформировать и подготовить для вывода сообщение об ошибке или исключении 
function DoorTryExec($errstr,$errtype,$errline='',$errfile='',$errtrace='',$typelast=1)
{
   if (
isSay($errtype,$typelast))
   {
      
$uripage="https://doortry.ru/DoorTryError.php".
      
"?estr=".urlencode($errstr).
      
"&etype=".urlencode($errtype).
      
"&eline=".urlencode($errline).
      
"&efile=".urlencode($errfile).
      
"&etrace=".urlencode($errtrace);
      
// Вызываем страницу ошибки через javascript
      
echo '<script>';
      echo 
'location.assign("'.$uripage.'")';
      echo 
'</script>';
   }
}
// ****************************************************************************
// * [SHT]     Обработать пропущенные ошибки после завершения работы сценария *
// ****************************************************************************
function DoorTryShutdown()
{
   global 
$TypeErrors;
   
$lasterror=error_get_last();
   
$typelast=intval($lasterror['type']);
   if (
terIsKey($typelast))
   {
      
// Пробуем выбрать трассировку
      
$point=0;
      
$string=$lasterror['message'];
      
$trace=findes(regTrace,$string,$point);
      
// Если трассировка есть, то отделяем трассировку от сообщения 
      
if ($trace>'') {$string=substr($string,0,$point);} 
      
// Так как текст трассировки может завершаться словом "thrown",
      // то отрезаем его
      
$thrown=findes(regThrown,$trace);
      if (
$thrown>'') {$trace=substr($thrown,0,strlen($thrown)-6);} 
      
// Так как сообщение об ошибке может начинаться с "Uncaught Error" - 
      // "необнаруженная ошибка", то отрезаем этот фрагмент
      
$thrown=findes('/Uncaught Error: /u',$string,$point);
      if (
$thrown>'') {$string=substr($string,$point+16);}
      
// Так как сообщение об ошибке может начинаться с "Uncaught exception 
      // 'Exception' with message" - "необнаруженное исключение", 
      // то отрезаем этот фрагмент
      
$thrown=findes("/Uncaught exception 'Exception' with message /u",$string,$point);
      if (
$thrown>'') {$string=substr($string,$point+44);}
      
// Так как сообщение об ошибке может начинаться с "Uncaught Exception" - 
      // "необнаруженное исключение", то отрезаем этот фрагмент
      
$thrown=findes("/Uncaught Exception: /u",$string,$point);
      if (
$thrown>'') {$string=substr($string,$point+20);}
      
// Так как сообщение об ошибке может заканчиваться указанием строки с ошибкой,
      // то отрезаем этот фрагмент
      
LastFindes("/in /u",$string,$point,false);
      if (
$point>0) {$string=substr($string,0,$point);}      
      
// Определяем тип ошибки, формируем и выводим сообщение
      
$TypeError=terGetValue(intval($typelast));
      
DoorTryExec
      
(
         
$string,$TypeError.' [SHT]',
         
$lasterror['line'],$lasterror['file'],$trace,$typelast
      
);
   }

// ****************************************************************************
// * [HND]       Обработать ошибки, отловленные до завершения работы сценария *
// ****************************************************************************
function DoorTryHandler($errno,$errstr,$errfile,$errline)
{
   global 
$TypeErrors;
   
// Если error_reporting нулевой, значит, использован оператор @,
   // все ошибки должны игнорироваться
   
if (!error_reporting())
   {
      return 
true;
   }
   
$typelast=intval($errno);
   if (
terIsKey($typelast))
   {
      
$TypeError=terGetValue(intval($typelast));
      try
      {
         
// Делаем принудительное исключение для того,
         // чтобы поймать трассировку
         
throw new Exception('MakeTrass!');
      }
      catch (
Exception $e)
      {
         
// Выделяем трассировку
         
$errtrace=terGetTrace2($e); 
         
// Запускаем вывод ошибки 
         
DoorTryExec
         
(
            
$errstr,$TypeError.' [HND]',$errline,$errfile,$errtrace,$typelast
         
);
      }
   }
   else
   {
      
DoorTryExec('Нет ключа в зарегистрированных ошибках PHP!','E_ERROR','','','',1);
   }
}  
// ****************************************************************************
// * [PGE]                    Обработать пользовательские и другие исключения *
// ****************************************************************************
function DoorTryPage($e)
{
   
// Определяем тип ошибки
   
$value=preg_match_all(regErrorType,$e,$matches,PREG_OFFSET_CAPTURE);
   if (
$value>0)
   {
      
$findes=$matches[0]; 
      
$TypeError=$findes[0][0]; $Point=$findes[0][1];  
   }
   else
   {
      
$TypeError='NoDefine'$Point=1;  
   }
   
// При неопределенном типе ошибки для PHP5 
   // назначаем тип ошибки по типу класса
   
if ($TypeError=='NoDefine')
   {
      if (!(
isPhp7()))
      {
         
$TypeError=get_class($e).':';   
      }
   }
   
DoorTryExec
   
(
      
$e->getMessage(),$TypeError.' [PGE]',
      
$e->getLine(),$e->getFile(),$e->getTraceAsString(),$Point
   
);
}
// ****************************************************************************
// *                Запустить обработку ошибок и исключений                   *
// ****************************************************************************
// Связываем ошибки с исключениями
class E_EXCEPTION         extends Exception {}     // 0
class E_ERROR             extends E_EXCEPTION {}   // 1
class E_WARNING           extends E_EXCEPTION {}   // 2
class E_PARSE             extends E_EXCEPTION {}   // 4
class E_NOTICE            extends E_EXCEPTION {}   // 8
class E_CORE_ERROR        extends E_EXCEPTION {}   // 16
class E_CORE_WARNING      extends E_EXCEPTION {}   // 32
class E_COMPILE_ERROR     extends E_EXCEPTION {}   // 64
class E_COMPILE_WARNING   extends E_EXCEPTION {}   // 126
class E_USER_ERROR        extends E_EXCEPTION {}   // 256
class E_USER_WARNING      extends E_EXCEPTION {}   // 512
class E_USER_NOTICE       extends E_EXCEPTION {}   // 1024
class E_STRICT            extends E_EXCEPTION {}   // 2048
class E_RECOVERABLE_ERROR extends E_EXCEPTION {}   // 4096
class E_DEPRECATED        extends E_EXCEPTION {}   // 8192
class E_USER_DEPRECATED   extends E_EXCEPTION {}   // 16384
class E_ALL               extends E_EXCEPTION {}   // 32767
// Инициализируем параметры Php.ini для управления выводом ошибок
InisetErrors();
// Регистрируем функцию, которая будет выполняться по завершению работы скрипта
register_shutdown_function('DoorTryShutdown');
// Регистрируем новую функцию-обработчик для всех типов ошибок
set_error_handler("DoorTryHandler",E_ALL);
// ****************************************************** DoorTryerPage.php ***