Как стать автором
Обновить

Комментарии 29

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

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

Так зачем начинать раздел с однозначно вредного совета, тем более в руководстве для начинающих? А потом находятся уникумы, которые считают что Exception — это такой удобный способ передавать контекст управления и с их помощью можно, например, реализовать событийную модель, вместо этих ваших непонятных EventBus и прочих PubSub.

Единообразие
— Там ошибки, тут исключения, а можно это всё как-то до кучи сгрести?

Я стесняюсь спросить, а
try {
...
} catch (\Throwable $e) {
}
что мешает использовать?
Надо подумать как правильно описать, чтобы не сбивать с толку.
По Throwable, это видать ещё дух 5.6 не выветрился, сейчас дополню статью.
Так зачем начинать раздел с однозначно вредного совета, тем более в руководстве для начинающих?

Вопрос от новичка: а как на Ваш взгляд лучше реализовать приведенный в качестве демонстрации исключений код записи в файл? И почему ошибки при доступе к файлу, на Ваш взгляд, не вписываются в концепцию исключений?
Проблема приведенного в статье примера в том что он слишком упрощен и начинающий разработчик вынесет из него ровно одно: исключения можно использовать для того чтобы управлять процессом выполнения скрипта. Посмотрим на код (пример очень плохого кода)
function saveToCache(string $data, array $pathList = []) {
    if (\count($pathList) < 1) {
        throw new NoPathLeft();
    }

    $nextPath = array_shift($pathList);

    try {
        if (!\is_writable($nextPath)) {
            throw new NotWritablePath($nextPath);
        }

        if (false === \file_put_contents($nextPath, $data)) {
            throw new SomethingWrong($nextPath);
        }
    } catch (NotWritablePath|SomethingWrong $e) {
        saveToCache($data, $pathList);
    }
}

Подходит этот код под пример из статьи? Подходит:
— если что-то пошло не так, бросаем типизированное исключение;
— в обработчике ловим типизированное исключение и решаем что делать дальше.
Но нужны ли тут вообще исключения?
Далее, необходимость обрабатывать разные типы исключений разными способами достаточно спорная. Если мы можем обработать исключение на том же уровне абстракции где оно возникло, то почему это исключение? Если мы поймали типизированное исключение на несколько слоев выше уровня где оно возникло, то что мы в принципе тут можем сделать? Выбор на самом деле не так уж велик:
— записать в лог и завершить работу;
— повторить операцию (всякие RetryableException);
— переключиться на альтернативный сервис и выполнить операцию в нем.
Так где здесь место для большого количества разных типов исключений? Разные типы полезны при анализе логов, но не при программировании логики.
И почему ошибки при доступе к файлу, на Ваш взгляд, не вписываются в концепцию исключений?

Я хотел сказать ровно одно: исключения вещь очень сложная, давать их нужно в разделе advanced usage на примере взаимодействия подсистем с разным уровнем абстракции. Вот там можно будет показать, например, как в слое инфраструктуры могут ловиться исключения нижележащего FS уровня и может приниматься решение либо о повторной записи либо о переключении на failover хранилище.

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


$file = fopen($directory. DIRECTORY_SEPARATOR. date('Y-m-d'). '.log', 'a+');


Если директории нету, РНР сообщит нам об этом.
Если нет прав для записи, РНР сообщит нам об этом.
Если по какой-то причине невозможно открыть лог-файл — … ну, вы уже уловили тренд, да?
То есть все эти проверки и вручную бросаемые исключения в реальности не нужны.


Причем нативное сообщение об ошибке будет актуальным — там будет написано, почему конкретно файл не может быть записан, а не абстрактное "Нимагу открыть файл, насяльника!".
То есть все эти вручную бросаемые исключения на самом деле несут реальный вред, скрывая от нас реальную причину проблемы.

находятся уникумы, которые считают что Exception — это такой удобный способ передавать контекст управления
Ну вот в Python «try/except block is extremely efficient if no exceptions are raised» и вполне себе используется, например, в WTForms для передачи управления из валидатора в форму: custom-validators.

Встречал нечто похожее в Laravel, да и в Symfony местами встречается: custom_constraint, data_transformers.
Не нужно использовать их для обработки очевидных ошибок, к примеру, для валидации введённых пользователем данных (хотя тут не всё так однозначно).

На столько неоднозначно, что фактически наоборот.


А вообще материал годный. Не хватает примеров с перебросом исключений.

При всем уважении к PHP, начинать его изучать настоявшее время — ошибка.
«Взялся хоронить PHP — запаси сарай лопат» (с) Конфуций
Хоронить? Нет.
Как и наш с Вами русский язык…
Интересная позиция, можно узнать, почему вы так считаете?
Причин несколько. Вот самая простая.
hh
2 420 вакансий «Python»
1 383 вакансии «php»

Ради интереса глянул по своему городу
Слон 21
Питон 24

PHP это в первую очередь web-разработка, а взрывной рост спроса на Python c web-разработкой связан лишь опосредованно. Посмотрите на запрос «django» на hh.
Ну если сравнивать django и laravel (ближайший его аналог в мире PHP), то количество вакансий примерно одинаковое, смотрел по Москве. Оплата тоже одинаковая.

С другой стороны поиску по symfony питону уже нечего противопоставить, в этой сфере он непопулярен.
В golang, например отличная стандартная библиотека для работы с web и учитывая, что веб становится сервисно ориентированным (API <-> SPA, API <-> Mobile app) лет через пять go не оставит PHP (с его CGI подходом) ни единого шанса. Учитывая что выбирая язык нужно рассматривать ситуацию с горизонтом лет в 5 а лучше 10, то да можно учить PHP — от голода никто не умрет. Но надо быть готовым, что придется заниматься поддержкой легаси проектов, так как новые проекты будут создаваться на других технологиях.
Возьмите горизонт в 15 лет, там уже программист-ремесленник будет вымирающим видом.
Мне за три недели написало 8 хедхантеров, все на тему РНР. Это в четвертом по размеру городе в стране на 300 миллионов человек. Работы по горло, делать некому, зарплаты по местному РНР-рынку выросли на 20-30% за три года (личные наблюдения). Настоящая ошибка — это не изучать технологии, за которые платят.

UPD: уточняю — речь о США, ниже указали, что подобный рост зарплат в других местах может быть стерт инфляцией.
С позицией согласны, а вот аргументация так себе.
Увеличившиеся за 3 года зарплаты на 30% это ниже инфляции, а если еще вспомнить что у ИТ-шников зарплаты обычно к баксовым привязывались, то получится что они упали.
Прошу извинения — не подумал, что разница в инфляции может стереть весь смысл аргументации. Речь про США, инфляция примерно 2% в год.
А эта тенденция (про 20-30%) отражает общую тенденцию по США в целом?
В россии, например, в москве зарплаты нехило выросли, в питере адекватно, а вот в регионах даже упали за последние годы.

В целом правильно, но очень длинно и захвачена слишком широкая тематика. В итоге получается "галопом по европам", а после прочтения в голове остается каша. Тут нужно как минимум 2-3 статьи, причем четко позиционировать их. К примеру, первая часть статьи посвящена исправлению ошибок, а не их обработке. Это очень полезный материал, но я бы поместил его в конце, после отбивки "а теперь, когда мы научились правильно ловить возникающие ошибки, рассмотрим типичные примеры и их исправление".


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


Ну и мелкие противоречия, типа того о котором говорили выше или error_reporting(E_ALL & ~E_NOTICE); сразу после пассажа о том, что нотисы давить нельзя.

Думаю перекинуть на gitbook, там это будут различные статьи.
Небольшой пример из практики, возможно кому-то будет интересно. Как раз сегодня отправляю pull request.

Есть готовая библиотека, которая управляет tcp-сокетами, при создании сокета в ней используется примерно такой код:
// задаётся в пользовательском коде
$context = ['socket' => ['bindto' => '8.8.8.8:0']];
$remote = '8.8.8.8:53';
// а это уже в самой библиотеке
$stream = @stream_socket_client(
        $remote,
        $errno,
        $errstr,
        0,
        STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_CONNECT,
        stream_context_create($context)
);
if($stream === false) throw new \RuntimeException('Stream was not created');
return $stream;

Это рабочий код, вы можете его запустить — он успешно создаст подключение к DNS-серверам гугла. Если убрать собачку, — получится PHP Warning, т.к. в системе нет интерфейса с IP 8.8.8.8 и, соответственно, невозможно выполнить биндинг. Но несмотря на ошибку, — сокет будет создан, что в моём случае недопустимо — неправильно сработает маршрутизация. В errno, errstr разумеется ничего не попадает.
Варианты лечения:
  1. Используем set_error_handler, а затем restore_error_handler — хороший вариант, т.к. не задевает любой другой код (restore_error_handler возвращает именно тот обработчик ошибок, который был установлен до этого).
  2. Используем clear_error_last, а потом get_error_last. Плохой вариант, т.к. можно затереть уже имевшуюся ошибку, на которую теоретически может быть завязан код.
  3. Особых идей нет.


С первой проблемой справились — написали код, который делает обработку ошибки. Стоп. Как именно мы должны обработать эту ошибку? — Выбросить исключение не имеем права, т.к. сломаем обратную совместимость, а у нас библиотека с большой аудиторией между прочим. Соответственно добавляем новый параметр в библиотеку, вида «is_allow_socket_with_errors», по-умолчанию ставим false, и в зависимости от значения этого флага уже проводим проверку.
В итоге всё оказалось не так. В этом кейсе генерируется syn/ack/rst, что не гуд. Поэтому багрепорт ушёл в апстрим пхп (до последнего надеялся избежать, т.к. это надолго). А проверку потом можно было присобачить проверив куда забинжено готовое подключение (почему-то такая мысль сразу не пришла).
Пожалуйста дополните статью информацией по PSR-3 и про его реализацию например в виде библиотеки Monolog. Просто мне кажется, что обработка ошибок и их логирование тесно связаны.
Узнаю код из Bluz'а)

Раздел «Единообразие», на мой взгляд, вводит в заблуждение. В примере с set_error_handler() при любых ошибках выбрасыватся исключение. В примере с \Throwable отлавливаться будут только фатальные ошибки. Если хочется перехватывать warning'и и notice'ы всё равно придется устанавливать свой set_error_handler.

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