Накратко: PHP съобщава за проблеми по два начина: като класически грешки, например предупреждения и забележки, които се управляват чрез error_reporting, и като изключения (exceptions), които могат да се уловят с try и catch. От PHP 7 много вътрешни грешки също хвърлят обект от клас Error; заедно с Exception той имплементира интерфейса Throwable. В продукция важи: грешките се записват в лог и никога не се показват в браузъра. При PHP 8.5, заради PHP2Shell, към временните мерки спада и fatal_error_backtraces=0.
Два свята: класически грешки и изключения
PHP произхожда от време, когато грешките се извеждаха най-вече като съобщение: забележки (E_NOTICE), предупреждения (E_WARNING), остарели функции (E_DEPRECATED) и фатални грешки (E_ERROR). Много от тези съобщения оставят скрипта да продължи, често с грешни или празни данни.
Изключенията работят по различен начин. Те прекъсват нормалния ход и прескачат към следващия подходящ блок catch. Ако не се намери такъв, скриптът завършва. За вземащите решения разликата е важна: едно тихо предупреждение може да доведе до непълно записана поръчка, без някой да забележи. Изключението прави проблема видим и обработваем.
Подценявана полза от класическите съобщения са забележките от тип E_DEPRECATED. Те обявяват, че едно езиково средство ще отпадне в бъдеща версия на PHP. Който ги събира в лога и редовно ги анализира, знае преди надграждането кои места трябва да се адаптират и при спешна уязвимост не трябва първо дълго да тества.
Йерархията Throwable
Всичко, което може да се хвърли с throw и да се улови с catch, имплементира Throwable. Под него има два клона:
| Клас | Предназначение | Примери за подкласове |
|---|---|---|
Exception |
очаквани грешки на приложението и на разширенията | ErrorException, PDOException, собствени класове |
Error |
вътрешни грешки на PHP | TypeError, ValueError, ArgumentCountError, ArithmeticError, DivisionByZeroError, UnhandledMatchError |
Собствените класове за грешки наследяват Exception, а не Error. От PHP 8.0 повечето вътрешни функции при невалидни параметри хвърлят TypeError или ValueError, вместо само да изведат предупреждение. strlen([]) е такъв случай.
try, catch и finally на практика
<?php
declare(strict_types=1);
final class ZahlungFehlgeschlagen extends RuntimeException {}
function bezahlen(Bestellung $b, Zahlungsdienst $dienst, LoggerInterface $log): bool
{
try {
$dienst->belasten($b->betrag(), $b->referenz());
return true;
} catch (ZahlungFehlgeschlagen $e) {
$log->warning('Zahlung abgelehnt', ['bestellung' => $b->id(), 'exception' => $e]);
return false;
} catch (TimeoutException | VerbindungsFehler) {
// от PHP 8.0 променливата в catch е незадължителна
$log->error('Zahlungsdienst nicht erreichbar', ['bestellung' => $b->id()]);
return false;
} finally {
$dienst->verbindungSchliessen();
}
}
Няколко класа могат да се обединят с | в един catch. Блокът finally се изпълнява винаги, независимо дали е възникнала грешка. От PHP 8.0 throw е и израз и може да стои например след ??: $id = $_GET['id'] ?? throw new InvalidArgumentException('id fehlt');.
Глобални handler: set_error_handler и set_exception_handler
Две функции улавят онова, което иначе би се изплъзнало. set_exception_handler() регистрира функция за неуловени Throwable обекти. Според ръководството скриптът приключва, след като тя бъде извикана; затова тя е подходяща за записване в лог и за неутрална страница с грешка, а не за продължаване на работата.
Добра практика е handler, който записва пълната грешка с момента и случаен идентификатор в лога, а на посетителя показва само неутрална страница с този идентификатор. Поддръжката може тогава да намери случая в лога, без вътрешни подробности да излизат навън.
set_error_handler() улавя класическите грешки. Ръководството показва как те се превръщат в ErrorException, така че предупрежденията да се обработват като изключения:
<?php
set_error_handler(function (int $nr, string $text, string $datei, int $zeile): bool {
if (!(error_reporting() & $nr)) {
return false; // изключено от error_reporting
}
if ($nr === E_DEPRECATED || $nr === E_USER_DEPRECATED) {
return false; // не ескалирай забележките за остаряло
}
throw new ErrorException($text, 0, $nr, $datei, $zeile);
});
Фатални грешки като препълване на паметта не могат да се уловят по този начин. Какво се случва вътрешно при препълване, обяснява страницата memory_limit и Out-of-Memory.
Настройка на извеждането и записването на грешки
Най-важните настройки с техните стойности по подразбиране според ръководството:
| Настройка | По подразбиране | Препоръка за продукция |
|---|---|---|
error_reporting |
E_ALL от PHP 8.0.0 |
E_ALL |
display_errors |
1 |
Off |
display_startup_errors |
1 от PHP 8.0.0 |
Off |
log_errors |
0 |
On |
error_log |
празно (лог на уеб сървъра, респективно на SAPI) | собствен файл извън webroot |
fatal_error_backtraces |
1, ново в PHP 8.5.0 |
временно 0 (PHP2Shell) |
От PHP 8.2.0 error_log_mode определя правата върху лог файла, по подразбиране 0644. Ако логовете съдържат лични данни или пътища, по-строга стойност е разумна, за да не могат другите потребители на сървъра да ги четат. С ignore_repeated_errors еднакви съобщения от същия файл и същия ред могат да се обединят, за да не залее един цикъл от грешки лога.
Доставяният php.ini-production вече задава display_errors на Off. Според ръководството ini_set('display_errors', ...) по време на изпълнение не действа върху фатални грешки, а синтактичните грешки възникват, преди изобщо да се изпълни някакъв ini_set(). Затова тези стойности принадлежат в конфигурацията на сървъра. Основите са в статията Изключване на извеждането на грешки в продукция; как се водят логове без изтичане на данни, е описано в статията Сигурно записване в лог.
Обработка на грешки и PHP2Shell
За PHP 8.5 php.net посочва като една от временните мерки срещу PHP2Shell да се зададе fatal_error_backtraces=0 и след това PHP да се рестартира. Останалите мерки засягат memory_limit, post_max_size и session.upload_progress.enabled. Нито една от тях не замества актуализацията до PHP 8.5.12, 8.4.27, 8.3.36 или 8.2.35.
; само PHP 8.5, в php.ini или FPM пул, след това рестартирайте всички worker процеси
fatal_error_backtraces = 0
След PHP2Shell логовете за грешки са и важно доказателство. Ако от 20.10.2026 г. се натрупват грешки поради памет или прекратени worker процеси, това може да е признак за опити за атака, но може да има и безобидни причини. Запазете логовете и ги анализирайте заедно с логовете за достъп, както е описано в наръчника Анализ на логовете на уеб сървъра след атака.
Как помага PHP SOS конкретно?
- Проверяваме действащите настройки за извеждане на грешки и за записване в лог за всеки уебсайт и ги привеждаме във вид, подходящ за продукция.
- Прилагаме временните мерки срещу PHP2Shell според версията на PHP и рестартираме всички worker процеси.
- Запазваме логовете за грешки и за достъп от 20.10.2026 г. и ги анализираме за нередности.
- В рамките на мониторинга на сигурността докладваме необичайни струпвания на грешки, вместо да ги оставим да се загубят в лога.
Често задавани въпроси
Каква е разликата между Error и Exception?
И двата имплементират интерфейса Throwable. Exception е предвиден за грешки, които приложението очаква и обработва, например неуспешна връзка с базата данни. Error означава вътрешни грешки на PHP като TypeError или деление на нула.
Трябва ли display_errors да е включен в продукция?
Не. Съобщенията за грешки в браузъра издават пътища, заявки, а понякога и данни за достъп. В продукция display_errors трябва да е Off, а log_errors On, така че грешките да попадат в лога, а не при посетителя.
Може ли препълване на паметта да се улови с try/catch?
Не. Ако скрипт надхвърли memory_limit, PHP го прекратява с фатална грешка, която не може да се улови като изключение. Тя се появява само в лога за грешки, при условие че log_errors е активен.
Какво е fatal_error_backtraces?
Настройка, която съществува от PHP 8.5.0 и добавя backtrace към фаталните грешки. По подразбиране е активна. Като временна мярка срещу PHP2Shell php.net посочва за PHP 8.5 да се зададе 0 и след това PHP да се рестартира.