Как грамотно обрабатывать ошибку 404 в PHP приложении
Обработка ошибки 404 в PHP
Как реализовать централизованную обработку ошибки 404 с помощью единой точки входа (front controller)?
Самое эффективное решение для современных PHP-приложений - использование единой точки входа (например, index.php) вместе с серверным перенаправлением всех запросов на этот файл (через .htaccess или конфигурацию Nginx). Внутри скрипта определяется маршрут, и если он не найден, отправляется код 404 и выводится кастомная страница. Этот подход даёт полный контроль над обработкой ошибок, позволяет логировать инциденты и избегать дублирования кода.
// index.php - единая точка входа
$requestUri = $_SERVER['REQUEST_URI'];
$routes = [
'/' => 'home',
'/about' => 'about',
'/contact' => 'contact'
];
if (isset($routes[$requestUri])) {
$page = $routes[$requestUri];
include __DIR__ . '/pages/' . $page . '.php';
} else {
http_response_code(404);
echo 'Страница не найдена
';
// Дополнительно: логирование
error_log('404: ' . $requestUri . ' - ' . date('Y-m-d H:i:s'), 3, __DIR__ . '/errors.log');
}
Проблемы, которые могут возникнуть:
- Если .htaccess не настроен правильно, запросы могут не перенаправляться на index.php. В таком случае сервер будет искать физический файл, и для несуществующих страниц вернёт стандартную 404 сервера, а не кастомную. Решение - проверить модуль mod_rewrite и правила в .htaccess.
- Неверный порядок проверки маршрутов - если регулярные выражения конфликтуют, маршрут может быть не найден. Рекомендуется сначала проверять статические маршруты, потом динамические.
- Отсутствие обработки исключений - если внутри подключаемого файла возникает ошибка, код 404 может не отправиться. Используйте try-catch.
Цель: обеспечить единообразную реакцию на отсутствующие страницы во всём приложении. Используется в проектах любой сложности, от простых сайтов до фреймворков.
Как отправить код 404 с помощью функции header() без маршрутизатора?
Можно использовать header('HTTP/1.0 404 Not Found') внутри скрипта, который вызывается напрямую. Это подходит для небольших сайтов, где каждая страница обрабатывается отдельным PHP-файлом.
// page.php
if (!isset($_GET['id']) || $_GET['id'] != 'valid') {
header('HTTP/1.0 404 Not Found');
echo 'Страница не найдена';
exit;
}
Цель: быстро добавить 404 на отдельные страницы. Случай: устаревшие проекты или простые скрипты без единой точки входа.
Как настроить кастомную страницу 404 через .htaccess без PHP-обработки?
Директива ErrorDocument в .htaccess позволяет задать статическую HTML-страницу или скрипт для определённых кодов ошибок.
# .htaccess
ErrorDocument 404 /errors/404.html
# или через PHP:
ErrorDocument 404 /errors/404.php
Цель: быстрое решение без написания кода, если нужна просто статическая страница. Случай: сайты на чистом HTML с небольшим количеством PHP-вставок.
Как обработать 404 при использовании try-catch и исключений?
В PHP можно выбросить исключение, если ресурс не найден, и перехватить его в центральном обработчике.
class NotFoundException extends Exception {}
try {
$id = $_GET['id'] ?? 0;
if ($id === 0) {
throw new NotFoundException('Ресурс не найден');
}
// ... остальная логика
} catch (NotFoundException $e) {
http_response_code(404);
echo '404: ' . $e->getMessage();
error_log($e->getMessage());
}
Цель: структурированная обработка ошибок в ООП-проектах. Случай: приложения с чёткой архитектурой, где ошибки - часть бизнес-логики.
Как использовать http_response_code() для отправки 404?
Современная альтернатива header() - функция http_response_code(), доступная с PHP 5.4.
http_response_code(404);
echo 'Страница не найдена';
Цель: простой способ изменить HTTP-статус. Случай: API-ответы, где надо вернуть JSON с кодом 404.
Расширенные примеры обработки ошибки 404 в PHP с пояснениями и результатами.
Пример 1: Простой маршрутизатор с регулярными выражениями
// index.php
$request = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$pattern = '~^/user/(\d+)$~';
if (preg_match($pattern, $request, $matches)) {
$userId = (int)$matches[1];
echo "Профиль пользователя ID: $userId";
} else {
http_response_code(404);
echo '404 - Такой страницы нет
';
echo 'Проверьте URL или вернитесь на главную.
';
}
Запрос: /user/42 → "Профиль пользователя ID: 42" Запрос: /unknown → "404 - Такой страницы нет" и статус 404
Пояснение: маршрутизация через регулярное выражение позволяет обрабатывать динамические пути. При отсутствии совпадения отправляется 404.
Пример 2: Кастомная страница 404 с использованием include
// index.php
$page = __DIR__ . '/pages' . $_SERVER['REQUEST_URI'] . '.php';
if (file_exists($page)) {
include $page;
} else {
http_response_code(404);
include __DIR__ . '/errors/404.php';
}
При обращении к /about (если /pages/about.php существует) - подключается about.php При обращении к /nonexistent - подключается errors/404.php, статус 404
Пояснение: простое сопоставление URI с файловой структурой. Недостаток - уязвимость к Path Traversal, если не фильтровать входные данные.
Пример 3: Логирование 404 в базу данных
// db_log.php
$pdo = new PDO('mysql:host=localhost;dbname=logs', 'user', 'pass');
$stmt = $pdo->prepare('INSERT INTO error_404 (url, ip, time) VALUES (?, ?, ?)');
$stmt->execute([$_SERVER['REQUEST_URI'], $_SERVER['REMOTE_ADDR'], date('Y-m-d H:i:s')]);
http_response_code(404);
echo 'Страница не найдена. Мы записали этот инцидент.';
Запись в БД: url='/broken', ip='192.168.1.1', time='2025-04-08 12:00:00' Пользователь видит сообщение и статус 404.
Пояснение: полезно для анализа популярных битых ссылок. Требует подключения к БД и правильной обработки исключений при записи.
Пример 4: Обработка 404 в REST API с JSON-ответом
// api.php
header('Content-Type: application/json');
$resource = $_GET['resource'] ?? '';
$validResources = ['users', 'posts'];
if (!in_array($resource, $validResources)) {
http_response_code(404);
echo json_encode([
'error' => 'Resource not found',
'code' => 404,
'message' => 'Запрашиваемый ресурс не существует.'
]);
exit;
}
// … обработка ресурса …
Запрос: /api.php?resource=invalid → HTTP/1.1 404 Not Found, тело:
{"error":"Resource not found","code":404,"message":"Запрашиваемый ресурс не существует."}
Пояснение: для API важно возвращать правильный код и структурированное тело ответа. Статус 404 гарантирует, что клиент (например, fetch) распознает ошибку.
Пример 5: Глобальный обработчик 404 через set_exception_handler
// main.php
function customExceptionHandler($exception) {
if ($exception instanceof \NotFoundException) {
http_response_code(404);
echo '404 - ' . $exception->getMessage() . '
';
} else {
http_response_code(500);
echo 'Внутренняя ошибка сервера';
}
}
set_exception_handler('customExceptionHandler');
// Пример
throw new \NotFoundException('Страница не найдена');
Выбрасывается NotFoundException → страница выводит статус 404 и сообщение "Страница не найдена"
Пояснение: глобальный обработчик упрощает код - не нужно везде писать try-catch. Подходит для фреймворков и приложений, где все исключения наследуются от базового класса.