PHP: Error Handling and Debugging

After 32 lessons of writing code, you've undoubtedly stumbled into your share of pitfalls—blank white pages, inscrutable error messages, and not knowing how to use try/catch. This lesson ties all debugging skills together, upgrading you from "finding bugs by luck" to "systematic troubleshooting."

1. Error Levels

PHP has three types of errors:

TEXT 📖 参照専用
Notice      ← "You may have overlooked something" (undefined variable, offset)
Warning     ← "The code has a problem but can continue" (include file not found)
Fatal Error ← "The code has a serious error and must stop" (calling a non-existent function)
PHP
<?php
// Notice: undefined variable (PHP continues execution)
echo $undefinedVar;  // Notice: Undefined variable $undefinedVar

// Warning: file not found (PHP continues execution)
include 'nonexistent.php';  // Warning: Failed opening...

// Fatal Error: calling a non-existent function (PHP stops)
thisFunctionDoesNotExist();  // Fatal Error: Call to undefined function
?>

2. Controlling Error Reporting

PHP
<?php
// Development: show all errors
error_reporting(E_ALL);
ini_set('display_errors', '1');

// Production: log but don't display
error_reporting(E_ALL);
ini_set('display_errors', '0');
ini_set('log_errors', '1');
ini_set('error_log', __DIR__ . '/php-errors.log');
?>
🔥 Common Mistake: Never set display_errors = On on a production server—error messages can expose file paths, database passwords, and other sensitive information. Users should see a generic 500 error page; the error details go to the log.

▶ サンプル: Environment-Aware Configuration

PHP
<?php
$isDev = ($_SERVER['SERVER_NAME'] ?? '') === 'localhost';

if ($isDev) {
    error_reporting(E_ALL);
    ini_set('display_errors', '1');
} else {
    error_reporting(E_ALL);
    ini_set('display_errors', '0');
    ini_set('log_errors', '1');
    ini_set('error_log', '/var/log/php/errors.log');
}
?>
▶ 試してみよう

3. Exception Handling: try/catch/finally

PHP
<?php
function divide(float $a, float $b): float {
    if ($b === 0.0) {
        throw new Exception("Division by zero is not allowed!");
    }
    return $a / $b;
}

try {
    echo divide(10, 2) . "<br>";  // 5
    echo divide(10, 0) . "<br>";  // Throws an exception
} catch (Exception $e) {
    echo "[Caught Exception] {$e->getMessage()}<br>";
    echo "File: {$e->getFile()}<br>";
    echo "Line: {$e->getLine()}<br>";
} finally {
    echo "Whether success or failure, finally always runs (e.g., close files, release resources)<br>";
}
?>
Block When It Runs
try Execution starts here—exceptions are monitored
catch Runs if an exception is thrown in the try block
finally Always runs regardless (commonly used for cleanup)

4. Custom Exceptions

PHP
<?php
class ValidationException extends Exception {
    private array $errors;
    
    public function __construct(array $errors, string $message = "Validation failed") {
        parent::__construct($message);
        $this->errors = $errors;
    }
    
    public function getErrors(): array {
        return $this->errors;
    }
}

class UserNotFoundException extends Exception {}

// Using custom exceptions
function findUser(int $id): array {
    $users = [1 => ['name' => 'John'], 2 => ['name' => 'Jane']];
    
    if (!isset($users[$id])) {
        throw new UserNotFoundException("User ID {$id} does not exist");
    }
    return $users[$id];
}

function validateUsername(string $name): void {
    $errors = [];
    if ($name === '') $errors[] = 'Username cannot be empty';
    if (strlen($name) > 50) $errors[] = 'Username is too long';
    if (!empty($errors)) throw new ValidationException($errors);
}

// Unified exception handling
try {
    validateUsername('');
    $user = findUser(99);
} catch (ValidationException $e) {
    echo "[Validation Failed]<br>" . implode("<br>", $e->getErrors());
} catch (UserNotFoundException $e) {
    echo "[User Not Found] {$e->getMessage()}";
} catch (Exception $e) {
    echo "[Unknown Error] {$e->getMessage()}";
}
?>

5. Logging

PHP
<?php
class AppLogger {
    public function __construct(
        private string $logDir
    ) {
        if (!is_dir($logDir)) {
            mkdir($logDir, 0755, true);
        }
    }
    
    public function info(string $message, array $context = []): void {
        $this->log('INFO', $message, $context);
    }
    
    public function error(string $message, array $context = []): void {
        $this->log('ERROR', $message, $context);
    }
    
    private function log(string $level, string $message, array $context): void {
        $date = date('Y-m-d');
        $time = date('H:i:s');
        
        $line = "[{$time}] [{$level}] {$message}";
        if (!empty($context)) {
            $line .= ' ' . json_encode($context, JSON_UNESCAPED_UNICODE);
        }
        $line .= "\n";
        
        $file = "{$this->logDir}/app-{$date}.log";
        file_put_contents($file, $line, FILE_APPEND | LOCK_EX);
    }
}

$log = new AppLogger(__DIR__ . '/logs');
$log->info('User logged in', ['username' => 'John', 'ip' => $_SERVER['REMOTE_ADDR'] ?? '']);
$log->error('Payment failed', ['order_id' => 12345, 'reason' => 'Insufficient balance']);
?>

6. Debugging Techniques

▶ サンプル: Debugging Utility Functions

PHP
<?php
// Most commonly used debug output
$data = ['name' => 'John', 'skills' => ['PHP', 'MySQL']];

var_dump($data);  // Detailed output (type + value)
// array(2) { ["name"]=> string(4) "John" ["skills"]=> ... }

print_r($data);   // Concise output
// Array ( [name] => John [skills] => Array ( [0] => PHP [1] => MySQL ) )

// Formatted var_dump (recommended)
echo '<pre>'; print_r($data); echo '</pre>';

// Debug and terminate
var_dump($data); exit;  // or die(var_dump($data));
?>
▶ 試してみよう

(1) Quick Debugging Reference

PHP
<?php
// Value + type
var_dump($variable);

// Only show in development
if ($isDev) {
    echo '<pre>'; var_dump($data); echo '</pre>';
}

// Log it
error_log("Debug info: " . print_r($data, true));

// Check if execution reaches a certain line
echo "HERE"; exit;  // or error_log("Reached this line");

// View the call stack
debug_print_backtrace();

// Check if a variable is set
var_dump(isset($var), empty($var));
?>

7. Output Buffering and Custom Error Handlers

PHP
<?php
// Output buffering: capture echo/print output
ob_start();
echo "This text won't be sent directly to the browser.";
$content = ob_get_clean();  // Get and clear the buffer
// $content is now "This text won't be sent directly to the browser."

// Custom error handler
function myErrorHandler(int $errno, string $errstr, string $errfile, int $errline): bool {
    $message = "[{$errno}] {$errstr} in {$errfile}:{$errline}";
    error_log($message);
    
    if (ini_get('display_errors')) {
        echo "<div style='background:#ffebee;padding:10px;margin:10px'>
            <strong>PHP Error:</strong> {$message}
        </div>";
    }
    return true;  // Prevent PHP's default handling
}
set_error_handler('myErrorHandler');

// Test the custom error handler
echo $undefined;  // Now displays in a nice HTML box
?>

❓ よくある質問

Q How do I debug a blank white page?
A The most common cause is a Fatal Error with display_errors = Off. Troubleshooting steps: (1) add error_reporting(E_ALL); ini_set('display_errors', '1');; (2) check the PHP error log; (3) insert echo "1"; exit; at intervals to find which line stops execution.
Q What is Xdebug? Do I need it?
A Xdebug is a professional PHP debugging extension—set breakpoints, step through code, inspect variables. As a beginner, var_dump is enough. When your project exceeds 1000 lines, it's worth installing. The VS Code + Xdebug combo is as comfortable as browser DevTools.

📖 まとめ

📝 練習問題

  1. Write a division calculator page that uses try/catch to handle division by zero and display a friendly message.
  2. Create an AppException base class with ValidationException and DatabaseException subclasses. Use them in user registration logic and catch them separately.
  3. Add logging to exercise 2: when exceptions are caught, write error details (timestamp, file, line number) to a log file.
Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%