Krótko: PHP zgłasza problemy na dwa sposoby: jako klasyczne błędy, takie jak ostrzeżenia i uwagi, sterowane przez error_reporting, oraz jako wyjątki, które można przechwycić za pomocą try i catch. Od PHP 7 wiele błędów wewnętrznych zgłasza też obiekt klasy Error; razem z Exception implementuje on interfejs Throwable. W produkcji obowiązuje zasada: błędy zapisywać w logu, nigdy nie wyświetlać w przeglądarce. W PHP 8.5 do środków przejściowych z powodu PHP2Shell należy ponadto fatal_error_backtraces=0.
Dwa światy: klasyczne błędy i wyjątki
PHP pochodzi z czasów, gdy błędy były wyświetlane przede wszystkim jako komunikaty: uwagi (E_NOTICE), ostrzeżenia (E_WARNING), przestarzałe funkcje (E_DEPRECATED) i błędy krytyczne (E_ERROR). Wiele z tych komunikatów pozwala skryptowi działać dalej, często z błędnymi lub pustymi danymi.
Wyjątki działają inaczej. Przerywają normalny przebieg i przeskakują do najbliższego pasującego bloku catch. Jeśli żadnego nie znajdą, skrypt się kończy. Dla osób podejmujących decyzje różnica jest ważna: ciche ostrzeżenie może sprawić, że zamówienie zostanie zapisane niekompletnie, a nikt tego nie zauważy. Wyjątek czyni problem widocznym i możliwym do obsłużenia.
Niedocenianą zaletą klasycznych komunikatów są uwagi typu E_DEPRECATED. Zapowiadają one, że jakiś środek językowy zniknie w przyszłej wersji PHP. Kto zbiera je w logu i regularnie analizuje, wie przed aktualizacją, które miejsca trzeba poprawić, i nie musi długo testować w razie pilnej luki bezpieczeństwa.
Hierarchia Throwable
Wszystko, co można rzucić za pomocą throw i złapać za pomocą catch, implementuje Throwable. Poniżej są dwie gałęzie:
| Klasa | Przeznaczenie | Przykłady podklas |
|---|---|---|
Exception |
przewidywalne błędy aplikacji i rozszerzeń | ErrorException, PDOException, własne klasy |
Error |
wewnętrzne błędy PHP | TypeError, ValueError, ArgumentCountError, ArithmeticError, DivisionByZeroError, UnhandledMatchError |
Własne klasy błędów dziedziczą po Exception, a nie po Error. Od PHP 8.0 większość funkcji wewnętrznych zgłasza przy nieprawidłowych parametrach TypeError lub ValueError, zamiast tylko wyświetlać ostrzeżenie. Przykładem jest strlen([]).
try, catch i finally w praktyce
<?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 zmienna w catch jest opcjonalna
$log->error('Zahlungsdienst nicht erreichbar', ['bestellung' => $b->id()]);
return false;
} finally {
$dienst->verbindungSchliessen();
}
}
Kilka klas można połączyć w jednym catch za pomocą |. Blok finally wykonuje się zawsze, niezależnie od tego, czy wystąpił błąd. Od PHP 8.0 throw jest ponadto wyrażeniem i może stać na przykład za ??: $id = $_GET['id'] ?? throw new InvalidArgumentException('id fehlt');.
Globalne handlery: set_error_handler i set_exception_handler
Dwie funkcje przechwytują to, co inaczej by się prześliznęło. set_exception_handler() rejestruje funkcję dla nieprzechwyconych obiektów Throwable. Według podręcznika skrypt kończy się po jej wywołaniu; nadaje się więc do zapisu w logu i neutralnej strony błędu, a nie do dalszej pracy.
Sprawdził się handler, który zapisuje do logu pełny błąd z czasem i losowym identyfikatorem, a odwiedzającemu pokazuje tylko neutralną stronę z tym identyfikatorem. Wsparcie techniczne może wtedy odnaleźć przypadek w logu, a wewnętrzne szczegóły nie wydostają się na zewnątrz.
set_error_handler() przechwytuje klasyczne błędy. Podręcznik pokazuje, jak zamienić je w ErrorException, aby ostrzeżenia były traktowane jak wyjątki:
<?php
set_error_handler(function (int $nr, string $text, string $datei, int $zeile): bool {
if (!(error_reporting() & $nr)) {
return false; // wykluczone przez error_reporting
}
if ($nr === E_DEPRECATED || $nr === E_USER_DEPRECATED) {
return false; // nie eskalować uwag o przestarzałych elementach
}
throw new ErrorException($text, 0, $nr, $datei, $zeile);
});
Błędów krytycznych, takich jak przepełnienie pamięci, nie można tą drogą przechwycić. Co dzieje się wewnątrz przy przepełnieniu, wyjaśnia strona memory_limit i Out-of-Memory.
Konfiguracja wyświetlania błędów i zapisu w logu
Najważniejsze ustawienia z wartościami domyślnymi według podręcznika:
| Ustawienie | Domyślnie | Zalecenie dla produkcji |
|---|---|---|
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 |
puste (log serwera WWW lub SAPI) | własny plik poza webrootem |
fatal_error_backtraces |
1, nowość w PHP 8.5.0 |
tymczasowo 0 (PHP2Shell) |
Od PHP 8.2.0 error_log_mode określa uprawnienia pliku logu, domyślnie 0644. Jeśli logi zawierają dane osobowe lub ścieżki, rozsądna jest ostrzejsza wartość, aby inni użytkownicy na serwerze nie mogli ich czytać. Za pomocą ignore_repeated_errors można łączyć identyczne komunikaty z tego samego pliku i wiersza, aby pętla błędów nie zalała logu.
Dołączony php.ini-production ustawia display_errors już na Off. Według podręcznika ini_set('display_errors', ...) w czasie działania nie wpływa na błędy krytyczne, a błędy składni występują, zanim jakiekolwiek ini_set() w ogóle się wykona. Dlatego te wartości należą do konfiguracji serwera. Tło opisuje artykuł Wyłączanie wyświetlania błędów w produkcji; jak prowadzić logi bez wycieku danych, wyjaśnia artykuł Bezpieczne logowanie.
Obsługa błędów a PHP2Shell
Dla PHP 8.5 php.net podaje jako jeden ze środków przejściowych przeciw PHP2Shell ustawienie fatal_error_backtraces=0 i późniejszy restart PHP. Pozostałe środki dotyczą memory_limit, post_max_size i session.upload_progress.enabled. Żaden z nich nie zastępuje aktualizacji do PHP 8.5.12, 8.4.27, 8.3.36 lub 8.2.35.
; tylko PHP 8.5, w php.ini lub puli FPM, potem zrestartować wszystkie workery
fatal_error_backtraces = 0
Logi błędów są po PHP2Shell ponadto ważnym materiałem dowodowym. Jeśli od 20.10.2026 przybywa błędów pamięci lub przerwanych workerów, może to wskazywać na próby ataku, ale może mieć też niegroźne przyczyny. Proszę zachować logi i analizować je razem z logami dostępu, jak opisano w poradniku Analiza logów serwera WWW po ataku.
Jak konkretnie pomaga PHP SOS?
- Sprawdzamy obowiązujące ustawienia wyświetlania błędów i zapisu w logach dla każdej strony i ustawiamy je odpowiednio do produkcji.
- Wprowadzamy środki przejściowe PHP2Shell odpowiednie do wersji PHP i restartujemy wszystkie workery.
- Zabezpieczamy logi błędów i dostępu od 20.10.2026 i analizujemy je pod kątem nieprawidłowości.
- W ramach monitoringu bezpieczeństwa zgłaszamy nietypowe nagromadzenia błędów, zamiast pozwalać im zniknąć w logu.
Częste pytania
Jaka jest różnica między Error a Exception?
Obie implementują interfejs Throwable. Exception przeznaczony jest dla błędów, które aplikacja przewiduje i obsługuje, na przykład nieudanego połączenia z bazą danych. Error oznacza wewnętrzne błędy PHP, takie jak TypeError lub dzielenie przez zero.
Czy display_errors powinno być w produkcji włączone?
Nie. Komunikaty o błędach w przeglądarce zdradzają ścieżki, zapytania, a czasem dane dostępowe. W produkcji display_errors powinno mieć wartość Off, a log_errors wartość On, aby błędy trafiały do logu, a nie do odwiedzającego.
Czy przepełnienie pamięci można przechwycić za pomocą try/catch?
Nie. Jeśli skrypt przekroczy memory_limit, PHP kończy go błędem krytycznym, którego nie można przechwycić jako wyjątku. Pojawia się on tylko w logu błędów, o ile log_errors jest aktywne.
Czym jest fatal_error_backtraces?
To ustawienie, które istnieje od PHP 8.5.0 i dołącza do błędów krytycznych backtrace. Jest domyślnie aktywne. Jako środek przejściowy przeciw PHP2Shell php.net podaje dla PHP 8.5 ustawienie go na 0 i późniejszy restart PHP.