# Грешки и изключения (Exceptions) в PHP

> Грешки и изключения в PHP: Throwable, Error и Exception, try/catch/finally, глобални handler и сигурни настройки за извеждане на грешки и логове.

Източник: https://phpsos.de/bg/php/fehler-und-exceptions/  
Актуалност: 25.10.2026  
Доставчик: PHP SOS, Спешна помощ за PHP и сигурност на сървъри. Незабавна помощ по WhatsApp: https://wa.me/491736494914?text=%D0%97%D0%B4%D1%80%D0%B0%D0%B2%D0%B5%D0%B9%D1%82%D0%B5%2C%20PHP%20SOS%2C%20%D0%B8%D0%BC%D0%B0%D0%BC%D0%B5%20%D0%BD%D1%83%D0%B6%D0%B4%D0%B0%20%D0%BE%D1%82%20%D0%BF%D0%BE%D0%BC%D0%BE%D1%89%20%D1%81%20%D0%BD%D0%B0%D1%88%D0%B8%D1%8F%20PHP%20%D1%81%D1%8A%D1%80%D0%B2%D1%8A%D1%80.%20%D0%A4%D0%B8%D1%80%D0%BC%D0%B0%3A%20%E2%80%A6%20%D0%A3%D0%B5%D0%B1%D1%81%D0%B0%D0%B9%D1%82%3A%20%E2%80%A6

**Накратко:** 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
<?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
<?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](https://phpsos.de/bg/php/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()`. Затова тези стойности принадлежат в конфигурацията на сървъра. Основите са в статията [Изключване на извеждането на грешки в продукция](https://phpsos.de/bg/blog/display-errors-produktion/); как се водят логове без изтичане на данни, е описано в статията [Сигурно записване в лог](https://phpsos.de/bg/blog/sicher-loggen-php/).

## Обработка на грешки и 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.

```ini
; само PHP 8.5, в php.ini или FPM пул, след това рестартирайте всички worker процеси
fatal_error_backtraces = 0
```

След PHP2Shell логовете за грешки са и важно доказателство. Ако от 20.10.2026 г. се натрупват грешки поради памет или прекратени worker процеси, това може да е признак за опити за атака, но може да има и безобидни причини. Запазете логовете и ги анализирайте заедно с логовете за достъп, както е описано в наръчника [Анализ на логовете на уеб сървъра след атака](https://phpsos.de/bg/ratgeber/logfiles-auswerten/).

## Как помага PHP SOS конкретно?

- Проверяваме действащите настройки за извеждане на грешки и за записване в лог за всеки уебсайт и ги привеждаме във вид, подходящ за продукция.
- Прилагаме временните мерки срещу PHP2Shell според версията на PHP и рестартираме всички worker процеси.
- Запазваме логовете за грешки и за достъп от 20.10.2026 г. и ги анализираме за нередности.
- В рамките на [мониторинга на сигурността](https://phpsos.de/bg/leistungen/security-monitoring/) докладваме необичайни струпвания на грешки, вместо да ги оставим да се загубят в лога.


## Често задавани въпроси

**Каква е разликата между 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 да се рестартира.

## Източници

- [Ръководство на PHP: Конфигурация на обработката на грешки](https://www.php.net/manual/de/errorfunc.configuration.php)
- [Ръководство на PHP: Клас Error](https://www.php.net/manual/de/class.error.php)
- [Ръководство на PHP: ErrorException](https://www.php.net/manual/en/class.errorexception.php)
- [Ръководство на PHP: set_exception_handler](https://www.php.net/manual/en/function.set-exception-handler.php)
- [php.net: Съобщения за сигурност](https://www.php.net/)
