# Klaidos ir išimtys PHP

> PHP klaidos ir išimtys: Throwable, Error ir Exception, try/catch/finally, globalios tvarkyklės ir saugūs klaidų rodymo bei žurnalų nustatymai.

Šaltinis: https://phpsos.de/lt/php/fehler-und-exceptions/  
Būsena: 2026-10-25  
Teikėjas: PHP SOS, PHP avarinė pagalba ir serverio sauga. Skubi pagalba per „WhatsApp“: https://wa.me/491736494914?text=Sveiki%2C%20PHP%20SOS%2C%20mums%20reikia%20pagalbos%20su%20m%C5%ABs%C5%B3%20PHP%20serveriu.%20%C4%AEmon%C4%97%3A%20%E2%80%A6%20Svetain%C4%97%3A%20%E2%80%A6

**Trumpai:** PHP apie problemas praneša dviem būdais: klasikinėmis klaidomis, tokiomis kaip įspėjimai ir pastabos, kurias valdo `error_reporting`, ir išimtimis (exceptions), kurias galima sugauti su `try` ir `catch`. Nuo PHP 7 daug vidinių klaidų taip pat išmeta `Error` klasės objektą; kartu su `Exception` jis įgyvendina sąsają `Throwable`. Produkcijoje galioja taisyklė: klaidas protokoluoti, niekada nerodyti naršyklėje. PHP 8.5 dėl PHP2Shell prie pereinamųjų priemonių papildomai priklauso `fatal_error_backtraces=0`.

## Du pasauliai: klasikinės klaidos ir išimtys

PHP atsirado laikais, kai klaidos daugiausia būdavo išvedamos kaip pranešimas: pastabos (`E_NOTICE`), įspėjimai (`E_WARNING`), pasenusios funkcijos (`E_DEPRECATED`) ir mirtinos klaidos (`E_ERROR`). Daug šių pranešimų leidžia skriptui veikti toliau, dažnai su neteisingais ar tuščiais duomenimis.

Išimtys veikia kitaip. Jos nutraukia įprastą eigą ir peršoka į artimiausią tinkamą `catch` bloką. Jei tokio nerandama, skriptas baigiasi. Sprendimų priėmėjams skirtumas svarbus: tylus įspėjimas gali lemti, kad užsakymas išsaugomas neišsamiai, ir niekas to nepastebi. Išimtis problemą padaro matomą ir apdorojamą.

Nepakankamai vertinama klasikinių pranešimų nauda yra tipo `E_DEPRECATED` pastabos. Jos praneša, kad kalbos priemonė būsimoje PHP versijoje bus pašalinta. Kas jas renka žurnale ir reguliariai vertina, prieš atnaujindamas žino, kurias vietas reikia pritaikyti, ir ištikus skubiai saugumo spragai neturi ilgai testuoti.

## Throwable hierarchija

Viskas, ką galima išmesti su `throw` ir sugauti su `catch`, įgyvendina `Throwable`. Po juo yra dvi šakos:

| Klasė | Paskirtis | Poklasių pavyzdžiai |
|---|---|---|
| `Exception` | nuspėjamos programos ir plėtinių klaidos | `ErrorException`, `PDOException`, savos klasės |
| `Error` | vidinės PHP klaidos | `TypeError`, `ValueError`, `ArgumentCountError`, `ArithmeticError`, `DivisionByZeroError`, `UnhandledMatchError` |

Savos klaidų klasės paveldi iš `Exception`, o ne iš `Error`. Nuo PHP 8.0 dauguma vidinių funkcijų esant netinkamiems parametrams išmeta `TypeError` ar `ValueError`, o ne tik išveda įspėjimą. `strlen([])` yra vienas tokių atvejų.

## try, catch ir finally praktikoje

```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) {
        // nuo PHP 8.0 kintamasis catch dalyje neprivalomas
        $log->error('Zahlungsdienst nicht erreichbar', ['bestellung' => $b->id()]);
        return false;
    } finally {
        $dienst->verbindungSchliessen();
    }
}
```

Kelias klases viename `catch` galima sujungti su `|`. Blokas `finally` vykdomas visada, nesvarbu, ar įvyko klaida. Nuo PHP 8.0 `throw` taip pat yra išraiška ir gali stovėti, pavyzdžiui, po `??`: `$id = $_GET['id'] ?? throw new InvalidArgumentException('id fehlt');`.

## Globalios tvarkyklės: set_error_handler ir set_exception_handler

Dvi funkcijos sugauna tai, kas kitaip praslystų. `set_exception_handler()` užregistruoja funkciją nesugautiems Throwable. Pasak vadovo, ją iškvietus skriptas baigiasi; todėl ji tinka protokolavimui ir neutraliam klaidos puslapiui, o ne tolesniam darbui.

Praktiškai pasiteisino tvarkyklė, kuri visą klaidą su laiku ir atsitiktiniu identifikatoriumi įrašo į žurnalą, o lankytojui rodo tik neutralų puslapį su tuo identifikatoriumi. Pagalbos tarnyba tuomet gali rasti atvejį žurnale, neatskleisdama vidinių detalių išorei.

`set_error_handler()` gaudo klasikines klaidas. Vadovas rodo, kaip jas paversti `ErrorException`, kad įspėjimai būtų apdorojami kaip išimtys:

```php
<?php
set_error_handler(function (int $nr, string $text, string $datei, int $zeile): bool {
    if (!(error_reporting() & $nr)) {
        return false; // error_reporting išskirta
    }
    if ($nr === E_DEPRECATED || $nr === E_USER_DEPRECATED) {
        return false; // pastabų apie pasenusį kodą neeskaluoti
    }
    throw new ErrorException($text, 0, $nr, $datei, $zeile);
});
```

Mirtinų klaidų, tokių kaip atminties perpildymas, tokiu būdu sugauti negalima. Kas viduje vyksta perpildymo metu, paaiškina puslapis [memory_limit ir Out-of-Memory](https://phpsos.de/lt/php/out-of-memory/).

## Klaidų rodymo ir protokolavimo konfigūravimas

Svarbiausi nustatymai su numatytosiomis reikšmėmis pagal vadovą:

| Nustatymas | Numatytasis | Rekomendacija produkcijai |
|---|---|---|
| `error_reporting` | `E_ALL` nuo PHP 8.0.0 | `E_ALL` |
| `display_errors` | `1` | `Off` |
| `display_startup_errors` | `1` nuo PHP 8.0.0 | `Off` |
| `log_errors` | `0` | `On` |
| `error_log` | tuščias (žiniatinklio serverio arba SAPI žurnalas) | atskiras failas už žiniatinklio šakninio katalogo ribų |
| `fatal_error_backtraces` | `1`, nauja PHP 8.5.0 | laikinai `0` (PHP2Shell) |

Nuo PHP 8.2.0 `error_log_mode` nustato žurnalo failo teises, pagal numatymą `0644`. Jei žurnaluose yra asmens duomenų ar kelių, prasminga griežtesnė reikšmė, kad kiti serverio naudotojai jų neperskaitytų. Su `ignore_repeated_errors` galima sujungti identiškus pranešimus iš to paties failo ir eilutės, kad klaidų ciklas neužtvindytų žurnalo.

Kartu pateikiamas `php.ini-production` jau nustato `display_errors` į `Off`. Pasak vadovo, `ini_set('display_errors', ...)` vykdymo metu mirtinoms klaidoms neveikia, o sintaksės klaidos atsiranda dar prieš pradedant vykdyti bet kokį `ini_set()`. Todėl šios reikšmės turi būti serverio konfigūracijoje. Pagrindimą rasite straipsnyje [Klaidų rodymo išjungimas produkcijoje](https://phpsos.de/lt/blog/display-errors-produktion/); kaip žurnalus vesti be duomenų nutekėjimo, aprašyta straipsnyje [Saugus protokolavimas](https://phpsos.de/lt/blog/sicher-loggen-php/).

## Klaidų apdorojimas ir PHP2Shell

PHP 8.5 php.net kaip vieną iš pereinamųjų priemonių nuo PHP2Shell nurodo nustatyti `fatal_error_backtraces=0` ir po to paleisti PHP iš naujo. Kitos priemonės liečia `memory_limit`, `post_max_size` ir `session.upload_progress.enabled`. Nė viena jų neatstoja atnaujinimo iki PHP 8.5.12, 8.4.27, 8.3.36 ar 8.2.35.

```ini
; tik PHP 8.5, php.ini arba FPM baseine, po to visus darbinius procesus (worker) paleisti iš naujo
fatal_error_backtraces = 0
```

Po PHP2Shell klaidų žurnalai taip pat yra svarbus įrodymas. Jei nuo 2026-10-20 dažnėja atminties klaidos ar nutrūkę darbiniai procesai, tai gali rodyti atakos bandymus, bet gali turėti ir nekenksmingų priežasčių. Išsaugokite žurnalus ir vertinkite juos kartu su prieigos žurnalais, kaip aprašyta vadove [Žiniatinklio serverio žurnalų vertinimas po atakos](https://phpsos.de/lt/ratgeber/logfiles-auswerten/).

## Kaip PHP SOS padeda konkrečiai?

- Tikriname veikiančius klaidų rodymo ir protokolavimo nustatymus kiekvienai svetainei ir nustatome juos tinkamai produkcijai.
- Pereinamąsias PHP2Shell priemones nustatome pagal PHP versiją ir paleidžiame iš naujo visus darbinius procesus (worker).
- Išsaugome klaidų ir prieigos žurnalus nuo 2026-10-20 ir vertiname juos dėl neįprastumų.
- Pagal [saugumo stebėsenos](https://phpsos.de/lt/leistungen/security-monitoring/) paslaugą pranešame apie neįprastą klaidų dažnėjimą, užuot leidę jam paskęsti žurnale.


## Dažni klausimai

**Kuo skiriasi Error ir Exception?**  
Abi įgyvendina sąsają Throwable. Exception skirta klaidoms, kurių programa tikisi ir jas apdoroja, pavyzdžiui, nepavykusiam prisijungimui prie duomenų bazės. Error žymi vidines PHP klaidas, tokias kaip TypeError ar dalyba iš nulio.

**Ar produkcijoje display_errors turi būti įjungtas?**  
Ne. Klaidų pranešimai naršyklėje atskleidžia kelius, užklausas, o kartais ir prieigos duomenis. Produkcijoje display_errors turi būti Off, o log_errors On, kad klaidos patektų į žurnalą, o ne pas lankytoją.

**Ar atminties perpildymą galima sugauti su try/catch?**  
Ne. Jei skriptas viršija memory_limit, PHP jį nutraukia su mirtina klaida, kurios negalima sugauti kaip išimties. Ji matoma tik klaidų žurnale, jei log_errors aktyvus.

**Kas yra fatal_error_backtraces?**  
Nustatymas, egzistuojantis nuo PHP 8.5.0, kuris prideda atgalinį sekimą (backtrace) prie mirtinų klaidų. Pagal numatymą jis aktyvus. Kaip pereinamąją priemonę nuo PHP2Shell php.net PHP 8.5 nurodo nustatyti jį į 0 ir po to paleisti PHP iš naujo.

## Šaltiniai

- [PHP vadovas: klaidų apdorojimo konfigūracija](https://www.php.net/manual/de/errorfunc.configuration.php)
- [PHP vadovas: klasė Error](https://www.php.net/manual/de/class.error.php)
- [PHP vadovas: ErrorException](https://www.php.net/manual/en/class.errorexception.php)
- [PHP vadovas: set_exception_handler](https://www.php.net/manual/en/function.set-exception-handler.php)
- [php.net: saugumo pranešimai](https://www.php.net/)
