23. PHP – Debugowanie i obsługa błędów


Debugowanie to proces znajdowania i naprawiania błędów w kodzie. To umiejętność, która odróżnia początkującego od doświadczonego programisty.

Rodzaje błędów w PHP

1. Parse Errors (Błędy składni)

Błędy w składni kodu – PHP nie może nawet uruchomić skryptu.

<?php
// Błąd: brak średnika
echo "Hello World"

// Błąd: brak zamykającego nawiasu
if ($x > 5 {
    echo "OK";
}

// Błąd: brak zamykającego cudzysłowu
echo "Hello;
?>

Komunikat błędu:

Parse error: syntax error, unexpected end of file in script.php on line 3

2. Fatal Errors (Błędy krytyczne)

Błędy krytyczne przerywające wykonanie skryptu.

<?php
// Wywołanie nieistniejącej funkcji
nieistniejacaFunkcja();

// Przekroczenie limitu pamięci
$huge_array = array_fill(0, 10000000, 'data');

// Błąd require/include
require 'nieistniejacy_plik.php';
?>

Komunikat błędu:

Fatal error: Uncaught Error: Call to undefined function nieistniejacaFunkcja()

3. Warning (Ostrzeżenia)

Ostrzeżenia nie przerywają wykonania skryptu, ale wskazują na problemy.

<?php
// Otwarcie nieistniejącego pliku
$file = fopen('nieistniejacy.txt', 'r');

// Dzielenie przez zero
$result = 10 / 0;

// Użycie niezdefiniowanej zmiennej
echo $niezdefiniowana;
?>

Komunikat błędu:

Warning: fopen(nieistniejacy.txt): failed to open stream: No such file or directory
Warning: Division by zero

4. Notice (Notki)

Najmniej poważne komunikaty – sugestie ulepszeń.

<?php
// Użycie niezdefiniowanego klucza tablicy
$tab = ['a' => 1];
echo $tab['b'];

// Użycie niezdefiniowanej zmiennej
echo $x;

// Użycie niezdefiniowanej stałej
echo NIEISTNIEJACA_STALA;
?>

Komunikat błędu:

Notice: Undefined index: b
Notice: Undefined variable: x
Notice: Use of undefined constant NIEISTNIEJACA_STALA

5. Deprecated (Przestarzałe)

Informacje o przestarzałych funkcjach.

<?php
// Przestarzała funkcja
mysql_connect('localhost', 'root', ''); // Deprecated w PHP 5.5, usunięte w PHP 7.0

// Przestarzały sposób tworzenia obiektu
$date = new DateTime;
$date->setDate(2024, 13, 32); // Deprecated warning
?>

1. error_reporting() – Poziomy błędów

Konfiguracja raportowania błędów

<?php
// Wyświetl WSZYSTKIE błędy (rozwój)
error_reporting(E_ALL);

// Wyświetl wszystkie oprócz Notice
error_reporting(E_ALL & ~E_NOTICE);

// Wyświetl tylko błędy krytyczne
error_reporting(E_ERROR | E_WARNING | E_PARSE);

// Wyłącz wyświetlanie błędów (produkcja)
error_reporting(0);

// Sprawdź aktualny poziom
echo error_reporting(); // Zwraca liczbę (np. 32767 dla E_ALL)
?>

Poziomy błędów – kompletna lista

<?php
// Błędy krytyczne
E_ERROR             // Błędy krytyczne (fatal errors)
E_PARSE             // Błędy składni
E_CORE_ERROR        // Błędy w jądrze PHP
E_COMPILE_ERROR     // Błędy kompilacji

// Ostrzeżenia
E_WARNING           // Ostrzeżenia runtime
E_CORE_WARNING      // Ostrzeżenia jądra PHP
E_COMPILE_WARNING   // Ostrzeżenia kompilacji
E_USER_WARNING      // Własne ostrzeżenia (trigger_error)

// Notki i informacje
E_NOTICE            // Notki runtime
E_USER_NOTICE       // Własne notki
E_STRICT            // Sugestie najlepszych praktyk
E_DEPRECATED        // Przestarzałe funkcje
E_USER_DEPRECATED   // Własne deprecation

// Wszystkie
E_ALL               // Wszystkie błędy i ostrzeżenia
?>

Przykład – Różne konfiguracje dla środowisk

<?php
// config.php

// Wykryj środowisko
$environment = getenv('APP_ENV') ?: 'development';

if ($environment === 'development') {
    // ROZWÓJ - pokaż wszystkie błędy
    error_reporting(E_ALL);
    ini_set('display_errors', 1);
    ini_set('display_startup_errors', 1);
    
} elseif ($environment === 'staging') {
    // STAGING - pokaż błędy, ale loguj je
    error_reporting(E_ALL);
    ini_set('display_errors', 1);
    ini_set('log_errors', 1);
    ini_set('error_log', '/var/log/php/staging_errors.log');
    
} else {
    // PRODUKCJA - ukryj błędy, tylko loguj
    error_reporting(E_ALL);
    ini_set('display_errors', 0);
    ini_set('display_startup_errors', 0);
    ini_set('log_errors', 1);
    ini_set('error_log', '/var/log/php/production_errors.log');
}
?>

2. ini_set() – Konfiguracja PHP w runtime

Podstawowe ustawienia

<?php
// Wyświetlanie błędów
ini_set('display_errors', 1);           // Wyświetl błędy (0 = wyłącz)
ini_set('display_startup_errors', 1);   // Błędy przy starcie PHP

// Logowanie błędów
ini_set('log_errors', 1);               // Loguj błędy do pliku
ini_set('error_log', 'errors.log');     // Ścieżka do pliku logów

// Limity
ini_set('memory_limit', '256M');        // Limit pamięci
ini_set('max_execution_time', 60);      // Czas wykonania (sekundy)
ini_set('upload_max_filesize', '10M');  // Max rozmiar uploadu

// Inne
ini_set('default_charset', 'UTF-8');
ini_set('date.timezone', 'Europe/Warsaw');

// Sprawdź wartość ustawienia
echo ini_get('memory_limit'); // 256M
?>

Przykład – Kompleksowa konfiguracja rozwojowa

<?php
// dev_config.php - Konfiguracja dla rozwoju

// Błędy
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);

// Logowanie
ini_set('log_errors', 1);
ini_set('error_log', __DIR__ . '/logs/php_errors.log');

// Limity (luźne dla rozwoju)
ini_set('memory_limit', '512M');
ini_set('max_execution_time', 300);
ini_set('max_input_time', 300);
ini_set('post_max_size', '50M');
ini_set('upload_max_filesize', '50M');

// Sesje
ini_set('session.cookie_httponly', 1);
ini_set('session.use_strict_mode', 1);

// Output buffering (przydatne przy debugowaniu)
ini_set('output_buffering', 'Off');
ini_set('implicit_flush', 1);

// Pokaż szczegóły SQL (tylko dla developmentu!)
ini_set('mysqli.allow_local_infile', 1);

echo "Konfiguracja deweloperska załadowana<br>";
echo "Memory limit: " . ini_get('memory_limit') . "<br>";
echo "Max execution time: " . ini_get('max_execution_time') . "s<br>";
?>

3. trigger_error() – Własne błędy

Wywoływanie własnych błędów

<?php
function divide($a, $b) {
    
    if ($b == 0) {
        // Wywołaj własny błąd
        trigger_error("Dzielenie przez zero!", E_USER_WARNING);
        return false;
    }
    
    return $a / $b;
}

$result = divide(10, 0);

// Bardziej szczegółowe błędy
function processUser($user_id) {
    
    if (!is_int($user_id)) {
        trigger_error("User ID musi być liczbą całkowitą, podano: " . gettype($user_id), E_USER_ERROR);
    }
    
    if ($user_id < 1) {
        trigger_error("User ID musi być dodatni, podano: $user_id", E_USER_WARNING);
        return false;
    }
    
    // Przestarzałe użycie
    if (func_num_args() > 1) {
        trigger_error("Funkcja processUser() przyjmuje tylko jeden argument. Wiele argumentów jest przestarzałe.", E_USER_DEPRECATED);
    }
    
    return "Processing user $user_id";
}

processUser(-5);        // Warning
processUser("abc");     // Fatal Error
processUser(1, 2);      // Deprecated
?>

Przykład – Walidacja z trigger_error()

<?php
class Validator {
    
    public static function validateEmail($email) {
        if (empty($email)) {
            trigger_error("Email nie może być pusty", E_USER_NOTICE);
            return false;
        }
        
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            trigger_error("Nieprawidłowy format email: $email", E_USER_WARNING);
            return false;
        }
        
        return true;
    }
    
    public static function validateAge($age) {
        if (!is_numeric($age)) {
            trigger_error("Wiek musi być liczbą, podano: " . gettype($age), E_USER_ERROR);
        }
        
        if ($age < 0) {
            trigger_error("Wiek nie może być ujemny: $age", E_USER_WARNING);
            return false;
        }
        
        if ($age > 150) {
            trigger_error("Nieprawdopodobny wiek: $age", E_USER_WARNING);
            return false;
        }
        
        return true;
    }
}

// Testy
Validator::validateEmail('');                    // Notice
Validator::validateEmail('invalid-email');       // Warning
Validator::validateAge('abc');                   // Fatal Error
Validator::validateAge(-5);                      // Warning
Validator::validateAge(200);                     // Warning
?>

4. error_log() – Logowanie błędów

Podstawowe użycie

<?php
// Loguj do domyślnego pliku error_log
error_log("To jest testowa wiadomość w logu");

// Loguj zmienną
$user_id = 123;
error_log("User ID: $user_id");

// Loguj tablicę (trzeba skonwertować do stringa)
$data = ['name' => 'Jan', 'age' => 25];
error_log("User data: " . print_r($data, true));

// Loguj z kontekstem
error_log("[" . date('Y-m-d H:i:s') . "] User logged in: $user_id");
?>

Logowanie do konkretnego pliku

<?php
// Loguj do konkretnego pliku
error_log("Custom log message", 3, "/var/log/myapp/custom.log");

// Funkcja pomocnicza do logowania
function logMessage($message, $level = 'INFO') {
    $timestamp = date('Y-m-d H:i:s');
    $log_file = __DIR__ . '/logs/app.log';
    
    $formatted_message = "[$timestamp] [$level] $message" . PHP_EOL;
    
    error_log($formatted_message, 3, $log_file);
}

// Użycie
logMessage("Aplikacja uruchomiona", "INFO");
logMessage("Użytkownik zalogowany: ID 123", "INFO");
logMessage("Błąd połączenia z bazą", "ERROR");
logMessage("Podejrzana aktywność z IP: 192.168.1.100", "WARNING");
?>

Przykład – Klasa Logger

<?php
class Logger {
    
    private $log_file;
    private $levels = ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'];
    
    public function __construct($log_file = null) {
        $this->log_file = $log_file ?? __DIR__ . '/logs/app.log';
        
        // Utwórz katalog jeśli nie istnieje
        $log_dir = dirname($this->log_file);
        if (!is_dir($log_dir)) {
            mkdir($log_dir, 0755, true);
        }
    }
    
    private function write($level, $message, $context = []) {
        $timestamp = date('Y-m-d H:i:s');
        
        // Format: [2024-01-15 14:30:45] [ERROR] Message
        $log_line = "[$timestamp] [$level] $message";
        
        // Dodaj kontekst jeśli istnieje
        if (!empty($context)) {
            $log_line .= " | Context: " . json_encode($context);
        }
        
        $log_line .= PHP_EOL;
        
        // Zapisz do pliku
        error_log($log_line, 3, $this->log_file);
    }
    
    public function debug($message, $context = []) {
        $this->write('DEBUG', $message, $context);
    }
    
    public function info($message, $context = []) {
        $this->write('INFO', $message, $context);
    }
    
    public function warning($message, $context = []) {
        $this->write('WARNING', $message, $context);
    }
    
    public function error($message, $context = []) {
        $this->write('ERROR', $message, $context);
    }
    
    public function critical($message, $context = []) {
        $this->write('CRITICAL', $message, $context);
    }
    
    public function exception(Exception $e) {
        $context = [
            'file' => $e->getFile(),
            'line' => $e->getLine(),
            'trace' => $e->getTraceAsString()
        ];
        
        $this->error($e->getMessage(), $context);
    }
}

// Użycie
$logger = new Logger();

$logger->info("Aplikacja uruchomiona");
$logger->debug("Zmienna \$x = 5", ['x' => 5]);
$logger->warning("Niska pamięć", ['available' => '50MB']);
$logger->error("Błąd połączenia z bazą", ['host' => 'localhost', 'error' => 'timeout']);

try {
    throw new Exception("Testowy wyjątek");
} catch (Exception $e) {
    $logger->exception($e);
}
?>

5. var_dump(), print_r(), debug_backtrace()

var_dump() – Pełne informacje o zmiennej

<?php
$string = "Hello World";
$number = 42;
$float = 3.14;
$bool = true;
$null = null;
$array = ['a' => 1, 'b' => 2, 'c' => 3];
$object = new stdClass();
$object->name = "Test";
$object->value = 100;

// var_dump() pokazuje typ i wartość
var_dump($string);      // string(11) "Hello World"
var_dump($number);      // int(42)
var_dump($float);       // float(3.14)
var_dump($bool);        // bool(true)
var_dump($null);        // NULL
var_dump($array);       // array(3) { ["a"]=> int(1) ...}
var_dump($object);      // object(stdClass)#1 (2) { ...}

// Wiele zmiennych naraz
var_dump($string, $number, $array);
?>

print_r() – Czytelniejszy output

<?php
$array = [
    'user' => [
        'id' => 123,
        'name' => 'Jan Kowalski',
        'email' => 'jan@example.com',
        'roles' => ['admin', 'editor']
    ],
    'settings' => [
        'theme' => 'dark',
        'language' => 'pl'
    ]
];

// print_r() - czytelniejsze formatowanie
print_r($array);

// Zwróć jako string zamiast wyświetlać
$output = print_r($array, true);
error_log("Array content: $output");

// Porównanie
echo "<pre>";
echo "var_dump():\n";
var_dump($array);

echo "\nprint_r():\n";
print_r($array);
echo "</pre>";
?>

debug_backtrace() – Śledzenie wywołań

<?php
function levelOne() {
    levelTwo();
}

function levelTwo() {
    levelThree();
}

function levelThree() {
    // Pokaż stos wywołań
    $backtrace = debug_backtrace();
    
    echo "<pre>";
    print_r($backtrace);
    echo "</pre>";
    
    // Bardziej czytelne
    echo "<h3>Call Stack:</h3>";
    foreach ($backtrace as $index => $call) {
        echo "#{$index} ";
        
        if (isset($call['file'])) {
            echo "{$call['file']}:{$call['line']} - ";
        }
        
        if (isset($call['class'])) {
            echo "{$call['class']}{$call['type']}";
        }
        
        echo "{$call['function']}()<br>";
    }
}

levelOne();
?>

Funkcja debug() – Ulepszony var_dump()

<?php
function debug($var, $label = null, $die = false) {
    echo '<pre style="background:#f4f4f4; padding:15px; border:2px solid #333; border-radius:5px; margin:10px 0;">';
    
    if ($label) {
        echo "<strong>DEBUG: $label</strong>\n";
        echo str_repeat('-', 50) . "\n";
    }
    
    // Informacje o wywołaniu
    $backtrace = debug_backtrace();
    $caller = $backtrace[0];
    echo "File: {$caller['file']}\n";
    echo "Line: {$caller['line']}\n";
    echo str_repeat('-', 50) . "\n\n";
    
    // Typ zmiennej
    echo "Type: " . gettype($var) . "\n\n";
    
    // Wartość
    if (is_array($var) || is_object($var)) {
        print_r($var);
    } else {
        var_dump($var);
    }
    
    echo '</pre>';
    
    if ($die) {
        die("\n<strong>Script terminated by debug()</strong>");
    }
}

// Użycie
$user = [
    'id' => 123,
    'name' => 'Jan Kowalski',
    'active' => true
];

debug($user, "User Data");
debug($_SERVER, "Server Variables");
debug($user, "Critical Error - User Data", true); // Zatrzyma skrypt
?>

6. Try-Catch – Obsługa wyjątków

Materiał dostępny w temacie https://kamakaczmarek.net/12-php-wyjatki-try-catch/

7. Xdebug – Profesjonalne debugowanie

Xdebug to popularne rozszerzenie (moduł) dla języka PHP, które służy do debugowania (szukania błędów), profilowania oraz analizy wydajności aplikacji.

Zamiast polegać na tradycyjnym, często uciążliwym wypisywaniu wartości zmiennych w oknie przeglądarki za pomocą funkcji var_dump() czy print_r(),

Xdebug pozwala na zaawansowaną, interaktywną pracę z kodem.

W tym miejscu odsyłam Was do zewnętrznych źródeł celem zgłębienia tematu – jest ich mnóstwo w sieci.