Модуль сообщить об ошибке

Когда-то, для одной организации разрабатывал интернет-магазин на базе популярного бесплатного французского движка Prestashop 1.6.0.12. На данный момент уже доступна более новая версия. Так вот, необходимо было разработать модуль, умеющий отправлять сообщение об ошибке в тексте. Пользователь выделяет слово или фразу, а затем нажимает Ctrl+Enter, сообщение об ошибке отправляется администратору сайта.

Данный модуль простой и не является чем-то особенным. Тестировался на версии 1.6.012. Устанавливается модуль просто, через интерфейс сайта. Итак, приступим.

Для начала создадим папку с именем blockcheckspelling, которая будет содержать все файлы модуля и будет располагаться в папке mosules корня движка Prestashop.

Внутри каталога blockcheckspelling создадим еще 3 папки: css, mails и translations. В css будет размещен файл стилей, в mails — шаблоны писем на нужном языке, в translations — соответственно перевод сообщений на нужный нам язык.

Для начала работы модуля создадим среди данных трех каталогов файл с именем blockcheckspelling.php и следующего содержания:

<?php
/*
*  @author Denis Sitko  <info@sitkodenis.ru>
*  @copyright  2015 Denis Sitko
*  @license    http://opensource.org/licenses/afl-3.0.php  Academic Free License (AFL 3.0)
*/
if (!defined('_CAN_LOAD_FILES_'))
  exit;

class BlockCheckSpelling extends Module
{
  public function __construct()
  {
    $this->name = 'blockcheckspelling';
    $this->tab = 'front_office_features';
    $this->version = '1.0';
    $this->author = 'Denis Sitko';
    $this->controllers = ['form', 'senderror'];
    $this->need_instance = 0;
    $this->bootstrap = true;

    parent::__construct();

    $this->displayName = $this->l('Error in the text');
    $this->description = $this->l('Sending a visitor notices an error on the site.');
    $this->ps_versions_compliancy = ['min' => '1.6', 'max' => _PS_VERSION_];
  }

  public function install($delete_params = true)
  {
    if (!parent::install() || !$this->registerHook('header') || !$this->registerHook('displayCheckSpelling'))
      return false;

    if ($delete_params)
    {
      Configuration::updateValue('BLOCKCHECKSPELLING_EMAIL', Configuration::get('PS_SHOP_EMAIL'));
    }

    return true;
  }

  public function uninstall($delete_params = true)
  {
    if ($delete_params)
    {
      Configuration::deleteByName('BLOCKCHECKSPELLING_EMAIL');
    }

    return parent::uninstall();
  }

  public function hookHeader($params)
  {
    $this->context->controller->addCss($this->_path . '/css/blockcheckspelling.css');
    $this->context->controller->addJqueryPlugin('fancybox');
  }

  public function hookDisplayCheckSpelling($params)
  {
    return $this->display(__FILE__, 'block.tpl');
  }

  public function hookAjaxForm()
  {
    return $this->display(__FILE__, 'form.tpl');
  }

  public function getContent()
  {
    $errors = [];
    $output = null;

    if (Tools::isSubmit('submit'.$this->name))
    {
      if (!Configuration::updateValue('BLOCKCHECKSPELLING_EMAIL', (string)Tools::getValue('BLOCKCHECKSPELLING_EMAIL')))
        $errors[] = $this->l('Cannot update settings');

      if (count($errors) > 0)
        $output .= $this->displayError(implode('<br />', $errors));
      else
        $output .= $this->displayConfirmation($this->l('Settings updated successfully'));
    }

    return $output.$this->displayForm();
  }

  public function displayForm()
  {
    $default_lang = (int)Configuration::get('PS_LANG_DEFAULT');

    $fields_form[0]['form'] = [
      'legend' => [
        'title' => $this->l('Settings'),
        'icon' => 'icon-cog'
      ],
      'input' => [
        [
          'type' => "text",
          'name' => 'BLOCKCHECKSPELLING_EMAIL',
          'label' => $this->l('Email'),
        ],
      ],
      'submit' => [
        'title' => $this->l('Save'),
        'class' => 'btn btn-default pull-right'
      ]
    ];

    $helper = new HelperForm();

    $helper->module = $this;
    $helper->name_controller = $this->name;
    $helper->token = Tools::getAdminTokenLite('AdminModules');
    $helper->currentIndex = AdminController::$currentIndex.'&configure='.$this->name;

    $helper->default_form_language = $default_lang;
    $helper->allow_employee_form_lang = $default_lang;

    $helper->title = $this->displayName;
    $helper->show_toolbar = true;
    $helper->toolbar_scroll = true;
    $helper->submit_action = 'submit'.$this->name;

    $helper->toolbar_btn = [
      'save' =>
        [
          'desc' => $this->l('Save'),
          'href' => AdminController::$currentIndex.'&configure='.$this->name.'&save'.$this->name.
            '&token='.Tools::getAdminTokenLite('AdminModules'),
        ]
    ];

    $helper->fields_value['BLOCKCHECKSPELLING_EMAIL'] = (string)Configuration::get('BLOCKCHECKSPELLING_EMAIL');

    return $helper->generateForm($fields_form);
  }

  public function sendError($errorText)
  {
    $template_vars = array(
      '{errorText}' => Tools::safeOutput($errorText),
      '{shop_name}' => Configuration::get('PS_SHOP_NAME'),
      '{date}' => date('d.m.Y H:i:s')
    );

    return Mail::Send($this->context->language->id,
      'blockcheckspelling',
      'Орфографическая ошибка на сайте',
      $template_vars,
      Configuration::get('BLOCKCHECKSPELLING_EMAIL'),
      null,
      Configuration::get('PS_SHOP_EMAIL'),
      Configuration::get('PS_SHOP_NAME'),
      null,
      null,
      dirname(__FILE__).'/mails/',
      false,
      $this->context->shop->id
    );
  }
}

Данный файл является основным загружаемым файлом модуля. Здесь в конструкторе мы задаем основные настройки модуля. В свойстве

$this->controllers = ['form', 'senderror'];

перечисляться дополнительные имена контроллеров. Давайте их создадим.

Файл form.php для вызова формы:

<?php
/*
*  @author Denis Sitko  <info@sitkodenis.ru>
*  @copyright  2015 Denis Sitko
*  @license    http://opensource.org/licenses/afl-3.0.php  Academic Free License (AFL 3.0)
*/

include_once(dirname(__FILE__).'/../../config/config.inc.php');
include_once(dirname(__FILE__).'/blockcheckspelling.php');

$blockCheckSpelling = new BlockCheckSpelling();
echo $blockCheckSpelling->hookAjaxForm();

Файл senderror.php для отправки сообщения об ошибке:

<?php
/*
*  @author Denis Sitko  <info@sitkodenis.ru>
*  @copyright  2015 Denis Sitko
*  @license    http://opensource.org/licenses/afl-3.0.php  Academic Free License (AFL 3.0)
*/
include_once(dirname(__FILE__).'/../../config/config.inc.php');
include_once(dirname(__FILE__).'/blockcheckspelling.php');

$blockCheckSpelling = new BlockCheckSpelling();

$errorText = trim($_POST['errorText']);

if (!empty($errorText) && Validate::isMessage($errorText)){
  $blockCheckSpelling->sendError($errorText);

  Tools::redirect('index.php');
}

Здесь же в корне модуля разместим файл index.php, его можно скопировать из любого другого существующего модуля. Еще необходимо приготовить и разместить в этом же каталоге 2 файла: logo.png и logo.gif (размером 16x16px).

Для корректной установки модуля создадим 2 конфигурационных файла.

Файл config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<module>
  <name>blockcallme</name>
  <displayName><![CDATA[Error in the text]]></displayName>
  <version><![CDATA[1.0]]></version>
  <description><![CDATA[Sending a visitor notices an error on the site.]]></description>
  <author><![CDATA[Denis Sitko]]></author>
  <tab><![CDATA[front_office_features]]></tab>
  <is_configurable>1</is_configurable>
  <need_instance>0</need_instance>
  <limited_countries></limited_countries>
</module>

Файл config_ru.xml

<?xml version="1.0" encoding="UTF-8" ?>
<module>
  <name>blockcheckspelling</name>
  <displayName><![CDATA[Ошибка в тексте]]></displayName>
  <version><![CDATA[1.0]]></version>
  <description><![CDATA[Отправка посетителем уведомления об ошибке на сайте.]]></description>
  <author><![CDATA[Denis Sitko]]></author>
  <tab><![CDATA[front_office_features]]></tab>
  <is_configurable>1</is_configurable>
  <need_instance>0</need_instance>
  <limited_countries></limited_countries>
</module>

В функции install() при установке мы создаем новый параметр в конфигурации Perstashop.

Configuration::updateValue('BLOCKCHECKSPELLING_EMAIL', Configuration::get('PS_SHOP_EMAIL'));

Аналогично, в функции uninstall() мы его удаляем из системы.

В функции hookHeader() мы подключаем необходимые нам для работы файлы. Fancybox уже имеется в системе изначально, так что его просто подключаем строкой

$this->context->controller->addJqueryPlugin('fancybox');

А вот файл со стилями blockcheckspelling.css создадим и разместим в папке css.

#blockcheckspelling {
  float: left;
  margin: 0 20px 0 20px;
  padding: 3px;
  width: 350px;
  font-size: 12px;
  color: #ccc;
}

#blockcheckspelling span.keys {
  color: #fff;
  background-color: #3a8a41;
  padding: 3px;
  -webkit-border-radius: 10px 2px 10px 2px;
  -moz-border-radius: 10px 2px 10px 2px;
  border-radius: 10px 2px 10px 2px;
}

.block-call-window-cs {
  display: none;
}

hr {
  border-bottom: 1px solid #99CC66;
  box-shadow: 0 0 10px rgba(0,153,0,0.5);
}

h2.blockcheckspelling {
  font-size: 16px;
  font-weight: bold;
  margin: 15px 0 0 15px;
  color: #99CC66;
  text-shadow: 2px 2px 3px rgba(0,0,0,0.1);
}

.blockcheckspelling label {
  margin: 0 0 0 20px;
}

form.blockcheckspelling {
  margin: 0 auto;
  margin-top: 20px;
}

.blockcheckspelling input {
  font-family: "Helvetica Neue", Helvetica, sans-serif;
  font-size: 12px;
  outline: none;
}

.blockcheckspelling input[type=text] {
  color: #777;
  padding-left: 10px;
  margin: 10px;
  margin-top: 12px;
  margin-left: 18px;
  width: 93%;
  height: 35px;
  border: 1px solid #097b33;
  border-radius: 2px;
  box-shadow: inset 0 1.5px 3px rgba(190, 190, 190, .4), 0 0 0 5px #f5f7f8;
  -webkit-transition: all .4s ease;
  -moz-transition: all .4s ease;
  transition: all .4s ease;
}

.blockcheckspelling input[type=text]:hover {
  border: 1px solid #99CC66;
  box-shadow: inset 0 1.5px 3px rgba(190, 190, 190, .7), 0 0 0 5px #f5f7f8;
}

.blockcheckspelling input[type=text] {
  border: 1px solid #99CC66;
  box-shadow: inset 0 1.5px 3px rgba(190, 190, 190, .4), 0 0 0 5px #e6f2f9;
}

.blockcheckspelling input[type=submit] {
  float: right;
  margin-right: 20px;
  margin-top: 20px;
  width: 180px;
  height: 30px;
  font-size: 12px;
  font-weight: bold;
  color: #fff;
  background-color: #097b33;
  border: 1px solid #097b33;
  cursor: pointer;
}

.blockcheckspelling input[type=submit]:hover,
.blockcheckspelling input[type=submit]:active {
  background-color: #669966;
}

.blockcheckspelling input[type=submit]:disabled {
  background-color: #E8E8E8;
  border: 1px solid #C8C8C8;
  color: #C0C0C0;
}

Функция hookDisplayCheckSpelling() описывает хук, который подключает html-код и скрипт для вызова окна с сообщением об ошибке. Для данной функции создадим здесь же файл с именем block.tpl

<div id="blockcheckspelling">
  <span class="descr">{l s='Found a mistake? Select it and click' mod='blockcheckspelling'} <span class="keys">Ctrl + Enter</span></span>
</div>

<div class="block-call-window-cs"></div>

<script>
  $(function(){
    var selectText = function(){
      var text = "";
      if (window.getSelection) {
        text = window.getSelection();
      }else if (document.getSelection) {
        text = document.getSelection();
      }else if (document.selection) {
        text = document.selection.createRange().text;
      }
      return text.toString();
    };

    $(".block-call-window-cs").on("click", function (e) {
      e.preventDefault();

      $.ajax({
        type: "POST",
        cache: false,
        url: "/modules/blockcheckspelling/form.php",
        data: { text : selectText },
        success: function (data) {
          $.fancybox(data, {
            fitToView: false,
            width: 520,
            height: 210,
            autoSize: false,
            openEffect: 'none',
            closeEffect: 'none'
          });
        }
      });
    });

    $(document).on("keydown", function(e) {
      if ((e.keyCode == 10 || e.keyCode == 13) && e.ctrlKey) {
        $(".block-call-window-cs").trigger("click");
      }
    });
  });
</script>

Кстати, не забудьте подключить ваш хук в удобном для вас месте

{hook h="displayCheckSpelling"}

Теперь создадим в корне файл form.tpl, отвечающий за вид отображаемой формы

<h2 class="blockcheckspelling">{l s='Share a mistake in the text' mod='blockcheckspelling'}</h2>
<hr />
<form class="blockcheckspelling" action="{$module_dir}senderror.php" method="post">
  <label for="errorText">{l s='Spelling error in the phrase:' mod='blockcheckspelling'}</label>
  <input type="text" name="errorText" id="errorText" value="{$smarty.post.text}" />
  <input type="submit" value="{l s='Send an error' mod='blockcheckspelling'}" />
</form>

<script>
  $(function(){
    var errorText = $("#errorText").val();
    var $submit = $(".blockcheckspelling").find("input[type=submit]");

    if (errorText.length > 0){
      $submit.attr("disabled", false);
    } else {
      $submit.attr("disabled", true);
    }

    $("form").on("change", "#errorText", function(){
      if ($(this).val().length > 0){
        $submit.attr("disabled", false);
      } else {
        $submit.attr("disabled", true);
      }
    });
  });
</script>

В функции getContent() используется для сохранения настроек в админке данного модуля. Сам вывод полей формы для настроек находится в функции displayForm().

Функция sendError() используется контроллером senderror, про содержимое которого я писал уже выше. В функции происходит отправка данных на указанный адрес электронной почты.

Последнее, что осталось, разместить 2 файла с шаблонами писем в директории mails/ru/.

Файл blockcheckspelling.html

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/1999/REC-html401-19991224/strict.dtd">
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
    <title>Сообщение от магазина {shop_name}</title>

    <style>	@media only screen and (max-width: 300px){ 
        body {
          width:218px !important;
          margin:auto !important;
        }
        .table {width:195px !important;margin:auto !important;}
        .logo, .titleblock, .box, .footer, .space_footer{width:auto !important;display: block !important;}
        span.title{font-size:20px !important;line-height: 23px !important}
        td.box p{font-size: 12px !important;font-weight: bold !important;}
        .table-recap table, .table-recap thead, .table-recap tbody, .table-recap th, .table-recap td, .table-recap tr { 
          display: block !important; 
        }

        .table-recap tr td, .conf_body td{text-align:center !important;}
      }
      @media only screen and (min-width: 301px) and (max-width: 500px) {
        body {width:308px!important;margin:auto!important;}
        .table {width:285px!important;margin:auto!important;}	
        .logo, .titleblock, .linkbelow, .box, .footer, .space_footer{width:auto!important;display: block!important;}	
        .table-recap table, .table-recap thead, .table-recap tbody, .table-recap th, .table-recap td, .table-recap tr { 
          display: block !important; 
        }
        .table-recap tr td, .conf_body td{text-align:center !important;}
      }
      @media only screen and (min-width: 501px) and (max-width: 768px) {
        body {width:478px!important;margin:auto!important;}
        .table {width:450px!important;margin:auto!important;}	
        .logo, .titleblock, .box, .footer, .space_footer{width:auto!important;display: block!important;}
      }
      @media only screen and (max-device-width: 480px) {
        body {width:308px!important;margin:auto!important;}
        .table {width:285px;margin:auto!important;}	
        .logo, .titleblock, .box, .footer, .space_footer{width:auto!important;display: block!important;}
        .table-recap tr td, .conf_body td{text-align:center!important;}
      }
    </style>
  </head>
  <body style="-webkit-text-size-adjust:none;background-color:#fff;width:650px;font-family:Open-sans, sans-serif;color:#555454;font-size:13px;line-height:18px;margin:auto">
    <table class="table table-mail" style="width:100%;margin-top:10px;-moz-box-shadow:0 0 5px #afafaf;-webkit-box-shadow:0 0 5px #afafaf;-o-box-shadow:0 0 5px #afafaf;box-shadow:0 0 5px #afafaf;filter:progid:DXImageTransform.Microsoft.Shadow(color=#afafaf,Direction=134,Strength=5)">
      <tr>
        <td class="space" style="width:20px;padding:7px 0">&nbsp;</td>
        <td align="center" style="padding:7px 0">
          <table class="table" bgcolor="#ffffff" style="width:100%">
            <tr>
              <td align="center" class="logo" style="border-bottom:4px solid #333333;padding:7px 0">
                <a title="{shop_name}" href="{shop_url}" style="color:#337ff1">
                  <img src="{shop_logo}" alt="{shop_name}" />
                </a>
              </td>
            </tr>
            <tr>
              <td align="center" class="titleblock" style="padding:7px 0">
                <font size="2" face="Open-sans, sans-serif" color="#555454">
                  <span class="title" style="font-weight:500;font-size:28px;text-transform:uppercase;line-height:33px">Здравствуйте,</span>
                </font>
              </td>
            </tr>
            <tr>
              <td class="space_footer" style="padding:0!important">&nbsp;</td>
            </tr>
            <tr>
              <td class="box" style="border:1px solid #D6D4D4;background-color:#f8f8f8;padding:7px 0">
                <table class="table" style="width:100%">
                  <tr>
                    <td width="10" style="padding:7px 0">&nbsp;</td>
                    <td style="padding:7px 0">
                      <font size="2" face="Open-sans, sans-serif" color="#555454">
                        <p data-html-only="1" style="border-bottom:1px solid #D6D4D4;margin:3px 0 7px;text-transform:uppercase;font-weight:500;font-size:18px;padding-bottom:10px">
                          На сайте была обнаружена ошибка
                        </p>
                        <span style="color:#777">Фраза: <strong>{fio}</strong></span>
                        <span style="color: #777">Дата: <strong>{date}</strong></span>
                      </font>
                    </td>
                    <td width="10" style="padding:7px 0">&nbsp;</td>
                  </tr>
                </table>
              </td>
            </tr>
            <tr>
              <td class="space_footer" style="padding:0!important">&nbsp;</td>
            </tr>
            <tr>
              <td class="footer" style="border-top:4px solid #333333;padding:7px 0">
                <span><a href="{shop_url}" style="color:#337ff1">{shop_name}</a></span>
              </td>
            </tr>
          </table>
        </td>
        <td class="space" style="width:20px;padding:7px 0">&nbsp;</td>
      </tr>
    </table>
  </body>
</html>

Файл blockcheckspelling.txt

{shop_name}

Здравствуйте,

На сайте была обнаружена ошибка
в следующей фразе

{errorText}

Дата: {date}

Ну и конечно, не забываем положить файл с переводом в папку translations (ru.php)

<?php

global $_MODULE;
$_MODULE = array();
$_MODULE['<{blockcheckspelling}prestashop>block_9acb1671e5f7efb984602b324eb8bf4e'] = 'Нашли ошибку? Выделите ее и нажмите';
$_MODULE['<{blockcheckspelling}prestashop>blockcheckspelling_519719c01844d3991a43458039ed5a6b'] = 'Ошибка в тексте';
$_MODULE['<{blockcheckspelling}prestashop>blockcheckspelling_ea400a8ae277d9aa56116509bf1807f1'] = 'Отправка посетителем уведомления об ошибке на сайте.';
$_MODULE['<{blockcheckspelling}prestashop>blockcheckspelling_c1ee76f076a5b97e3b4b0c0e5703246e'] = 'Невозможно обновить настройки';
$_MODULE['<{blockcheckspelling}prestashop>blockcheckspelling_462390017ab0938911d2d4e964c0cab7'] = 'Настройки успешно обновлены';
$_MODULE['<{blockcheckspelling}prestashop>blockcheckspelling_f4f70727dc34561dfde1a3c529b6205c'] = 'Настройки';
$_MODULE['<{blockcheckspelling}prestashop>blockcheckspelling_ce8ae9da5b7cd6c3df2929543a9af92d'] = 'Электронная почта';
$_MODULE['<{blockcheckspelling}prestashop>blockcheckspelling_c9cc8cce247e49bae79f15173ce97354'] = 'Сохранить';
$_MODULE['<{blockcheckspelling}prestashop>form_15c1decf6b1b7add4b3cbc6c44de4f12'] = 'Поделиться ошибкой в тексте';
$_MODULE['<{blockcheckspelling}prestashop>form_11f3a9d7193127816c0db3690b5113cc'] = 'Орфографическая ошибка в фразе:';
$_MODULE['<{blockcheckspelling}prestashop>form_01881661b0715d201d4bc18826a48d33'] = 'Отправить ошибку';

Вот и все. Было не так и сложно. Конечно, данный код далеко не идеален, можно было бы некоторые моменты сделать по другому. Но я не квалифицируюсь в области разработки решений для Prestashop, просто в один момент была задача и ее надо было решить как можно скорее.

Да, для ленивых прилагаю архив с исходниками модуля.

Спасибо и до новых встреч.

Если вы нашли ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter.

Скрипт проверки ошибок на сайте

На нашем сайте — сайте, посвященном ошибкам, очень желательно было установить систему устранения этих самых ошибок. В Рунете для этих целей уже традиционно используется система Orphus.

Одно время скрипт этой системы успешно работал на сайте, но в один прекрасный момент вдруг перестал, и все попытки реанимировать его оказались тщетными.

Пришлось разрабатывать свой скрипт. Как оказалось, все не так-то просто. Помогли уроки JavaScript, ну и, конечно же, Google с Яндексом. В итоге, при сложении нескольких найденных блоков скрипта и почерпнутых из учебника знаний получился вполне рабочий модуль для проверки ошибок на сайте. Для его работы необходима поддержка PHP на хостинге.

Последняя версия скрипта 4.1 основана на плагине typo к CMS Drupal. Автор плагина Роман Архаров. В этой версии появилась функция отображения окружающего ошибку текста, сама же ошибка выделяется красным.

Модуль состоит из четырех файлов: mistakes.js, mistakes.css, mistakes.php и overlay.png.

Скачать можно отсюда.

Чтобы его подключить, нужно поместить эти файлы в директорию своего сайта, например в папку «mistakes» и на всех страницах сайта между тегами «head» вставить две строки:

<script type="text/javascript" charset="windows-1251" src="/путь до файла/mistakes.js"></script>
<link href="/путь до файла/mistakes.css" rel="stylesheet" type="text/css" />

При этом после src=» и href=» нужно прописать путь к файлу mistakes.js и mistakes.css соответственно.

Файл mistakes.js. В этом файле нужно изменить значение переменной misphploc (то, что между кавычками «») на путь к файлу mistakes.php.

Файл mistakes.php.
Здесь нужно изменить значение нескольких переменных:

$title — заголовок сообщения,
$to, — email, на который будут отправляться сообщения,
$mymail — email, от кого пришло сообщение.

Ну и, конечно же, для того чтобы посетитель знал, что он может посодействовать в исправлении ошибок, нужно разместить на страницах вашего сайта, например, такую фразу:

На сайте работает система проверки ошибок.
Обнаружив неточность в тексте, выделите ее и нажмите Ctrl + Enter.

Также открыть окошко отправки сообщения можно, кликнув по ссылке:

Отправить сообщение об ошибке

Код ссылки:

<a href="javascript:void(0)" onclick="PressLink()">Отправить сообщение об ошибке</a>

Исходная кодировка скрипта windows-1251. Если ваш сайт работает на utf-8, то создайте в папке со скриптом файл .htaccess и добавьте в нем строку:

AddDefaultCharset windows-1251

Также вам скорее всего нужно будет переформатировать в utf-8 файл mistakes.js

Скачать модуль mistakes-4.1.zip (zip архив 7Kb).

Если вас по каким-либо причинам не устраивает последняя версия, попробуйте предыдущую — 3.3.
mistakes3.3.zip (zip архив 6Kb).

Успешной вам борьбы с опечатками))
Вопросы и пожелания оставляйте в комментариях.

  1. Главная
  2. Форумы
  3. Техподдержка Drupal
  4. FAQ

Главные вкладки

  • Просмотр(активная вкладка)
  • Реакции

Всем привет !
Мож кто подскажет модуль, что бы работал по следующим принципу.
На странице есть кнопка, пользователь нажимает, появляется форма, он пишет что на странице не так или что дополнить, отправляет, приходит письмо админу (можно ЛС, емайл или отдельная страница с сообщениям … ), главное что бы админ в письме получал ссылку на ту страницу, название ноды с которой было отправлено сообщение. Есть такой модуль ?
Попробовал модуль Флаг, вроде всё устраивает, вот только не пойму как админу потом отменять отметку флага после исправления ошибки на странице ?!

  • FAQ

Всем привет! Давно не писал потому, что уже наступило жаркое лето, и я старался поменьше сидеть за компом, занимался спортом и принимал солнечные ванны. Сегодняшняя статья посвящена модулю: «Заметили Ошибку? — нажимай Ctrl + Q». Мне пришлось ввести данный модуль в связи с критикой моих посетителей, в частности Николая, за многочисленные ошибки по русскому языку. И этот модуль себя хорошо зарекомендовал. Уже множество ошибок исправлено благодаря нему и вам, неравнодушным читателям. Думаю, и Вам он может пригодится на вашем сайте. Также сегодня посоветовался с одним ГУРУ веб-программирования, который помог избавится от некрасивых мест в коде.

Так что, поехали!

Формулируем задачу: При обнаружении пользователем ошибки он выделяет данный текст и нажимает CTRL + Q. Всплывает модальное окно с текстовым полем, где уже находится выделенный текст, и кнопкой отправить. Пользователь поправляет ошибку в тексте и нажимает отправить. Нам на почту приходит: адрес страницы с которой произошла отправка формы, выделенный текст и поправленный вариант. Можно и не на почту сделать, а в админку CMS. Но у меня пока админка CMS не доделана, поэтому будем пока отсылать на почту.

Скрипт использует библиотеку jquery-1.9.0.min.js

Отслеживаем нажатие CTRL+Q

var isCtrl = false;

$(document).keyup(function (e) {
  if(e.which == 17) isCtrl=false;
}).keydown(function (e) {

  if(e.which == 17) isCtrl=true;
  if(e.which == 81 && isCtrl == true) {

      // Здесь можем писать действия, которые необходимы при нажатии CTRL+Q
  }

});

Далее необходимо, чтобы у нас всплывало модальное окно. Поискав немного в интернете, нашёлся отличный вариант. В него сразу прописываем форму с текстовым полем и кнопкой отправить.

Вставляем перед закрывающимся тегом </body>.

<div id="modal_form"> <!-- Сaмo oкнo --> 
      <span id="modal_close">X</span> <!-- Кнoпкa зaкрыть --> 
      <!-- Тут любoе сoдержимoе -->
      <div id='content_modal_error'>
      <h2 align='center' style='color:#490301;'>Опишите, пожалуйста, ошибку</h2>
	  
      <form name='from_error' id='from_error'>
      <textarea id='modal_form_text' align='left' maxlength="500">
      </textarea>
      <br><br>
      <input type='submit' value='Отправить' class='button13' name='sub_from_error' style='margin-left:260px;'>
      </form>
      </div>
</div>
<div id="overlay">
</div><!-- Пoдлoжкa -->

При закрытии необходимо с помощью innerHTML в модальное окно вписать то, что было в первоначальном положении (т.е. нашу форму), иначе при повтором запуске у пользователя могут остаться предыдущие сообщения, например, об успешной отправке формы.

Скрипт для модального окна + делаем в фокусе текстовое поле.

...
$('#overlay').fadeIn(400, // снaчaлa плaвнo пoкaзывaем темную пoдлoжку 
   function(){ // пoсле выпoлнения предыдущей aнимaции
			
			
   $('#modal_form') 
	.css('display', 'block') // убирaем у мoдaльнoгo oкнa display: none;	
	.animate({opacity: 1, top: '50%'}, 200); // плaвнo прибaвляем прoзрaчнoсть oднoвременнo сo съезжaнием вниз
				 
    // Делаем текстовое поле в фокусе.
    var modal_form_text = document.getElementById("modal_form_text"); 			
    modal_form_text.focus();
			
});

/* Зaкрытие мoдaльнoгo oкнa, тут делaем тo же сaмoе нo в oбрaтнoм пoрядке */
$('#modal_close, #overlay').click( function(){ // лoвим клик пo крестику или пoдлoжке
  $('#modal_form')
	.animate({opacity: 0, top: '45%'}, 200,  // плaвнo меняем прoзрaчнoсть нa 0 и oднoвременнo двигaем oкнo вверх
	      function(){ // пoсле aнимaции
		    $(this).css('display', 'none'); // делaем ему display: none;
		    $('#overlay').fadeOut(400); // скрывaем пoдлoжку
               }
         );
			
			
  var content_modal = document.getElementById("content_modal_error");
			
  content_modal.innerHTML = "<h2 align='center' style='color:#490301;'>Опишите, пожалуйста, ошибку</h2><form name='from_error' id='from_error'><textarea id='modal_form_text' align='left' maxlength='500'></textarea><br><br><input type='submit' value='Отправить' class='button13' name='sub_from_error' style='margin-left:260px;'></form>";
			
});
...

Стили (Также прикладываю стили для кнопки):

#modal_form {
  width: 700px; 
  height: 300px; /* Рaзмеры дoлжны быть фиксирoвaны */
  border-radius: 5px;
  border: 3px #000 solid;
  background: #fff;
  position: fixed; /* чтoбы oкнo былo в видимoй зoне в любoм месте */
  top: 45%; /* oтступaем сверху 45%, oстaльные 5% пoдвинет скрипт */
  left: 50%; /* пoлoвинa экрaнa слевa */
  margin-top: -150px;
  margin-left: -350px; /* тут вся мaгия центрoвки css, oтступaем влевo и вверх минус пoлoвину ширины и высoты сooтветственнo =) */
  display: none; /* в oбычнoм сoстoянии oкнa не дoлжнo быть */
  opacity: 0; /* пoлнoстью прoзрaчнo для aнимирoвaния */
  z-index: 9999; /* oкнo дoлжнo быть нaибoлее бoльшем слoе */
  padding: 20px 10px;
}
/* Кнoпкa зaкрыть  */
#modal_form #modal_close {
  width: 21px;
  height: 21px;
  position: absolute;
  top: 10px;
  right: 10px;
  cursor: pointer;
  display: block;
}
/* Пoдлoжкa */
#overlay {
  z-index:9998; /* пoдлoжкa дoлжнa быть выше слoев элементoв сaйтa, нo ниже слoя мoдaльнoгo oкнa */
  position:fixed; /* всегдa перекрывaет весь сaйт */
  background-color:#000; /* чернaя */
  opacity:0.8; /* нo немнoгo прoзрaчнa */
  -moz-opacity:0.8; /* фикс прозрачности для старых браузеров */
  filter:alpha(opacity=80);
  width:100%; 
  height:100%; /* рaзмерoм вo весь экрaн */
  top:0; /* сверху и слевa 0, oбязaтельные свoйствa! */
  left:0;
  cursor:pointer;
  display:none; /* в oбычнoм сoстoянии её нет) */
}

#modal_form_text {
  margin-top:20px;
  margin-left:50px;
  width:600px;
  height:150px;
  border:1px solid gray;
  resize: none;
  padding:5px;
	
}
input.button13 {
  display: inline-block;
  width: 15em;
  font-size: 80%;
  color: rgba(255,255,255,.8);
  text-shadow: #543e3c 0 1px 2px;
  text-decoration: none;
  text-align: center;
  line-height: 1.1;
  white-space: pre-line;
  padding: .7em 0;
  border: 1px solid;
  border-color: #72140c #72140c #72140c #72140c;
  border-radius: 6px;
  outline: none;
  background: #60a3d8 linear-gradient(#3c0b06, #580f08 50%, #72140c);
  box-shadow: inset rgba(255,255,255,.5) 1px 1px;
  height:40px;
}
input.button13:first-line{
  font-size: 140%;
  font-weight: 700;
}
input.button13:hover {
  color: rgb(255,255,255);
  background-image: linear-gradient(#3c0b06, #580f08 50%, #72140c);
}
input.button13:active {
  color: rgb(255,255,255);
  border-color: #2970a9;
  background-image: linear-gradient(#a40d00, #a40d00);
  box-shadow: none;
}

Отлично! Будем использовать для получении информации о выделенном тексте пользователя функцию:

function getSelectedText() 
{
    var text = "";
    if (window.getSelection) {
        text = window.getSelection().toString();
    }else if (document.getSelection) {
        text = document.getSelection().toString();
    }else if (document.selection) {
        text = document.selection.createRange().text.toString();
    }
    return text;
}

Таким образом, помещаем в текстовое поле то, что именно выделил пользователь:

...
 if(e.which == 17) isCtrl= true;
 if(e.which == 81 && isCtrl ==  true) {
		
  TextSelect = getSelectedText();		
  $( '#modal_form_text').val(TextSelect);
 
   ...

}
...

Пишем обработчик события отправки формы. С помощью ajax отправляем три параметра: Выделенный текст, исправленный вариант с текстового поля модального окна и URL страницы (с которой отослали сообщение).

Как только отправили, если сервер немного тормозит — ставим гиф-картинку с крутящимся колёсиком.

$('#from_error').submit( function(){
			
    var url = window.location.pathname;
    var modal_form_text = $('#modal_form_text').val();
			
    $.post('/ajax/detect_error.php', {TextSelect: TextSelect, url: url, modal_form_text: modal_form_text}, detect_func);			
    var content_modal = document.getElementById("content_modal_error");			
    content_modal.innerHTML = "<br><br><br><center><img src='/images/ajax_loader_red_512.gif' width='150' height='150'></center>";
    
    // Функция, которая запускается при ответе сервера при ajax запросе.
    function detect_func(data) {
        var data_str = "";
	if(data == "") {
         data_str = "<center><br><br><br><Br><span style='color:red;'>Идут технические работы - попробуйте позже</span>  </center>";
        }
	else {	
	 data_str = data.toString();		
	}	
	var content_modal = document.getElementById("content_modal_error");
	content_modal.innerHTML = data_str;		
   } 
			
  return false;
			
});

Ловим с помощью php скрипта ajax запрос:

if(isset($_POST['TextSelect']) && isset($_POST['url']) && isset($_POST['modal_form_text'])) {
		
  $_POST['TextSelect'] = trim($_POST['TextSelect']);
  $_POST['url'] = trim($_POST['url']);
  $_POST['modal_form_text'] = trim($_POST['modal_form_text']);
		
  $_POST['TextSelect'] = strip_tags($_POST['TextSelect']);
  $_POST['url'] =strip_tags($_POST['url']);
  $_POST['modal_form_text'] = strip_tags($_POST['modal_form_text']);
		
  $_POST['TextSelect'] = substr($_POST['TextSelect'], 0, 500);
  $_POST['url'] = substr($_POST['url'], 0, 255);
  $_POST['modal_form_text']  = substr($_POST['modal_form_text'], 0, 500);
		
		
  $from = "Content-type: text/plain; charset=utf-8"."\r\n";
  $from = $from.'From: xxx@gmail.com';
		
  $subject = "Сайт yyy Присалил ошибку" ;
  $message = "\n\n URL:".$_POST['url'] ;
  $message = $message."\n\n Выделенный текст:".$_POST['TextSelect'];
  $message = $message."\n\n Описание ошибки:".$_POST['modal_form_text'];
		
  if (!mail( "zzz@yandex.ru", $subject, $message,  $from)) {
          
      echo "Письмо не отправлено. Ошибка 1. Сообщите Администратору.";
      exit;
  }
  else {
				
      echo "<br><Br><br><Br><br><h2 align='center' style='color:#742c2c;'>Спасибо за ваше замечание! В ближайшее время я его рассмотрю.</h2>";
				
  }
		
}

Именно он и отсылает данные на E-mail: zzz@yandex.ru

Вы должны сами проверить БЕЗОПАСНОСТЬ данного скрипта. В статье показаны лишь идеи.

Можно вместо отправки email добавлять сообщения в БАЗУ ДАННЫХ.

Файлы для данного скрипта:

Скачать модуль «Нашли ошибку — нажимай Ctrl + Q»

Счастливого программирования!

Ошибки — это хорошо. Автор материала, перевод которого мы сегодня публикуем, говорит, что уверен в том, что эта идея известна всем. На первый взгляд ошибки кажутся чем-то страшным. Им могут сопутствовать какие-то потери. Ошибка, сделанная на публике, вредит авторитету того, кто её совершил. Но, совершая ошибки, мы на них учимся, а значит, попадая в следующий раз в ситуацию, в которой раньше вели себя неправильно, делаем всё как нужно.

Выше мы говорили об ошибках, которые люди совершают в обычной жизни. Ошибки в программировании — это нечто иное. Сообщения об ошибках помогают нам улучшать код, они позволяют сообщать пользователям наших проектов о том, что что-то пошло не так, и, возможно, рассказывают пользователям о том, как нужно вести себя для того, чтобы ошибок больше не возникало.

Этот материал, посвящённый обработке ошибок в JavaScript, разбит на три части. Сначала мы сделаем общий обзор системы обработки ошибок в JavaScript и поговорим об объектах ошибок. После этого мы поищем ответ на вопрос о том, что делать с ошибками, возникающими в серверном коде (в частности, при использовании связки Node.js + Express.js). Далее — обсудим обработку ошибок в React.js. Фреймворки, которые будут здесь рассматриваться, выбраны по причине их огромной популярности. Однако рассматриваемые здесь принципы работы с ошибками универсальны, поэтому вы, даже если не пользуетесь Express и React, без труда сможете применить то, что узнали, к тем инструментам, с которыми работаете.

Код демонстрационного проекта, используемого в данном материале, можно найти в этом репозитории.

1. Ошибки в JavaScript и универсальные способы работы с ними

Если в вашем коде что-то пошло не так, вы можете воспользоваться следующей конструкцией.

throw new Error('something went wrong')

В ходе выполнения этой команды будет создан экземпляр объекта Error и будет сгенерировано (или, как говорят, «выброшено») исключение с этим объектом. Инструкция throw может генерировать исключения, содержащие произвольные выражения. При этом выполнение скрипта остановится в том случае, если не были предприняты меры по обработке ошибки.

Начинающие JS-программисты обычно не используют инструкцию throw. Они, как правило, сталкиваются с исключениями, выдаваемыми либо средой выполнения языка, либо сторонними библиотеками. Когда это происходит — в консоль попадает нечто вроде ReferenceError: fs is not defined и выполнение программы останавливается.

▍Объект Error

У экземпляров объекта Error есть несколько свойств, которыми мы можем пользоваться. Первое интересующее нас свойство — message. Именно сюда попадает та строка, которую можно передать конструктору ошибки в качестве аргумента. Например, ниже показано создание экземпляра объекта Error и вывод в консоль переданной конструктором строки через обращение к его свойству message.

const myError = new Error('please improve your code')
console.log(myError.message) // please improve your code

Второе свойство объекта, очень важное, представляет собой трассировку стека ошибки. Это — свойство stack. Обратившись к нему можно просмотреть стек вызовов (историю ошибки), который показывает последовательность операций, приведшую к неправильной работе программы. В частности, это позволяет понять — в каком именно файле содержится сбойный код, и увидеть, какая последовательность вызовов функций привела к ошибке. Вот пример того, что можно увидеть, обратившись к свойству stack.

Error: please improve your code
 at Object.<anonymous> (/Users/gisderdube/Documents/_projects/hacking.nosync/error-handling/src/general.js:1:79)
 at Module._compile (internal/modules/cjs/loader.js:689:30)
 at Object.Module._extensions..js (internal/modules/cjs/loader.js:700:10)
 at Module.load (internal/modules/cjs/loader.js:599:32)
 at tryModuleLoad (internal/modules/cjs/loader.js:538:12)
 at Function.Module._load (internal/modules/cjs/loader.js:530:3)
 at Function.Module.runMain (internal/modules/cjs/loader.js:742:12)
 at startup (internal/bootstrap/node.js:266:19)
 at bootstrapNodeJSCore (internal/bootstrap/node.js:596:3)

Здесь, в верхней части, находится сообщение об ошибке, затем следует указание на тот участок кода, выполнение которого вызвало ошибку, потом описывается то место, откуда был вызван этот сбойный участок. Это продолжается до самого «дальнего» по отношению к ошибке фрагмента кода.

▍Генерирование и обработка ошибок

Создание экземпляра объекта Error, то есть, выполнение команды вида new Error(), ни к каким особым последствиям не приводит. Интересные вещи начинают происходить после применения оператора throw, который генерирует ошибку. Как уже было сказано, если такую ошибку не обработать, выполнение скрипта остановится. При этом нет никакой разницы — был ли оператор throw использован самим программистом, произошла ли ошибка в некоей библиотеке или в среде выполнения языка (в браузере или в Node.js). Поговорим о различных сценариях обработки ошибок.

▍Конструкция try…catch

Блок try...catch представляет собой самый простой способ обработки ошибок, о котором часто забывают. В наши дни, правда, он используется гораздо интенсивнее чем раньше, благодаря тому, что его можно применять для обработки ошибок в конструкциях async/await.

Этот блок можно использовать для обработки любых ошибок, происходящих в синхронном коде. Рассмотрим пример.

const a = 5

try {
    console.log(b) // переменная b не объявлена - возникает ошибка
} catch (err) {
    console.error(err) // в консоль попадает сообщение об ошибке и стек ошибки
}

console.log(a) // выполнение скрипта не останавливается, данная команда выполняется

Если бы в этом примере мы не заключили бы сбойную команду console.log(b) в блок try...catch, то выполнение скрипта было бы остановлено.

▍Блок finally

Иногда случается так, что некий код нужно выполнить независимо от того, произошла ошибка или нет. Для этого можно, в конструкции try...catch, использовать третий, необязательный, блок — finally. Часто его использование эквивалентно некоему коду, который идёт сразу после try...catch, но в некоторых ситуациях он может пригодиться. Вот пример его использования.

const a = 5

try {
    console.log(b) // переменная b не объявлена - возникает ошибка
} catch (err) {
    console.error(err) // в консоль попадает сообщение об ошибке и стек ошибки
} finally {
    console.log(a) // этот код будет выполнен в любом случае
}

▍Асинхронные механизмы — коллбэки

Программируя на JavaScript всегда стоит обращать внимание на участки кода, выполняющиеся асинхронно. Если у вас имеется асинхронная функция и в ней возникает ошибка, скрипт продолжит выполняться. Когда асинхронные механизмы в JS реализуются с использованием коллбэков (кстати, делать так не рекомендуется), соответствующий коллбэк (функция обратного вызова) обычно получает два параметра. Это нечто вроде параметра err, который может содержать ошибку, и result — с результатами выполнения асинхронной операции. Выглядит это примерно так:

myAsyncFunc(someInput, (err, result) => {
    if(err) return console.error(err) // порядок работы с объектом ошибки мы рассмотрим позже
    console.log(result)
})

Если в коллбэк попадает ошибка, она видна там в виде параметра err. В противном случае в этот параметр попадёт значение undefined или null. Если оказалось, что в err что-то есть, важно отреагировать на это, либо так как в нашем примере, воспользовавшись командой return, либо воспользовавшись конструкцией if...else и поместив в блок else команды для работы с результатом выполнения асинхронной операции. Речь идёт о том, чтобы, в том случае, если произошла ошибка, исключить возможность работы с результатом, параметром result, который в таком случае может иметь значение undefined. Работа с таким значением, если предполагается, например, что оно содержит объект, сама может вызвать ошибку. Скажем, это произойдёт при попытке использовать конструкцию result.data или подобную ей.

▍Асинхронные механизмы — промисы

Для выполнения асинхронных операций в JavaScript лучше использовать не коллбэки а промисы. Тут, в дополнение к улучшенной читабельности кода, имеются и более совершенные механизмы обработки ошибок. А именно, возиться с объектом ошибки, который может попасть в функцию обратного вызова, при использовании промисов не нужно. Здесь для этой цели предусмотрен специальный блок catch. Он перехватывает все ошибки, произошедшие в промисах, которые находятся до него, или все ошибки, которые произошли в коде после предыдущего блока catch. Обратите внимание на то, что если в промисе произошла ошибка, для обработки которой нет блока catch, это не остановит выполнение скрипта, но сообщение об ошибке будет не особенно удобочитаемым.

(node:7741) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: something went wrong
(node:7741) DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code. */

В результате можно порекомендовать всегда, при работе с промисами, использовать блок catch. Взглянем на пример.

Promise.resolve(1)
    .then(res => {
        console.log(res) // 1

        throw new Error('something went wrong')

        return Promise.resolve(2)
    })
    .then(res => {
        console.log(res) // этот блок выполнен не будет
    })
    .catch(err => {
        console.error(err) // о том, что делать с этой ошибкой, поговорим позже
        return Promise.resolve(3)
    })
    .then(res => {
        console.log(res) // 3
    })
    .catch(err => {
        // этот блок тут на тот случай, если в предыдущем блоке возникнет какая-нибудь ошибка
        console.error(err)
    })

▍Асинхронные механизмы и try…catch

После того, как в JavaScript появилась конструкция async/await, мы вернулись к классическому способу обработки ошибок — к try...catch...finally. Обрабатывать ошибки при таком подходе оказывается очень легко и удобно. Рассмотрим пример.

;(async function() {
    try {
        await someFuncThatThrowsAnError()
    } catch (err) {
        console.error(err) // об этом поговорим позже
    }

    console.log('Easy!') // будет выполнено
})()

При таком подходе ошибки в асинхронном коде обрабатываются так же, как в синхронном. В результате теперь, при необходимости, в одном блоке catch можно обрабатывать более широкий диапазон ошибок.

2. Генерирование и обработка ошибок в серверном коде

Теперь, когда у нас есть инструменты для работы с ошибками, посмотрим на то, что мы можем с ними делать в реальных ситуациях. Генерирование и правильная обработка ошибок — это важнейший аспект серверного программирования. Существуют разные подходы к работе с ошибками. Здесь будет продемонстрирован подход с использованием собственного конструктора для экземпляров объекта Error и кодов ошибок, которые удобно передавать во фронтенд или любым механизмам, использующим серверные API. Как структурирован бэкенд конкретного проекта — особого значения не имеет, так как при любом подходе можно использовать одни и те же идеи, касающиеся работы с ошибками.

В качестве серверного фреймворка, отвечающего за маршрутизацию, мы будем использовать Express.js. Подумаем о том, какая структура нам нужна для организации эффективной системы обработки ошибок. Итак, вот что нам нужно:

  1. Универсальная обработка ошибок — некий базовый механизм, подходящий для обработки любых ошибок, в ходе работы которого просто выдаётся сообщение наподобие Something went wrong, please try again or contact us, предлагающее пользователю попробовать выполнить операцию, давшую сбой, ещё раз или связаться с владельцем сервера. Эта система не отличается особой интеллектуальностью, но она, по крайней мере, способна сообщить пользователю о том, что что-то пошло не так. Подобное сообщение гораздо лучше, чем «бесконечная загрузка» или нечто подобное.
  2. Обработка конкретных ошибок — механизм, позволяющий сообщить пользователю подробные сведения о причинах неправильного поведения системы и дать ему конкретные советы по борьбе с неполадкой. Например, это может касаться отсутствия неких важных данных в запросе, который пользователь отправляет на сервер, или в том, что в базе данных уже существует некая запись, которую он пытается добавить ещё раз, и так далее.

▍Разработка собственного конструктора объектов ошибок

Здесь мы воспользуемся стандартным классом Error и расширим его. Пользоваться механизмами наследования в JavaScript — дело рискованное, но в данном случае эти механизмы оказываются весьма полезными. Зачем нам наследование? Дело в том, что нам, для того, чтобы код удобно было бы отлаживать, нужны сведения о трассировке стека ошибки. Расширяя стандартный класс Error, мы, без дополнительных усилий, получаем возможности по трассировке стека. Мы добавляем в наш собственный объект ошибки два свойства. Первое — это свойство code, доступ к которому можно будет получить с помощью конструкции вида err.code. Второе — свойство status. В него будет записываться код состояния HTTP, который планируется передавать клиентской части приложения.

Вот как выглядит класс CustomError, код которого оформлен в виде модуля.

class CustomError extends Error {
    constructor(code = 'GENERIC', status = 500, ...params) {
        super(...params)

        if (Error.captureStackTrace) {
            Error.captureStackTrace(this, CustomError)
        }

        this.code = code
        this.status = status
    }
}

module.exports = CustomError

▍Маршрутизация

Теперь, когда наш объект ошибки готов к использованию, нужно настроить структуру маршрутов. Как было сказано выше, нам требуется реализовать унифицированный подход к обработке ошибок, позволяющий одинаково обрабатывать ошибки для всех маршрутов. По умолчанию фреймворк Express.js не вполне поддерживает такую схему работы. Дело в том, что все его маршруты инкапсулированы.

Для того чтобы справиться с этой проблемой, мы можем реализовать собственный обработчик маршрутов и определять логику маршрутов в виде обычных функций. Благодаря такому подходу, если функция маршрута (или любая другая функция) выбрасывает ошибку, она попадёт в обработчик маршрутов, который затем может передать её клиентской части приложения. При возникновении ошибки на сервере мы планируем передавать её во фронтенд в следующем формате, полагая, что для этого будет применяться JSON-API:

{
    error: 'SOME_ERROR_CODE',
    description: 'Something bad happened. Please try again or contact support.'
}

Если на данном этапе происходящие кажется вам непонятным — не беспокойтесь — просто продолжайте читать, пробуйте работать с тем, о чём идёт речь, и постепенно вы во всём разберётесь. На самом деле, если говорить о компьютерном обучении, здесь применяется подход «сверху-вниз», когда сначала обсуждаются общие идеи, а потом осуществляется переход к частностям.

Вот как выглядит код обработчика маршрутов.

const express = require('express')
const router = express.Router()
const CustomError = require('../CustomError')

router.use(async (req, res) => {
    try {
        const route = require(`.${req.path}`)[req.method]

        try {
            const result = route(req) // Передаём запрос функции route
            res.send(result) // Передаём клиенту то, что получено от функции route
        } catch (err) {
            /*
            Сюда мы попадаем в том случае, если в функции route произойдёт ошибка
            */
            if (err instanceof CustomError) {
                /* 
                Если ошибка уже обработана - трансформируем её в 
                возвращаемый объект
                */

                return res.status(err.status).send({
                    error: err.code,
                    description: err.message,
                })
            } else {
                console.error(err) // Для отладочных целей

                // Общая ошибка - вернём универсальный объект ошибки
                return res.status(500).send({
                    error: 'GENERIC',
                    description: 'Something went wrong. Please try again or contact support.',
                })
            }
        }
    } catch (err) {
        /* 
         Сюда мы попадём, если запрос окажется неудачным, то есть,
         либо не будет найдено файла, соответствующего пути, переданному
         в запросе, либо не будет экспортированной функции с заданным
         методом запроса
        */
        res.status(404).send({
            error: 'NOT_FOUND',
            description: 'The resource you tried to access does not exist.',
        })
    }
})

module.exports = router

Полагаем, комментарии в коде достаточно хорошо его поясняют. Надеемся, читать их удобнее, чем объяснения подобного кода, данные после него.

Теперь взглянем на файл маршрутов.

const CustomError = require('../CustomError')

const GET = req => {
    // пример успешного выполнения запроса
    return { name: 'Rio de Janeiro' }
}

const POST = req => {
    // пример ошибки общего характера
    throw new Error('Some unexpected error, may also be thrown by a library or the runtime.')
}

const DELETE = req => {
    // пример ошибки, обрабатываемой особым образом
    throw new CustomError('CITY_NOT_FOUND', 404, 'The city you are trying to delete could not be found.')
}

const PATCH = req => {
    // пример перехвата ошибок и использования CustomError
    try {
        // тут случилось что-то нехорошее
        throw new Error('Some internal error')
    } catch (err) {
        console.error(err) // принимаем решение о том, что нам тут делать

        throw new CustomError(
            'CITY_NOT_EDITABLE',
            400,
            'The city you are trying to edit is not editable.'
        )
    }
}

module.exports = {
    GET,
    POST,
    DELETE,
    PATCH,
}

В этих примерах с самими запросами ничего не делается. Тут просто рассматриваются разные сценарии возникновения ошибок. Итак, например, запрос GET /city попадёт в функцию const GET = req =>..., запрос POST /city попадёт в функцию const POST = req =>... и так далее. Эта схема работает и при использовании параметров запросов. Например — для запроса вида GET /city?startsWith=R. В целом, здесь продемонстрировано, что при обработке ошибок, во фронтенд может попасть либо общая ошибка, содержащая лишь предложение попробовать снова или связаться с владельцем сервера, либо ошибка, сформированная с использованием конструктора CustomError, которая содержит подробные сведения о проблеме.
Данные общей ошибки придут в клиентскую часть приложения в таком виде:

{
    error: 'GENERIC',
    description: 'Something went wrong. Please try again or contact support.'
}

Конструктор CustomError используется так:

throw new CustomError('MY_CODE', 400, 'Error description')

Это даёт следующий JSON-код, передаваемый во фронтенд:

{
    error: 'MY_CODE',
    description: 'Error description'
}

Теперь, когда мы основательно потрудились над серверной частью приложения, в клиентскую часть больше не попадают бесполезные логи ошибок. Вместо этого клиент получает полезные сведения о том, что пошло не так.

Не забудьте о том, что здесь лежит репозиторий с рассматриваемым здесь кодом. Можете его загрузить, поэкспериментировать с ним, и, если надо, адаптировать под нужды вашего проекта.

3. Работа с ошибками на клиенте

Теперь пришла пора описать третью часть нашей системы обработки ошибок, касающуюся фронтенда. Тут нужно будет, во-первых, обрабатывать ошибки, возникающие в клиентской части приложения, а во-вторых, понадобится оповещать пользователя об ошибках, возникающих на сервере. Разберёмся сначала с показом сведений о серверных ошибках. Как уже было сказано, в этом примере будет использована библиотека React.

▍Сохранение сведений об ошибках в состоянии приложения

Как и любые другие данные, ошибки и сообщения об ошибках могут меняться, поэтому их имеет смысл помещать в состояние компонентов. При монтировании компонента данные об ошибке сбрасываются, поэтому, когда пользователь впервые видит страницу, там сообщений об ошибках не будет.

Следующее, с чем надо разобраться, заключается в том, что ошибки одного типа нужно показывать в одном стиле. По аналогии с сервером, здесь можно выделить 3 типа ошибок.

  1. Глобальные ошибки — в эту категорию попадают сообщения об ошибках общего характера, приходящие с сервера, или ошибки, которые, например, возникают в том случае, если пользователь не вошёл в систему и в других подобных ситуациях.
  2. Специфические ошибки, выдаваемые серверной частью приложения — сюда относятся ошибки, сведения о которых приходят с сервера. Например, подобная ошибка возникает, если пользователь попытался войти в систему и отправил на сервер имя и пароль, а сервер сообщил ему о том, что пароль неправильный. Подобные вещи в клиентской части приложения не проверяются, поэтому сообщения о таких ошибках должны приходить с сервера.
  3. Специфические ошибки, выдаваемые клиентской частью приложения. Пример такой ошибки — сообщение о некорректном адресе электронной почты, введённом в соответствующее поле.

Ошибки второго и третьего типов очень похожи, работать с ними можно, используя хранилище состояния компонентов одного уровня. Их главное различие заключается в том, что они исходят из разных источников. Ниже, анализируя код, мы посмотрим на работу с ними.

Здесь будет использоваться встроенная в React система управления состоянием приложения, но, при необходимости, вы можете воспользоваться и специализированными решениями для управления состоянием — такими, как MobX или Redux.

▍Глобальные ошибки

Обычно сообщения о таких ошибках сохраняются в компоненте наиболее высокого уровня, имеющем состояние. Они выводятся в статическом элементе пользовательского интерфейса. Это может быть красное поле в верхней части экрана, модальное окно или что угодно другое. Реализация зависит от конкретного проекта. Вот как выглядит сообщение о такой ошибке.

Сообщение о глобальной ошибке

Теперь взглянем на код, который хранится в файле Application.js.

import React, { Component } from 'react'

import GlobalError from './GlobalError'

class Application extends Component {
    constructor(props) {
        super(props)

        this.state = {
            error: '',
        }

        this._resetError = this._resetError.bind(this)
        this._setError = this._setError.bind(this)
    }

    render() {
        return (
            <div className="container">
                <GlobalError error={this.state.error} resetError={this._resetError} />
                <h1>Handling Errors</h1>
            </div>
        )
    }

    _resetError() {
        this.setState({ error: '' })
    }

    _setError(newError) {
        this.setState({ error: newError })
    }
}

export default Application

Как видно, в состоянии, в Application.js, имеется место для хранения данных ошибки. Кроме того, тут предусмотрены методы для сброса этих данных и для их изменения.

Ошибка и метод для сброса ошибки передаётся компоненту GlobalError, который отвечает за вывод сообщения об ошибке на экран и за сброс ошибки после нажатия на значок x в поле, где выводится сообщение. Вот код компонента GlobalError (файл GlobalError.js).

import React, { Component } from 'react'

class GlobalError extends Component {
    render() {
        if (!this.props.error) return null

        return (
            <div
                style={{
                    position: 'fixed',
                    top: 0,
                    left: '50%',
                    transform: 'translateX(-50%)',
                    padding: 10,
                    backgroundColor: '#ffcccc',
                    boxShadow: '0 3px 25px -10px rgba(0,0,0,0.5)',
                    display: 'flex',
                    alignItems: 'center',
                }}
            >
                {this.props.error}
                 
                <i
                    className="material-icons"
                    style={{ cursor: 'pointer' }}
                    onClick={this.props.resetError}
                >
                    close
                </font></i>
            </div>
        )
    }
}

export default GlobalError

Обратите внимание на строку if (!this.props.error) return null. Она указывает на то, что при отсутствии ошибки компонент ничего не выводит. Это предотвращает постоянный показ красного прямоугольника на странице. Конечно, вы, при желании, можете поменять внешний вид и поведение этого компонента. Например, вместо того, чтобы сбрасывать ошибку по нажатию на x, можно задать тайм-аут в пару секунд, по истечении которого состояние ошибки сбрасывается автоматически.

Теперь, когда всё готово для работы с глобальными ошибками, для задания глобальной ошибки достаточно воспользоваться _setError из Application.js. Например, это можно сделать в том случае, если сервер, после обращения к нему, вернул сообщение об общей ошибке (error: 'GENERIC'). Рассмотрим пример (файл GenericErrorReq.js).

import React, { Component } from 'react'
import axios from 'axios'

class GenericErrorReq extends Component {
    constructor(props) {
        super(props)

        this._callBackend = this._callBackend.bind(this)
    }

    render() {
        return (
            <div>
                <button onClick={this._callBackend}>Click me to call the backend</button>
            </div>
        )
    }

    _callBackend() {
        axios
            .post('/api/city')
            .then(result => {
                // сделать что-нибудь с результатом в том случае, если запрос оказался успешным
            })
            .catch(err => {
                if (err.response.data.error === 'GENERIC') {
                    this.props.setError(err.response.data.description)
                }
            })
    }
}

export default GenericErrorReq

На самом деле, на этом наш разговор об обработке ошибок можно было бы и закончить. Даже если в проекте нужно оповещать пользователя о специфических ошибках, никто не мешает просто поменять глобальное состояние, хранящее ошибку и вывести соответствующее сообщение поверх страницы. Однако тут мы не остановимся и поговорим о специфических ошибках. Во-первых, это руководство по обработке ошибок иначе было бы неполным, а во-вторых, с точки зрения UX-специалистов, неправильно будет показывать сообщения обо всех ошибках так, будто все они — глобальные.

▍Обработка специфических ошибок, возникающих при выполнении запросов

Вот пример специфического сообщения об ошибке, выводимого в том случае, если пользователь пытается удалить из базы данных город, которого там нет.

Сообщение о специфической ошибке

Тут используется тот же принцип, который мы применяли при работе с глобальными ошибками. Только сведения о таких ошибках хранятся в локальном состоянии соответствующих компонентов. Работа с ними очень похожа на работу с глобальными ошибками. Вот код файла SpecificErrorReq.js.

import React, { Component } from 'react'
import axios from 'axios'

import InlineError from './InlineError'

class SpecificErrorRequest extends Component {
    constructor(props) {
        super(props)

        this.state = {
            error: '',
        }

        this._callBackend = this._callBackend.bind(this)
    }

    render() {
        return (
            <div>
                <button onClick={this._callBackend}>Delete your city</button>
                <InlineError error={this.state.error} />
            </div>
        )
    }

    _callBackend() {
        this.setState({
            error: '',
        })

        axios
            .delete('/api/city')
            .then(result => {
                // сделать что-нибудь с результатом в том случае, если запрос оказался успешным
            })
            .catch(err => {
                if (err.response.data.error === 'GENERIC') {
                    this.props.setError(err.response.data.description)
                } else {
                    this.setState({
                        error: err.response.data.description,
                    })
                }
            })
    }
}

export default SpecificErrorRequest

Тут стоит отметить, что для сброса специфических ошибок недостаточно, например, просто нажать на некую кнопку x. То, что пользователь прочёл сообщение об ошибке и закрыл его, не помогает такую ошибку исправить. Исправить её можно, правильно сформировав запрос к серверу, например — введя в ситуации, показанной на предыдущем рисунке, имя города, который есть в базе. В результате очищать сообщение об ошибке имеет смысл, например, после выполнения нового запроса. Сбросить ошибку можно и в том случае, если пользователь внёс изменения в то, что будет использоваться при формировании нового запроса, то есть — при изменении содержимого поля ввода.

▍Ошибки, возникающие в клиентской части приложения

Как уже было сказано, для хранения данных о таких ошибках можно использовать состояние тех же компонентов, которое используется для хранения данных по специфическим ошибкам, поступающим с сервера. Предположим, мы позволяем пользователю отправить на сервер запрос на удаление города из базы только в том случае, если в соответствующем поле ввода есть какой-то текст. Отсутствие или наличие текста в поле можно проверить средствами клиентской части приложения.

В поле ничего нет, мы сообщаем об этом пользователю

Вот код файла SpecificErrorFrontend.js, реализующий вышеописанный функционал.

import React, { Component } from 'react'
import axios from 'axios'

import InlineError from './InlineError'

class SpecificErrorRequest extends Component {
    constructor(props) {
        super(props)

        this.state = {
            error: '',
            city: '',
        }

        this._callBackend = this._callBackend.bind(this)
        this._changeCity = this._changeCity.bind(this)
    }

    render() {
        return (
            <div>
                <input
                    type="text"
                    value={this.state.city}
                    style={{ marginRight: 15 }}
                    onChange={this._changeCity}
                />
                <button onClick={this._callBackend}>Delete your city</button>
                <InlineError error={this.state.error} />
            </div>
        )
    }

    _changeCity(e) {
        this.setState({
            error: '',
            city: e.target.value,
        })
    }

    _validate() {
        if (!this.state.city.length) throw new Error('Please provide a city name.')
    }

    _callBackend() {
        this.setState({
            error: '',
        })

        try {
            this._validate()
        } catch (err) {
            return this.setState({ error: err.message })
        }

        axios
            .delete('/api/city')
            .then(result => {
                // сделать что-нибудь с результатом в том случае, если запрос оказался успешным
            })
            .catch(err => {
                if (err.response.data.error === 'GENERIC') {
                    this.props.setError(err.response.data.description)
                } else {
                    this.setState({
                        error: err.response.data.description,
                    })
                }
            })
    }
}

export default SpecificErrorRequest

▍Интернационализация сообщений об ошибках с использованием кодов ошибок

Возможно, сейчас вы задаётесь вопросом о том, зачем нам нужны коды ошибок (наподобие GENERIC), если мы показываем пользователю только сообщения об ошибках, полученных с сервера. Дело в том, что, по мере роста и развития приложения, оно, вполне возможно, выйдет на мировой рынок, а это означает, что настанет время, когда создателям приложения нужно будет задуматься о поддержке им нескольких языков. Коды ошибок позволяют отличать их друг от друга и выводить сообщения о них на языке пользователя сайта.

Итоги

Надеемся, теперь у вас сформировалось понимание того, как можно работать с ошибками в веб-приложениях. Нечто вроде console.error(err) следует использовать только в отладочных целях, в продакшн подобные вещи, забытые программистом, проникать не должны. Упрощает решение задачи логирования использование какой-нибудь подходящей библиотеки наподобие loglevel.

Уважаемые читатели! Как вы обрабатываете ошибки в своих проектах?

Иногда ваше приложение не запускается должным образом, что приводит к ошибкам. Есть ряд причин, которые могут вызвать ошибки, например:

  • Веб-серверу может не хватить места на диске;
  • Пользователь мог ввести недопустимое значение в поле формы;
  • Файл или запись базы данных, к которой вы пытались получить доступ, возможно, не существует;
  • Приложение может не иметь разрешения на запись в файл на диске;
  • Служба, к которой приложение должно получить доступ, может быть временно недоступна.

Эти типы ошибок известны как ошибки времени выполнения, потому что они возникают во время выполнения скрипта. Они отличаются от синтаксических ошибок, которые необходимо исправлять перед запуском скриптов.

Профессиональное приложение должно иметь возможность изящно обрабатывать такие ошибки времени выполнения. Обычно это означает более четкое и точное информирование пользователя о проблеме.

Понимание уровней ошибок

Обычно, когда возникает проблема, препятствующая правильной работе скрипта, механизм PHP выдает ошибку. Каждая ошибка представлена целым числом и соответствующей константой. В следующей таблице перечислены некоторые из распространенных уровней ошибок:

Название Значение Описание
E_ERROR 1 Неустранимая ошибка времени выполнения от которой невозможно избавиться. Выполнение скрипта немедленно прекращается.
E_WARNING 2 Предупреждение во время выполнения. Она несущественна, и большинство ошибок попадают в эту категорию. Выполнение скрипта не останавливается.
E_NOTICE 8 Уведомление во время выполнения. Указывает, что скрипт обнаружил что-то, что могло быть ошибкой, хотя такая ситуация также может возникнуть при обычном запуске скрипта.
E_USER_ERROR 256 Сообщение о фатальной пользовательской ошибке. Она похожа на E_ERROR, за исключением того, что она генерируется PHP-скриптом с использованием функции trigger_error().
E_USER_WARNING 512 Предупреждающее сообщение, созданное пользователем без фатального исхода. Она похожа на E_WARNING, за исключением того, что она генерируется PHP-скриптом с использованием функции trigger_error().
E_USER_NOTICE 1024 Сообщение с уведомлением, созданное пользователем. Она похожа на E_NOTICE за исключением того, что она генерируется PHP-скриптом с использованием функции trigger_error().
E_STRICT 2048 Не совсем ошибка, но срабатывает всякий раз, когда PHP встречает код, который может привести к проблемам или несовместимости пересылки.
E_ALL 8191 Все ошибки и предупреждения, кроме E_STRICT до PHP 5.4.0.

Дополнительные сведения об уровнях ошибок см. в справочнике по уровням ошибок PHP.

Механизм PHP вызывает ошибку всякий раз, когда он сталкивается с проблемой в вашем скрипте, но вы также можете инициировать ошибки самостоятельно, чтобы генерировать более удобные сообщения об ошибках. Таким образом вы можете сделать свое приложение более сложным. В следующем разделе описаны некоторые из распространенных методов, используемых для обработки ошибок в PHP:

Базовая обработка ошибок с помощью функции die()

Рассмотрим следующий пример, в котором просто попытаемся открыть текстовый файл только для чтения.

<?php
// Пробуем открыть несуществующий файл
$file = fopen("sample.txt", "r"); // Выводит: Warning: fopen(sample.txt) [function.fopen]: failed to open stream: No such file or directory in C:wampwwwprojecttest.php on line 2
?>

Если мы выполним несколько простых шагов, мы сможем предотвратить получение пользователями такого сообщения об ошибке.

<?php
if(file_exists("sample.txt")){
    $file = fopen("sample.txt", "r");
} else{
    die("Error: The file you are trying to access doesn't exist.");
}
?>

Как вы можете видеть, реализовав простую проверку, существует ли файл перед попыткой доступа к нему, мы можем сгенерировать сообщение об ошибке, которое будет более понятным для пользователя.

Используемая выше функция die() просто отображает пользовательское сообщение об ошибке и завершает текущий скрипт, если файл sample.txt не найден.

Создание собственного обработчика ошибок

Вы можете создать свою собственную функцию обработчика ошибок, чтобы справляться с ошибкой времени выполнения, генерируемой механизмом PHP. Пользовательский обработчик ошибок обеспечивает большую гибкость и лучший контроль над ошибками; он может проверять ошибку и решать, что с ней делать — отображать сообщение пользователю, регистрировать ошибку в файле или базе данных или отправлять по электронной почте, попытаться исправить проблему и продолжить, выйти из выполнения скрипта или вообще игнорировать ошибку.

Функция пользовательского обработчика ошибок должна иметь возможность обрабатывать как минимум два параметра (errno и errstr), однако она может дополнительно принимать три дополнительных параметра (errfile, errline и errcontext), как описано ниже:

Параметр Описание
Обязательно — следующие параметры обязательны
errno. Задает уровень ошибки в виде целого числа. Это соответствует соответствующей константе уровня ошибки (E_ERROR, E_WARNING и т. д.).
errstr. Задает сообщение об ошибке в виде строки.
Опционально — следующие параметры являются необязательными
errfile. Задает имя файла скрипта, в котором произошла ошибка.
errline. Задает номер строки, в которой произошла ошибка.
errcontext. Задает массив, содержащий все переменные и их значения, которые существовали на момент возникновения ошибки. Полезно для отладки.

Вот пример простой пользовательской функции обработки ошибок. Этот обработчик customError() запускается всякий раз, когда возникает ошибка, какой бы тривиальной она ни была. Затем он выводит сведения об ошибке в браузер и останавливает выполнение скрипта.

<?php
// Функция обработчика ошибок
function customError($errno, $errstr){
    echo "<b>Error:</b> [$errno] $errstr";
}
?>

Вам нужно указать PHP, чтобы он использовал вашу пользовательскую функцию обработчика ошибок — просто вызовите встроенную функцию set_error_handler(), передав имя функции.

<?php
// Функция обработчика ошибок
function customError($errno, $errstr){
    echo "<b>Error:</b> [$errno] $errstr";
}
 
// Устанавливаем обработчик ошибок
set_error_handler("customError");
 
// Вызываем ошибку
echo($test);
?>

Регистрация ошибок

Журнал сообщений об ошибках в текстовом файле

Вы также можете записать подробную информацию об ошибке в файл журнала, например:

<?php
function calcDivision($dividend, $divisor){
    if($divisor == 0){
        trigger_error("calcDivision(): The divisor cannot be zero", E_USER_WARNING);
        return false;
    } else{
        return($dividend / $divisor);
    }
}
function customError($errno, $errstr, $errfile, $errline, $errcontext){
    $message = date("Y-m-d H:i:s - ");
    $message .= "Error: [" . $errno ."], " . "$errstr in $errfile on line $errline, ";
    $message .= "Variables:" . print_r($errcontext, true) . "rn";
    
    error_log($message, 3, "logs/app_errors.log");
    die("There was a problem, please try again.");
}
set_error_handler("customError");
echo calcDivision(10, 0);
echo "This will never be printed.";
?>

Отправка сообщений об ошибках по электронной почте

Вы также можете отправить электронное письмо с подробностями об ошибке, используя ту же функцию error_log().

<?php
function calcDivision($dividend, $divisor){
    if ($divisor == 0){
        trigger_error("calcDivision(): The divisor cannot be zero", E_USER_WARNING);
        return false;
    } else{
        return($dividend / $divisor);
    }
}
function customError($errno, $errstr, $errfile, $errline, $errcontext){
    $message = date("Y-m-d H:i:s - ");
    $message .= "Error: [" . $errno ."], " . "$errstr in $errfile on line $errline, ";
    $message .= "Variables:" . print_r($errcontext, true) . "rn";
    
    error_log($message, 1, "webmaster@example.com");
    die("There was a problem, please try again. Error report submitted to webmaster.");
}
set_error_handler("customError");
echo calcDivision(10, 0);
echo "This will never be printed.";
?>

Вызов ошибок

Хотя движок PHP выдает ошибку всякий раз, когда он сталкивается с проблемой в вашем скрипте, вы также можете вызвать ошибки самостоятельно. Это может помочь сделать ваше приложение более надежным, поскольку оно может выявлять потенциальные проблемы до того, как они перерастут в серьезные ошибки.

Чтобы вызвать ошибку в скрипте, вызовите функцию trigger_error(), передав сообщение об ошибке, которое вы хотите сгенерировать:

trigger_error("There was a problem.");

Рассмотрим следующую функцию, которая вычисляет деление двух чисел.

<?php
function calcDivision($dividend, $divisor){
    return($dividend / $divisor);
}
 
// Вызываем функцию
echo calcDivision(10, 0); // Выводит: Warning: Division by zero in C:wampwwwprojecttest.php on line 3
?>

Это сообщение выглядит не очень информативным. Рассмотрим следующий пример, в котором для генерации ошибки используется функция trigger_error().

<?php
function calcDivision($dividend, $divisor){
    if($divisor == 0){
        trigger_error("Делитель не может быть нулевым", E_USER_WARNING);
        return false;
    } else{
        return($dividend / $divisor);
    }
}
 
// Вызываем функцию
echo calcDivision(10, 0); // Выводит: Warning: Делитель не может быть нулевым C:wampwwwprojecterror.php on line 4
?>

Как видите, сообщение об ошибке, созданное во втором примере, более четко объясняет проблему по сравнению с предыдущим.

PHP5 Обработка ошибок



Обработка ошибок по умолчанию в PHP очень проста. Сообщение об ошибке с именем файла, строка число и сообщение, описывающее ошибку, отправляется в браузер.


При создании скриптов и веб-приложений, обработка ошибок, является важной
частью. Если коду не хватает кода проверки ошибок, программа может выглядеть
непрофессионально и Вы можете быть открыты для рисков безопасности.

Учебник содержит несколько из наиболее распространенных методов проверки ошибок в PHP.

Вы узнаете различные методы обработки ошибок:

  • Простое заявление это()
  • Пользовательские ошибки и триггеры ошибок
  • Отчеты об ошибках

PHP Основная обработка ошибок

В первом примере показан простой скрипт, открывающий текстовый файл: использование функции это()

Пример

<?php
$file=fopen(«welcome.txt»,»r»);
?>

Если файл не существует, Вы можете получить ошибку, как эта:

Внимание: fopen(welcome.txt) [function.fopen]: не удалось открыть поток:
Нет такого файла или каталога в C:webfoldertest.php на линии 2

Чтобы запретить пользователю получать сообщение об ошибке, подобное приведенному примеру выше, мы проверяем
файл, существует ли он до того, как мы попытаемся получить к нему доступ:

Пример

<?php
if(!file_exists(«welcome.txt»)) {
  die(«Файл не найден»);
}
else {
  $file=fopen(«welcome.txt»,»r»);
}
?>

Теперь, если файл не существует вы получите ошибку, как эта:

Файл не найден

Приведенный ниже код более эффективен, чем предыдущий код, поскольку он использует простой механизм обработки ошибок для остановки сценария после ошибки.

Тем не менее, остановить просто сценарий не всегда правильный путь. Рассмотрим альтернативные функции PHP для обработки ошибок.


PHP Создание пользовательского обработчика ошибок

Создать пользовательский обработчик ошибок довольно просто. Создаем специальную функцию, которая может быть вызвана при возникновении ошибки в PHP.

Эта функция должна быть способна обрабатывать, как минимум два параметра (уровень ошибки и сообщение об ошибке),
но можно принимать до пяти параметров (дополнительно: файл, номер строки и контекст ошибки):

Синтаксис

error_function(error_level,error_message,
error_file,error_line,error_context)

Параметр Описание
error_level Необходимо. Указывает уровень отчета об ошибках для пользовательской ошибки.
Должно быть числовое значение. См. таблицу ниже для возможных уровней отчета об ошибках
error_message Необходимо. Указывает сообщение об ошибке определяемая пользователем
error_file Необязательно. Задает имя файла, в котором произошла ошибка
error_line Необязательно. Указывает номер строки, в которой произошла ошибка
error_context Необязательно. Задает массив, содержащий все переменные и их значения, используемые при возникновении ошибки

PHP Уровни отчетов об ошибках

Эти уровни отчетов об ошибках, являются различными типами ошибок, для которых может использоваться определяемый пользователем обработчик ошибок:

Значение Констант Описание
2 E_WARNING Неустранимые ошибки выполнения. Выполнение скрипта не останавливается
8 E_NOTICE Уведомления среды выполнения. Сценарий нашел что-то, что могло бы быть ошибкой, но могло бы также произойти при запуске сценария, как обычно
256 E_USER_ERROR Неустранимая ошибка пользователя. Это похоже на набор E_ERROR установленный программистом с помощью функции PHP trigger_error()
512 E_USER_WARNING Неустранимое пользовательское предупреждение. Это похоже на набор E_WARNING установленный программистом с помощью функции PHP trigger_error()
1024 E_USER_NOTICE Автоматическое уведомление пользователя. Это похоже на набор E_NOTICE устанавливается программистом с помощью функции PHP trigger_error()
4096 E_RECOVERABLE_ERROR Перехватываемая неустранимая ошибка. Это похоже на набор E_ERROR но может быть перехватана пользователем, определенной обработкой (смотреть также set_error_handler())
8191 E_ALL Все ошибки и предупреждение (E_STRICT становится частью E_ALL в PHP 5.4)

Теперь давайте создадим функцию для обработки ошибок:

Пример

function customError($errno, $errstr) {
  echo «<b>Ошибка:</b> [$errno] $errstr<br>»;
  echo «Конечный Script»;
  die();
}

Приведенный выше код, является простой функцией обработки ошибок. Когда он срабатывает, он получает код ошибки и сообщение об ошибке.
Затем выводится уровень ошибки и сообщение и завершается сценарий.

Теперь, когда Вы создали функцию обработки ошибок, Вы должны решить, когда она должно сработать.


PHP Установить обработчик ошибок

Обработчик ошибок по умолчанию для PHP является встроенным обработчиком ошибок.
Мы собираемся сделать функцию над обработчиком ошибок по умолчанию на время скрипта.

Можно изменить обработчик ошибок для применения только к некоторым ошибкам, таким образом,
сценарий может обрабатывать различные ошибки по-разному.
Однако, в этом примере мы будем использовать наш пользовательский обработчик ошибок для всех ошибок:

set_error_handler(«customError»);

Поскольку мы хотим, чтобы наша пользовательская функция обрабатывала все ошибки, set_error_handler()
требуется только один параметр, второй параметр может быть добавлен, чтобы указать уровень ошибки.

Тестирование обработчика ошибок при попытке вывести несуществующую переменную:

Пример

<?php
//функция обработчика ошибок
function customError($errno, $errstr) {
  echo «<b>Ошибка:</b> [$errno] $errstr»;
}

//установить обработчик ошибок
set_error_handler(«customError»);

//Вызов ошибки
echo($test);
?>

Выходные данные приведенного выше кода должны быть примерно такими:

Ошибка: [8] Неопределенна переменная: test


PHP Вызвать ошибку

В скрипте, где пользователи могут вводить данные, полезно инициировать ошибки, когда происходит незаконный ввод.
В PHP это делается с помощью функции trigger_error().

В этом примере возникает ошибка, если $test переменная больше, чем 1:

Пример

<?php
$test=2;
if ($test>=1)
{

 
trigger_error(«Значение должно быть 1 или ниже»);
}
?>

Выходные данные приведенного выше кода должны быть примерно такими:

Заметьте: Значение должно быть 1 или ниже
в C:webfoldertest.php на линии 6

Ошибка может быть вызвана в любом месте сценария и путем добавления
второй параметр, Вы можете указать, какой уровень ошибки срабатывает.

Возможные типы ошибок:

  • E_USER_ERROR — Неустранимая пользовательская ошибка выполнения. Ошибки, из которых невозможно восстановить. Выполнение скрипта прекращается
  • E_USER_WARNING — Непоправимое пользовательское предупреждение во время выполнения. Выполнение скрипта не останавливается
  • E_USER_NOTICE — Невыполнение. Уведомление о времени выполнения, созданное пользователем. Сценарий нашел что-то, что могло бы быть ошибкой, но могло бы также произойти при запуске сценария

В этом примере E_USER_WARNING происходит, если переменная $test больше, чем 1. Если происходит E_USER_WARNING мы будем использовать наш пользовательский обработчик ошибок и закончить сценарий:

Пример

<?php
//функция обработчика ошибок
function customError($errno, $errstr) {
  echo «<b>Ошибка:</b> [$errno] $errstr<br>»;
  echo «Закончить Script»;
  die();
}

//установить обработчик ошибок
set_error_handler(«customError»,E_USER_WARNING);

//вызов ошибки
$test=2;
if ($test>=1) {
  trigger_error(«Значение должно быть 1 или ниже»,E_USER_WARNING);
}
?>

Выходные данные приведенного выше кода должны быть примерно такими:

Ошибка: [512] Значение должно быть 1 или ниже
Конец скрипта

Теперь, когда мы научились создавать собственные ошибки и как их вызвать,
давайте посмотрим на ошибки.


PHP Регистрация ошибок

По умолчанию, PHP отправляет отчет об ошибке в систему регистрации на сервер или файл,
в зависимости от того, как конфигурация error_log установлена в php.ini-файл. По
с помощью функции error_log() можно отправлять журнал ошибок в указанный файл или в удаленное место назначения.

Отправка сообщений об ошибках по электронной почте, может быть хорошим способом получения уведомления о конкретных ошибках.

PHP Отправка сообщение об ошибке по электронной почте

В приведенном ниже примере мы отправим электронное письмо с сообщением об ошибке и
сценарий, если возникает ошибка:

Пример

<?php
//функция обработчика ошибок
function customError($errno, $errstr) {
  echo «<b>Ошибка:</b> [$errno] $errstr<br>»;
  echo «Веб-мастер был уведомлен»;
  error_log(«Ошибка: [$errno] $errstr»,1,
  «someone@example.com»,»От: webmaster@example.com»);
}

//установить обработчик ошибок
set_error_handler(«customError»,E_USER_WARNING);

//вызов ошибки
$test=2;
if ($test>=1) {
  trigger_error(«Значение должно быть 1 или ниже»,E_USER_WARNING);
}
?>

Выходные данные приведенного выше кода должны быть примерно такими:

Ошибка: [512] Значение должно быть 1 или ниже
Веб-мастер был уведомлен

И почта, полученная из кода выше, выглядит так:

Ошибка: [512] начение должно быть 1 или ниже

Не должно использоваться со всеми ошибками. Регулярные ошибки должны быть зарегистрированы на
сервере, использующий систему регистрации PHP по умолчанию.


Всем привет! Давно не писал потому, что уже наступило жаркое лето, и я старался поменьше сидеть за компом, занимался спортом и принимал солнечные ванны. Сегодняшняя статья посвящена модулю: «Заметили Ошибку? — нажимай Ctrl + Q». Мне пришлось ввести данный модуль в связи с критикой моих посетителей, в частности Николая, за многочисленные ошибки по русскому языку. И этот модуль себя хорошо зарекомендовал. Уже множество ошибок исправлено благодаря нему и вам, неравнодушным читателям. Думаю, и Вам он может пригодится на вашем сайте. Также сегодня посоветовался с одним ГУРУ веб-программирования, который помог избавится от некрасивых мест в коде.

Так что, поехали!

Формулируем задачу: При обнаружении пользователем ошибки он выделяет данный текст и нажимает CTRL + Q. Всплывает модальное окно с текстовым полем, где уже находится выделенный текст, и кнопкой отправить. Пользователь поправляет ошибку в тексте и нажимает отправить. Нам на почту приходит: адрес страницы с которой произошла отправка формы, выделенный текст и поправленный вариант. Можно и не на почту сделать, а в админку CMS. Но у меня пока админка CMS не доделана, поэтому будем пока отсылать на почту.

Скрипт использует библиотеку jquery-1.9.0.min.js

Отслеживаем нажатие CTRL+Q

var isCtrl = false;

$(document).keyup(function (e) {
  if(e.which == 17) isCtrl=false;
}).keydown(function (e) {

  if(e.which == 17) isCtrl=true;
  if(e.which == 81 && isCtrl == true) {

      // Здесь можем писать действия, которые необходимы при нажатии CTRL+Q
  }

});

Далее необходимо, чтобы у нас всплывало модальное окно. Поискав немного в интернете, нашёлся отличный вариант. В него сразу прописываем форму с текстовым полем и кнопкой отправить.

Вставляем перед закрывающимся тегом </body>.

<div id="modal_form"> <!-- Сaмo oкнo --> 
      <span id="modal_close">X</span> <!-- Кнoпкa зaкрыть --> 
      <!-- Тут любoе сoдержимoе -->
      <div id='content_modal_error'>
      <h2 align='center' style='color:#490301;'>Опишите, пожалуйста, ошибку</h2>
	  
      <form name='from_error' id='from_error'>
      <textarea id='modal_form_text' align='left' maxlength="500">
      </textarea>
      <br><br>
      <input type='submit' value='Отправить' class='button13' name='sub_from_error' style='margin-left:260px;'>
      </form>
      </div>
</div>
<div id="overlay">
</div><!-- Пoдлoжкa -->

При закрытии необходимо с помощью innerHTML в модальное окно вписать то, что было в первоначальном положении (т.е. нашу форму), иначе при повтором запуске у пользователя могут остаться предыдущие сообщения, например, об успешной отправке формы.

Скрипт для модального окна + делаем в фокусе текстовое поле.

...
$('#overlay').fadeIn(400, // снaчaлa плaвнo пoкaзывaем темную пoдлoжку 
   function(){ // пoсле выпoлнения предыдущей aнимaции
			
			
   $('#modal_form') 
	.css('display', 'block') // убирaем у мoдaльнoгo oкнa display: none;	
	.animate({opacity: 1, top: '50%'}, 200); // плaвнo прибaвляем прoзрaчнoсть oднoвременнo сo съезжaнием вниз
				 
    // Делаем текстовое поле в фокусе.
    var modal_form_text = document.getElementById("modal_form_text"); 			
    modal_form_text.focus();
			
});

/* Зaкрытие мoдaльнoгo oкнa, тут делaем тo же сaмoе нo в oбрaтнoм пoрядке */
$('#modal_close, #overlay').click( function(){ // лoвим клик пo крестику или пoдлoжке
  $('#modal_form')
	.animate({opacity: 0, top: '45%'}, 200,  // плaвнo меняем прoзрaчнoсть нa 0 и oднoвременнo двигaем oкнo вверх
	      function(){ // пoсле aнимaции
		    $(this).css('display', 'none'); // делaем ему display: none;
		    $('#overlay').fadeOut(400); // скрывaем пoдлoжку
               }
         );
			
			
  var content_modal = document.getElementById("content_modal_error");
			
  content_modal.innerHTML = "<h2 align='center' style='color:#490301;'>Опишите, пожалуйста, ошибку</h2><form name='from_error' id='from_error'><textarea id='modal_form_text' align='left' maxlength='500'></textarea><br><br><input type='submit' value='Отправить' class='button13' name='sub_from_error' style='margin-left:260px;'></form>";
			
});
...

Стили (Также прикладываю стили для кнопки):

#modal_form {
  width: 700px; 
  height: 300px; /* Рaзмеры дoлжны быть фиксирoвaны */
  border-radius: 5px;
  border: 3px #000 solid;
  background: #fff;
  position: fixed; /* чтoбы oкнo былo в видимoй зoне в любoм месте */
  top: 45%; /* oтступaем сверху 45%, oстaльные 5% пoдвинет скрипт */
  left: 50%; /* пoлoвинa экрaнa слевa */
  margin-top: -150px;
  margin-left: -350px; /* тут вся мaгия центрoвки css, oтступaем влевo и вверх минус пoлoвину ширины и высoты сooтветственнo =) */
  display: none; /* в oбычнoм сoстoянии oкнa не дoлжнo быть */
  opacity: 0; /* пoлнoстью прoзрaчнo для aнимирoвaния */
  z-index: 9999; /* oкнo дoлжнo быть нaибoлее бoльшем слoе */
  padding: 20px 10px;
}
/* Кнoпкa зaкрыть  */
#modal_form #modal_close {
  width: 21px;
  height: 21px;
  position: absolute;
  top: 10px;
  right: 10px;
  cursor: pointer;
  display: block;
}
/* Пoдлoжкa */
#overlay {
  z-index:9998; /* пoдлoжкa дoлжнa быть выше слoев элементoв сaйтa, нo ниже слoя мoдaльнoгo oкнa */
  position:fixed; /* всегдa перекрывaет весь сaйт */
  background-color:#000; /* чернaя */
  opacity:0.8; /* нo немнoгo прoзрaчнa */
  -moz-opacity:0.8; /* фикс прозрачности для старых браузеров */
  filter:alpha(opacity=80);
  width:100%; 
  height:100%; /* рaзмерoм вo весь экрaн */
  top:0; /* сверху и слевa 0, oбязaтельные свoйствa! */
  left:0;
  cursor:pointer;
  display:none; /* в oбычнoм сoстoянии её нет) */
}

#modal_form_text {
  margin-top:20px;
  margin-left:50px;
  width:600px;
  height:150px;
  border:1px solid gray;
  resize: none;
  padding:5px;
	
}
input.button13 {
  display: inline-block;
  width: 15em;
  font-size: 80%;
  color: rgba(255,255,255,.8);
  text-shadow: #543e3c 0 1px 2px;
  text-decoration: none;
  text-align: center;
  line-height: 1.1;
  white-space: pre-line;
  padding: .7em 0;
  border: 1px solid;
  border-color: #72140c #72140c #72140c #72140c;
  border-radius: 6px;
  outline: none;
  background: #60a3d8 linear-gradient(#3c0b06, #580f08 50%, #72140c);
  box-shadow: inset rgba(255,255,255,.5) 1px 1px;
  height:40px;
}
input.button13:first-line{
  font-size: 140%;
  font-weight: 700;
}
input.button13:hover {
  color: rgb(255,255,255);
  background-image: linear-gradient(#3c0b06, #580f08 50%, #72140c);
}
input.button13:active {
  color: rgb(255,255,255);
  border-color: #2970a9;
  background-image: linear-gradient(#a40d00, #a40d00);
  box-shadow: none;
}

Отлично! Будем использовать для получении информации о выделенном тексте пользователя функцию:

function getSelectedText() 
{
    var text = "";
    if (window.getSelection) {
        text = window.getSelection().toString();
    }else if (document.getSelection) {
        text = document.getSelection().toString();
    }else if (document.selection) {
        text = document.selection.createRange().text.toString();
    }
    return text;
}

Таким образом, помещаем в текстовое поле то, что именно выделил пользователь:

...
 if(e.which == 17) isCtrl= true;
 if(e.which == 81 && isCtrl ==  true) {
		
  TextSelect = getSelectedText();		
  $( '#modal_form_text').val(TextSelect);
 
   ...

}
...

Пишем обработчик события отправки формы. С помощью ajax отправляем три параметра: Выделенный текст, исправленный вариант с текстового поля модального окна и URL страницы (с которой отослали сообщение).

Как только отправили, если сервер немного тормозит — ставим гиф-картинку с крутящимся колёсиком.

$('#from_error').submit( function(){
			
    var url = window.location.pathname;
    var modal_form_text = $('#modal_form_text').val();
			
    $.post('/ajax/detect_error.php', {TextSelect: TextSelect, url: url, modal_form_text: modal_form_text}, detect_func);			
    var content_modal = document.getElementById("content_modal_error");			
    content_modal.innerHTML = "<br><br><br><center><img src='/images/ajax_loader_red_512.gif' width='150' height='150'></center>";
    
    // Функция, которая запускается при ответе сервера при ajax запросе.
    function detect_func(data) {
        var data_str = "";
	if(data == "") {
         data_str = "<center><br><br><br><Br><span style='color:red;'>Идут технические работы - попробуйте позже</span>  </center>";
        }
	else {	
	 data_str = data.toString();		
	}	
	var content_modal = document.getElementById("content_modal_error");
	content_modal.innerHTML = data_str;		
   } 
			
  return false;
			
});

Ловим с помощью php скрипта ajax запрос:

if(isset($_POST['TextSelect']) && isset($_POST['url']) && isset($_POST['modal_form_text'])) {
		
  $_POST['TextSelect'] = trim($_POST['TextSelect']);
  $_POST['url'] = trim($_POST['url']);
  $_POST['modal_form_text'] = trim($_POST['modal_form_text']);
		
  $_POST['TextSelect'] = strip_tags($_POST['TextSelect']);
  $_POST['url'] =strip_tags($_POST['url']);
  $_POST['modal_form_text'] = strip_tags($_POST['modal_form_text']);
		
  $_POST['TextSelect'] = substr($_POST['TextSelect'], 0, 500);
  $_POST['url'] = substr($_POST['url'], 0, 255);
  $_POST['modal_form_text']  = substr($_POST['modal_form_text'], 0, 500);
		
		
  $from = "Content-type: text/plain; charset=utf-8"."rn";
  $from = $from.'From: xxx@gmail.com';
		
  $subject = "Сайт yyy Присалил ошибку" ;
  $message = "nn URL:".$_POST['url'] ;
  $message = $message."nn Выделенный текст:".$_POST['TextSelect'];
  $message = $message."nn Описание ошибки:".$_POST['modal_form_text'];
		
  if (!mail( "zzz@yandex.ru", $subject, $message,  $from)) {
          
      echo "Письмо не отправлено. Ошибка 1. Сообщите Администратору.";
      exit;
  }
  else {
				
      echo "<br><Br><br><Br><br><h2 align='center' style='color:#742c2c;'>Спасибо за ваше замечание! В ближайшее время я его рассмотрю.</h2>";
				
  }
		
}

Именно он и отсылает данные на E-mail: zzz@yandex.ru

Вы должны сами проверить БЕЗОПАСНОСТЬ данного скрипта. В статье показаны лишь идеи.

Можно вместо отправки email добавлять сообщения в БАЗУ ДАННЫХ.

Файлы для данного скрипта:

Скачать модуль «Нашли ошибку — нажимай Ctrl + Q»

Счастливого программирования!

Хотя PHP уже давно поддерживает обработку исключений, однако, по сравнению с Java эта поддержка была довольно слабой

Первоначальная поддержка обработки исключений была введена в язык с 5 версии PHP, с двумя простыми встроенными классами исключений — Exception и ErrorException, с поддержкой дополнительных классов через SPL. Идея этого поста состоит в том, чтобы представить читателям современные возможности обработки исключений PHP. 

Новый интерфейс

Хотя PHP 7 предоставляет классы Error и Exception, давайте сначала затронем интерфейс Throwable . И Error и Exception классы реализуют Throwable интерфейс — это основа для любого объекта , который может быть брошен с помощью оператора throw. Единственное, что он не может быть реализован непосредственно в классах пользовательского пространства, только через расширение класса Exception. Кроме того, он обеспечивает единую точку для отлова обоих типов ошибок в одном выражении:

<?php

try {
// ваш код
} catch (Throwable $e) {
echo 'Очень хороший способ отловить исключения и ошибки';
}

Список доступных встроенных классов исключений начиная с PHP 7.4:

  • Exception
  • ErrorException
  • Error
  • ArgumentCountError
  • ArithmeticError
  • AssertionError
  • DivisionByZeroError
  • CompileError
  • ParseError
  • TypeError

Дополнительные классы исключений можно найти в стандартной библиотеке PHP . И наиболее заметным из расширений JSON является класс JsonException.

THROWABLE

Интерфейс Throwable  PHP 7:

interface Throwable
{
public function getMessage(): string; // Error reason
public function getCode(): int; // Error code
public function getFile(): string; // Error begin file
public function getLine(): int; // Error begin line
public function getTrace(): array; // Return stack trace as array like debug_backtrace()
public function getTraceAsString(): string; // Return stack trace as string
public function getPrevious(): Throwable; // Return previous `Trowable`
public function __toString(): string; // Convert into string
}

Вот иерархия Throwable:

interface Throwable
|- Error implements Throwable
|- ArithmeticError extends Error
|- DivisionByZeroError extends ArithmeticError
|- AssertionError extends Error
|- ParseError extends Error
|- TypeError extends Error
|- ArgumentCountError extends TypeError
|- Exception implements Throwable
|- ClosedGeneratorException extends Exception
|- DOMException extends Exception
|- ErrorException extends Exception
|- IntlException extends Exception
|- LogicException extends Exception
|- BadFunctionCallException extends LogicException
|- BadMethodCallException extends BadFunctionCallException
|- DomainException extends LogicException
|- InvalidArgumentException extends LogicException
|- LengthException extends LogicException
|- OutOfRangeException extends LogicException
|- PharException extends Exception
|- ReflectionException extends Exception
|- RuntimeException extends Exception
|- OutOfBoundsException extends RuntimeException
|- OverflowException extends RuntimeException
|- PDOException extends RuntimeException
|- RangeException extends RuntimeException
|- UnderflowException extends RuntimeException
|- UnexpectedValueException extends RuntimeException

Ошибка, почему?

В предыдущих версиях PHP ошибки обрабатывались совершенно иначе, чем исключения. Если возникала ошибка, то пока она не была фатальной, она могла быть обработана пользовательской функцией.

Проблема заключалась в том, что было несколько фатальных ошибок, которые не могли быть обработаны определяемым пользователем обработчиком ошибок. Это означало, что вы не могли корректно обрабатывать фатальные ошибки в PHP. Было несколько побочных эффектов, которые были проблематичными, такие как потеря контекста времени выполнения, деструкторы не вызывались, да и вообще иметь дело с ними было неудобно. В PHP 7 фатальные ошибки теперь являются исключениями, и мы можем легко их обработать. Фатальные ошибки приводят к возникновению исключений. Вам необходимо обрабатывать нефатальные ошибки с помощью функции обработки ошибок.

Вот пример ловли фатальной ошибки в PHP 7.1. Обратите внимание, что нефатальная ошибка не обнаружена.

<?php 

try {
// будет генерировать уведомление, которое не будет поймано
echo $someNotSetVariable;
// фатальная ошибка, которая сейчас на самом деле ловится
someNoneExistentFunction();
} catch (Error $e) {
echo "Error caught: " . $e->getMessage();
}

Этот скрипт выведет сообщение об ошибке при попытке доступа к недопустимой переменной. Попытка вызвать функцию, которая не существует, приведет к фатальной ошибке в более ранних версиях PHP, но в PHP 7.1 вы можете ее перехватить. Вот вывод для скрипта:

Notice: Undefined variable: someNotSetVariable on line 3
Error caught: Call to undefined function someNoneExistentFunction()

Константы ошибок

В PHP много констант, которые используются в отношении ошибок. Эти константы используются при настройке PHP для скрытия или отображения ошибок определенных классов.

Вот некоторые из наиболее часто встречающихся кодов ошибок:

  • E_DEPRECATED — интерпретатор сгенерирует этот тип предупреждений, если вы используете устаревшую языковую функцию. Сценарий обязательно продолжит работать без ошибок.
  • E_STRICT — аналогично E_DEPRECATED, — указывает на то, что вы используете языковую функцию, которая не является стандартной в настоящее время и может не работать в будущем. Сценарий будет продолжать работать без каких-либо ошибок.
  • E_PARSE — ваш синтаксис не может быть проанализирован, поэтому ваш скрипт не запустится. Выполнение скрипта даже не запустится.
  • E_NOTICE — движок просто выведет информационное сообщение. Выполнение скрипта не прервется, и ни одна из ошибок не будет выдана.
  • E_ERROR — скрипт не может продолжить работу, и завершится. Выдает ошибки, а как они будут обрабатываться, зависит от обработчика ошибок.
  • E_RECOVERABLE_ERROR — указывает на то, что, возможно, произошла опасная ошибка, и движок работает в нестабильном состоянии. Дальнейшее выполнение зависит от обработчика ошибок, и ошибка обязательно будет выдана.

Полный список констант можно найти в руководстве по PHP.

Функция обработчика ошибок

Функция set_error_handler() используется, чтобы сообщить PHP как обрабатывать стандартные ошибки, которые не являются экземплярами класса исключений Error. Вы не можете использовать функцию обработчика ошибок для фатальных ошибок. Исключения ошибок должны обрабатываться с помощью операторов try/catch. set_error_handler() принимает callback функцию в качестве своего параметра. Callback-функции в PHP могут быть заданы двумя способами: либо строкой, обозначающей имя функции, либо передачей массива, который содержит объект и имя метода (именно в этом порядке). Вы можете указать защищенные и приватные методы для callable в объекте. Вы также можете передать значение null, чтобы указать PHP вернуться к использованию стандартного механизма обработки ошибок. Если ваш обработчик ошибок не завершает программу и возвращает результат, ваш сценарий будет продолжать выполняться со строки, следующей за той, где произошла ошибка.

PHP передает параметры в вашу функцию обработчика ошибок. Вы можете опционально объявить их в сигнатуре функции, если хотите использовать их в своей функции.

Вот пример:

<?php

function myCustomErrorHandler(int $errNo, string $errMsg, string $file, int $line) {
echo "Ух ты, мой обработчик ошибок получил #[$errNo] в [$file] на [$line]: [$errMsg]";
}

set_error_handler('myCustomErrorHandler');

try {
why;
} catch (Throwable $e) {
echo 'И моя ошибка: ' . $e->getMessage();
}

Если вы запустите этот код в PHP-консоли php -a, вы должны получить похожий вывод:

Error #[2] occurred in [php shell code] at line [3]: [Use of undefined constant why - assumed 'why' (this will throw an Error in a future version of PHP)]

Самые известные PHP библиотеки , которые делают обширное использование РНР set_error_handler() и могут сделать хорошие представления исключений и ошибок являются whoops,  и Symony Debug, ErrorHandler компоненты. Я смело рекомендую использовать один из них. Если не собираетесь их использовать в своем проекте, то вы всегда можете черпать вдохновение из их кода. В то время как компонент Debug широко используется в экосистеме Symfony, Whoops остается библиотекой выбора для фреймворка Laravel . 

Для подробного и расширенного использования, пожалуйста, обратитесь к руководству по PHP для обработчика ошибок.

Отображение или подавление нефатальной ошибки

Когда ваше приложение выходит в продакшн, логично, что вы хотите скрыть все системные сообщения об ошибках во время работы, и ваш код должен работать без генерации предупреждений или сообщений. Если вы собираетесь показать сообщение об ошибке, убедитесь, что оно сгенерировано и не содержит информации, которая может помочь злоумышленнику проникнуть в вашу систему.

В вашей среде разработки вы хотите, чтобы все ошибки отображались, чтобы вы могли исправить все проблемы, с которыми они связаны, но в процессе работы вы хотите подавить любые системные сообщения, отправляемые пользователю.

Для этого вам нужно настроить PHP, используя следующие параметры в вашем файле php.ini:

  • display_errors – может быть установлен в false для подавления сообщений
  • log_errors – может использоваться для хранения сообщений об ошибках в файлах журнала
  • error_reporting – можно настроить, какие ошибки вызывают отчет

Лучше всего корректно обрабатывать ошибки в вашем приложении. В производственном процессе вы должны скорее регистрировать необработанные ошибки, чем разрешать их отображение пользователю. Функция error_log() может использоваться для отправки сообщения одной из определенных процедур обработки ошибок. Вы также можете использовать функцию error_log() для отправки электронных писем, но лично вы бы предпочли использовать хорошее решение для регистрации ошибок и получения уведомлений при возникновении ошибок, например Sentry или Rollbar .

Существует вещь, называемая оператором контроля ошибок ( @ ), который по своей сути может игнорировать и подавлять ошибки. Использование очень простое — просто добавьте любое выражение PHP с символом «собаки», и сгенерированная ошибка будет проигнорирована. Хотя использование этого оператора может показаться интересным, я призываю вас не делать этого. Мне нравится называть это живым пережитком прошлого.

Больше информации для всех функций, связанных с ошибками PHP, можно найти в руководстве.

Исключения (Exceptions)

Исключения являются основной частью объектно-ориентированного программирования и впервые были представлены в PHP 5.0. Исключением является состояние программы, которое требует специальной обработки, поскольку оно не выполняется ожидаемым образом. Вы можете использовать исключение, чтобы изменить поток вашей программы, например, чтобы прекратить что-либо делать, если некоторые предварительные условия не выполняются.

Исключение будет возникать в стеке вызовов, если вы его не перехватите. Давайте посмотрим на простой пример:

try {
print "это наш блок попыток n";
throw new Exception();
} catch (Exception $e) {
print "что-то пошло не так, есть улов!";
} finally {
print "эта часть всегда выполняется";
}

PHP включает в себя несколько стандартных типов исключений, а стандартная библиотека PHP (SPL) включает в себя еще несколько. Хотя вам не нужно использовать эти исключения, это означает, что вы можете использовать более детальное обнаружение ошибок и отчеты. Классы Exception и Error реализуют интерфейс Throwable и, как и любые другие классы, могут быть расширены. Это позволяет вам создавать гибкие иерархии ошибок и адаптировать обработку исключений. Только класс, который реализует класс Throwable, может использоваться с ключевым словом throw. Другими словами, вы не можете объявить свой собственный базовый класс и затем выбросить его как исключение.

Надежный код может встретить ошибку и справиться с ней. Разумная обработка исключений повышает безопасность вашего приложения и облегчает ведение журнала и отладку. Управление ошибками в вашем приложении также позволит вам предложить своим пользователям лучший опыт. В этом разделе мы рассмотрим, как отлавливать и обрабатывать ошибки, возникающие в вашем коде.

Ловля исключений

Вы должны использовать try/catch структуру:

<?php

class MyCustomException extends Exception { }

function throwMyCustomException() {
throw new MyCustomException('Здесь что-то не так.');
}

try {
throwMyCustomException();
} catch (MyCustomException $e) {
echo "Ваше пользовательское исключение поймано";
echo $e->getMessage();
} catch (Exception $e) {
echo "Стандартное исключение PHP";
}

Как видите, есть два предложения catch. Исключения будут сопоставляться с предложениями сверху вниз, пока тип исключения не будет соответствовать предложению catch. Эта очень простая функция throwMyCustomException() генерирует исключение MyCustomException, и мы ожидаем, что оно будет перехвачено в первом блоке. Любые другие исключения, которые произойдут, будут перехвачены вторым блоком. Здесь мы вызываем метод getMessage() из базового класса Exception. Вы можете найти больше информации о дополнительном методе в Exception PHP docs.

Кроме того, можно указать несколько исключений, разделяя их трубой ( | ).

Давайте посмотрим на другой пример:

<?php

class MyCustomException extends Exception { }
class MyAnotherCustomException extends Exception { }

try {
throw new MyAnotherCustomException;
} catch (MyCustomException | MyAnotherCustomException $e) {
echo "Caught : " . get_class($e);
}

Этот очень простой блок catch будет перехватывать исключения типа MyCustomException и MyAnotherCustomException.

Немного более продвинутый сценарий:

// exceptions.php
use SymfonyComponentHttpKernelExceptionNotFoundHttpException;

try {
throw new NotFoundHttpException();
} catch (Exception $e) {
echo 1;
} catch (NotFoundHttpException $e) {
echo 2;
} catch (Exception $e) {
echo 3;
} finally {
echo 4;
}

Это ваш окончательный ответ?

В PHP 5.5 и более поздних, блок finally также может быть указан после или вместо блоков catch. Код внутри блока finally всегда будет выполняться после блоков try и catch независимо от того, было ли выброшено исключение, и до возобновления нормального выполнения. Одним из распространенных применений блока finally является закрытие соединения с базой данных, но, наконец, его можно использовать везде, где вы хотите, чтобы код всегда выполнялся.

<?php

class MyCustomException extends Exception { }

function throwMyCustomException() {
throw new MyCustomException('Здесь что-то не так');
}

try {
throwMyCustomException();
} catch (MyCustomException $e) {
echo "Ваше пользовательское исключение поймано ";
echo $e->getMessage();
} catch (Exception $e) {
echo "Стандартное исключение PHP";
} finally {
echo "Я всегда тут";
}

Вот хороший пример того, как работают операторы PHP catch/finally:

<?php

try {
try {
echo 'a-';
throw new exception();
echo 'b-';
} catch (Exception $e) {
echo 'пойманный-';
throw $e;
} finally {
echo 'завершенный-';
}
} catch (Exception $e) {
echo 'конец-';
}

Функция обработчика исключений

Любое исключение, которое не было обнаружено, приводит к фатальной ошибке. Если вы хотите изящно реагировать на исключения, которые не перехватываются в блоках перехвата, вам нужно установить функцию в качестве обработчика исключений по умолчанию.

Для этого вы используете функцию set_exception_handler() , которая принимает вызываемый элемент в качестве параметра. Ваш сценарий завершится после того, как вызов будет выполнен.

Функция restore_exception_handler() вернет обработчик исключений к его предыдущему значению.

<?php

class MyCustomException extends Exception { }

function exception_handler($exception) {
echo "Uncaught exception: " , $exception->getMessage(), "n";
}

set_exception_handler('exception_handler');

try {
throw new Exception('Uncaught Exception');
} catch (MyCustomException $e) {
echo "Ваше пользовательское исключение поймано ";
echo $e->getMessage();
} finally {
echo "Я всегда тут";
}

print "Не выполнено";

Здесь простая функция exception_handler будет выполняться после блока finally, когда ни один из типов исключений не был сопоставлен. Последний вывод никогда не будет выполнен.

Для получения дополнительной информации обратитесь к документации PHP. 

Старый добрый «T_PAAMAYIM_NEKUDOTAYIM»

Вероятно, это было самое известное сообщение об ошибке PHP. В последние годы было много споров по этому поводу. Вы можете прочитать больше в отличном сообщении в блоге от Фила Осетра .  

Сегодня я с гордостью могу сказать, что если вы запустите этот код с PHP 7, то сообщение о T_PAAMAYIM_NEKUDOTAYIM больше не будет:

<?php

class foo
{
static $bar = 'baz';
}

var_dump('foo'::$bar);

// Output PHP < 7.0:
// PHP Parse error: syntax error, unexpected '::' (T_PAAMAYIM_NEKUDOTAYIM) in php shell code on line 1
// Output PHP > 7.0:
// string(3) "baz"
?>

В заключении

Со времени первого внедрения обработки исключений в PHP прошло много лет, пока мы не получили гораздо более надежную и зрелую обработку исключений, чем в Java. Обработка ошибок в PHP 7 получила много внимания, что делает его хорошим, открывая пространство для будущих улучшений, если мы действительно с сегодняшней точки зрения нуждаемся в них.

Понравилась статья? Поделить с друзьями:
  • Модем выдает ошибку при подключении
  • Модуль отображения информации миртек ошибка error 1
  • Модераторам мероприятия приходилось играть двойную функцию ошибка
  • Модем выдает ошибку 651 что делать
  • Модем пишет ошибка подключения к сети