Na kratko: PHP težave sporoča na dva načina: kot klasične napake, na primer opozorila in namige, ki jih nadzira error_reporting, in kot izjeme, ki jih je mogoče ujeti z try in catch. Od PHP 7 tudi številne notranje napake sprožijo objekt razreda Error; skupaj z Exception implementira vmesnik Throwable. V produkciji velja: napake beležiti, jih nikoli ne prikazovati v brskalniku. Na PHP 8.5 med prehodne ukrepe zaradi PHP2Shell dodatno sodi fatal_error_backtraces=0.
Dva sveta: klasične napake in izjeme
PHP izvira iz časa, ko so se napake izpisovale predvsem kot sporočila: namigi (E_NOTICE), opozorila (E_WARNING), zastarele funkcije (E_DEPRECATED) in smrtne napake (E_ERROR). Številna od teh sporočil skripti dovolijo, da teče naprej, pogosto z napačnimi ali praznimi podatki.
Izjeme delujejo drugače. Prekinejo običajni potek in skočijo na naslednji ustrezni blok catch. Če ga ni, se skripta konča. Za odločevalce je razlika pomembna: tiho opozorilo lahko povzroči, da se naročilo shrani nepopolno, ne da bi kdo opazil. Izjema težavo naredi vidno in obravnavno.
Premalo cenjena korist klasičnih sporočil so namigi tipa E_DEPRECATED. Napovedujejo, da bo neko jezikovno sredstvo v prihodnji različici PHP izpadlo. Kdor jih zbira v dnevniku in redno ovrednoti, pred nadgradnjo ve, katera mesta je treba prilagoditi, in pri nujni varnostni vrzeli ni treba šele dolgo testirati.
Hierarhija Throwable
Vse, kar je mogoče vreči z throw in ujeti s catch, implementira Throwable. Pod njim sta dve veji:
| Razred | Namen | Primeri podrazredov |
|---|---|---|
Exception |
pričakovane napake aplikacije in razširitev | ErrorException, PDOException, lastni razredi |
Error |
notranje napake PHP | TypeError, ValueError, ArgumentCountError, ArithmeticError, DivisionByZeroError, UnhandledMatchError |
Lastni razredi napak izhajajo iz Exception, ne iz Error. Od PHP 8.0 večina notranjih funkcij pri neveljavnih parametrih sproži TypeError ali ValueError, namesto da bi samo izpisala opozorilo. strlen([]) je en tak primer.
try, catch in finally v praksi
<?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) {
// od PHP 8.0 je spremenljivka v bloku catch neobvezna
$log->error('Zahlungsdienst nicht erreichbar', ['bestellung' => $b->id()]);
return false;
} finally {
$dienst->verbindungSchliessen();
}
}
Več razredov je mogoče z | združiti v enem bloku catch. Blok finally se izvede vedno, ne glede na to, ali je prišlo do napake. Od PHP 8.0 je throw tudi izraz in lahko stoji na primer za ??: $id = $_GET['id'] ?? throw new InvalidArgumentException('id fehlt');.
Globalni handlerji: set_error_handler in set_exception_handler
Dve funkciji ujameta to, kar bi sicer ušlo. set_exception_handler() registrira funkcijo za neujete Throwable. Po priročniku se skripta konča, ko je funkcija poklicana; primerna je torej za beleženje in nevtralno stran z napako, ne za nadaljnje delo.
Obnesel se je handler, ki popolno napako z uro in naključno oznako zapiše v dnevnik, obiskovalcu pa pokaže samo nevtralno stran s to oznako. Podpora lahko primer nato najde v dnevniku, ne da bi notranje podrobnosti prišle navzven.
set_error_handler() ujame klasične napake. Priročnik pokaže, kako jih pretvoriti v ErrorException, da se opozorila obravnavajo kot izjeme:
<?php
set_error_handler(function (int $nr, string $text, string $datei, int $zeile): bool {
if (!(error_reporting() & $nr)) {
return false; // izključeno z error_reporting
}
if ($nr === E_DEPRECATED || $nr === E_USER_DEPRECATED) {
return false; // namigov na zastarelo ne stopnjevati
}
throw new ErrorException($text, 0, $nr, $datei, $zeile);
});
Smrtnih napak, kot je prekoračitev pomnilnika, na ta način ni mogoče ujeti. Kaj se ob prekoračitvi dogaja v ozadju, razlaga stran memory_limit in Out-of-Memory.
Konfiguracija izpisa napak in beleženja
Najpomembnejše nastavitve s privzetimi vrednostmi po priročniku:
| Nastavitev | Privzeto | Priporočilo za produkcijo |
|---|---|---|
error_reporting |
E_ALL od PHP 8.0.0 |
E_ALL |
display_errors |
1 |
Off |
display_startup_errors |
1 od PHP 8.0.0 |
Off |
log_errors |
0 |
On |
error_log |
prazno (dnevnik spletnega strežnika oz. SAPI) | lastna datoteka zunaj korena spleta (webroot) |
fatal_error_backtraces |
1, novo v PHP 8.5.0 |
začasno 0 (PHP2Shell) |
Od PHP 8.2.0 error_log_mode določa pravice dostopa do datoteke dnevnika, privzeto 0644. Če dnevniki vsebujejo osebne podatke ali poti, je smiselna strožja vrednost, da jih drugi uporabniki na strežniku ne morejo brati. Z ignore_repeated_errors je mogoče enaka sporočila iz iste datoteke in vrstice strniti, da zanka napak dnevnika ne preplavi.
Priložena php.ini-production display_errors že nastavi na Off. Po priročniku ini_set('display_errors', ...) med izvajanjem ne vpliva na smrtne napake, sintaksne napake pa nastopijo, še preden se ini_set() sploh izvede. Te vrednosti zato sodijo v konfiguracijo strežnika. Ozadje je v prispevku Izklop izpisa napak v produkciji; kako voditi dnevnike brez uhajanja podatkov, pa v prispevku Varno beleženje.
Obravnava napak in PHP2Shell
Za PHP 8.5 php.net kot enega od prehodnih ukrepov proti PHP2Shell navaja nastavitev fatal_error_backtraces=0 in nato ponovni zagon PHP. Drugi ukrepi zadevajo memory_limit, post_max_size in session.upload_progress.enabled. Noben od njih ne nadomesti posodobitve na PHP 8.5.12, 8.4.27, 8.3.36 ali 8.2.35.
; samo PHP 8.5, v php.ini ali poolu FPM, nato znova zaženite vse workerje
fatal_error_backtraces = 0
Dnevniki napak so po PHP2Shell poleg tega pomembno dokazno gradivo. Če se od 20. 10. 2026 kopičijo napake pomnilnika ali prekinjeni workerji, to lahko kaže na poskuse napadov, lahko pa ima tudi neškodljive vzroke. Dnevnike shranite in jih ovrednotite skupaj z dnevniki dostopov, kot je opisano v vodniku Analiza dnevnikov spletnega strežnika po napadu.
Kako PHP SOS pomaga konkretno?
- Preverimo veljavne nastavitve za izpis napak in beleženje pri vsaki spletni strani in jih nastavimo za produkcijo.
- Prehodne ukrepe PHP2Shell nastavimo glede na različico PHP in znova zaženemo vse workerje.
- Dnevnike napak in dostopov od 20. 10. 2026 zavarujemo in jih pregledamo glede nepravilnosti.
- V okviru varnostnega nadzora javimo nenavadna kopičenja napak, namesto da bi se izgubila v dnevniku.
Pogosta vprašanja
Kakšna je razlika med Error in Exception?
Oba implementirata vmesnik Throwable. Exception je namenjen napakam, ki jih aplikacija pričakuje in obravnava, na primer neuspeli povezavi s podatkovno bazo. Error pomeni notranje napake PHP, kot sta TypeError ali deljenje z ničlo.
Ali naj bo display_errors v produkciji vklopljen?
Ne. Sporočila o napakah v brskalniku izdajo poti, poizvedbe in včasih dostopne podatke. V produkciji mora biti display_errors nastavljen na Off, log_errors pa na On, da napake pristanejo v dnevniku in ne pri obiskovalcu.
Ali je prekoračitev pomnilnika mogoče ujeti s try/catch?
Ne. Če skripta preseže memory_limit, jo PHP konča s smrtno napako, ki je ni mogoče ujeti kot izjemo. Pojavi se samo v dnevniku napak, če je log_errors aktiven.
Kaj je fatal_error_backtraces?
Nastavitev, ki obstaja od PHP 8.5.0 in smrtnim napakam doda sled klicev (backtrace). Privzeto je aktivna. Kot prehodni ukrep proti PHP2Shell php.net za PHP 8.5 navaja, da jo nastavite na 0 in nato znova zaženete PHP.