Как грамотно отвечать на несуществующие страницы: обработка 404 в PHP

Раздел: Обработка ошибок PHP -> Диагностика ошибок HTTP и функций

Методы обработки ошибки 404 в PHP

Как реализовать корректный HTTP-ответ 404 и показать пользователю понятную страницу?

Основной способ - явно установить HTTP-статус 404 и вывести содержимое страницы ошибки. Это гарантирует, что клиент (браузер, поисковый робот) получит правильный код ответа.


<?php
// main.php - точка входа
$page = $_GET['page'] ?? 'home';
$allowedPages = ['home', 'about', 'contact'];

if (!in_array($page, $allowedPages)) {
    http_response_code(404);
    // или header('HTTP/1.0 404 Not Found');
    echo '<h1>Страница не найдена</h1>';
    exit;
}
echo '<h1>Добро пожаловать на страницу ' . htmlspecialchars($page) . '</h1>';
?>
  

404 ошибки php (обработка ошибки 404 в php)

Пояснение:

  • http_response_code(404) - устанавливает код ответа (PHP >= 5.4).
  • После установки кода следует прекратить выполнение сценария (exit), чтобы не выводить лишнюю информацию.

Типичная ошибка:

Если до вызова http_response_code() уже был выведен какой-то текст (например, пробел или BOM), заголовки не будут отправлены. Решение - проверить отсутствие вывода перед отправкой заголовков или использовать буферизацию (ob_start()).

Цель: простой и предсказуемый способ для небольших проектов без фреймворков.


Варианты реализации

Как перенаправить все 404 ошибки на единый обработчик через .htaccess?

Метод подходит для серверов Apache. Создаётся файл .htaccess в корне сайта:


ErrorDocument 404 /404.php
  

В файле 404.php:


<?php
http_response_code(404);
echo '<h1>Страница не найдена (из .htaccess)</h1>';
?>
  

Проблема:

Если файл 404.php сам не существует, сервер покажет пустую страницу или другую ошибку. Решение - всегда проверять существование файла и иметь запасной вариант в конфигурации сервера.

Цель: централизованная обработка 404 для статических файлов и отсутствующих маршрутов без изменения PHP-кода.

Как обработать 404 с помощью исключений и кастомного обработчика?

Создаётся класс исключения NotFoundException, который выбрасывается в коде при отсутствии ресурса. В глобальном обработчике перехватывается и устанавливается 404.


<?php
class NotFoundException extends Exception {}

function exception_handler($exception) {
    if ($exception instanceof NotFoundException) {
        http_response_code(404);
        echo '<h1>404 - ' . $exception->getMessage() . '</h1>';
    } else {
        http_response_code(500);
        echo '<h1>Внутренняя ошибка сервера</h1>';
    }
}

set_exception_handler('exception_handler');

// Пример использования
$id = $_GET['id'] ?? null;
if (!$id) {
    throw new NotFoundException('Не указан ID');
}
echo 'ID: ' . htmlspecialchars($id);
?>
  

Типичная ошибка:

Если исключение выброшено внутри блока try/catch, глобальный обработчик не сработает. Решение - не перехватывать NotFoundException локально или пробрасывать его дальше.

Цель: интеграция обработки ошибок с механизмом исключений, удобно для ООП-приложений.

Как использовать пользовательскую функцию ошибок для генерации 404?

PHP-функция set_error_handler() не обрабатывает ошибки уровня E_USER_ERROR, E_USER_WARNING и E_USER_NOTICE. Можно эмулировать 404 с помощью триггера:


<?php
function customError($errno, $errstr) {
    if ($errno == E_USER_ERROR && strpos($errstr, '404') !== false) {
        http_response_code(404);
        echo '<h1>Пользовательская 404 ошибка</h1>';
        exit;
    }
}

set_error_handler('customError');

trigger_error('404: Страница не найдена', E_USER_ERROR);
?>
  

Проблема:

Такой подход не даёт правильный HTTP-статус до вызова обработчика, если уже был вывод. Решение - использовать буферизацию вывода (ob_start()) и очищать буфер перед отправкой заголовков.

Цель: для проектов, где уже используется свой обработчик ошибок, можно добавить логику 404.

Как в современных фреймворках (Laravel, Symfony) настраивается обработка 404?

В Laravel достаточно создать представление resources/views/errors/404.blade.php. В Symfony файл шаблона templates/bundles/TwigBundle/Exception/error404.html.twig. Код ответа устанавливается автоматически.


<!-- Laravel resources/views/errors/404.blade.php -->
@extends('layouts.app')

@section('content')
    <h1>404 - Страница не найдена</h1>
    <p>Запрашиваемая страница отсутствует.</p>
@endsection
  

Если требуется кастомная логика (например, логирование), в Laravel используется App\Exceptions\Handler:


public function render($request, Throwable $exception)
{
    if ($exception instanceof NotFoundHttpException) {
        Log::warning('404 на URL: ' . $request->fullUrl());
    }
    return parent::render($request, $exception);
}
  

Цель: использование готового механизма фреймворка для единообразной обработки ошибок.

Расширенные примеры обработки 404

Пример 1: Проверка существования файла и возврат 404

Сценарий: пользователь запрашивает файл изображения. Если файла нет, отдаётся 404 и подменное изображение.

Пример

<?php
$file = $_GET['file'] ?? null;
if (!$file || !file_exists(__DIR__ . '/images/' . basename($file))) {
    http_response_code(404);
    // отдаём заглушку
    header('Content-Type: image/png');
    readfile(__DIR__ . '/images/notfound.png');
    exit;
}
header('Content-Type: image/png');
readfile(__DIR__ . '/images/' . basename($file));
?>
Результат: при отсутствии файла браузер получает HTTP 404 и изображение-заглушку. Важно: заголовок 404 отправляется, но тело ответа содержит нестандартный контент - допустимо.

Пример 2: Обработка 404 в REST API с JSON-ответом

Для API вместо HTML возвращается JSON с кодом ошибки.

Пример

<?php
function json404($message = 'Not Found') {
    http_response_code(404);
    header('Content-Type: application/json');
    echo json_encode(['error' => $message, 'code' => 404]);
    exit;
}

// Маршрутизация
$route = $_SERVER['REQUEST_URI'];
if (preg_match('#^/api/user/(\d+)$#', $route, $matches)) {
    $userId = (int)$matches[1];
    $user = getUserById($userId); // может вернуть false
    if (!$user) {
        json404('User not found');
    }
    echo json_encode($user);
} else {
    json404('Invalid API endpoint');
}
?>
Результат: клиент получает HTTP 404 и JSON: 
{"error":"User not found","code":404}

Пример 3: Кастомный обработчик 404 с логированием и разными шаблонами

Комбинируем .htaccess с PHP-скриптом, который логирует запрос и показывает разные страницы в зависимости от user-agent.

Пример

<?php
// 404.php
http_response_code(404);

// Логирование
$log = sprintf("[%s] 404 %s от %s\n", date('Y-m-d H:i:s'), $_SERVER['REQUEST_URI'], $_SERVER['REMOTE_ADDR']);
file_put_contents(__DIR__ . '/404.log', $log, FILE_APPEND);

// Выбор шаблона
$ua = $_SERVER['HTTP_USER_AGENT'] ?? '';
if (preg_match('/bot|spider|crawler/i', $ua)) {
    // для поисковых ботов - минимум кода
    echo '<!DOCTYPE html><title>404</title><h1>Not found</h1>';
} else {
    // полная страница с предложением перейти на главную
    include __DIR__ . '/templates/404_full.php';
}
?>
Результат: для ботов - простая страница, для людей - кастомный шаблон. Записи в логе позволяют анализировать частоту ошибок.

Пример 4: Обработка 404 через middleware в самописном маршрутизаторе

Микрофреймворк: маршрутизатор вызывает обработчик, если ни один маршрут не подошёл.

Пример

<?php
class Router {
    private $routes = [];

    public function add($pattern, $callback) {
        $this->routes[$pattern] = $callback;
    }

    public function dispatch($uri) {
        foreach ($this->routes as $pattern => $callback) {
            if (preg_match($pattern, $uri, $params)) {
                array_shift($params);
                return call_user_func_array($callback, $params);
            }
        }
        // Ни один маршрут не совпал
        $this->handle404($uri);
    }

    private function handle404($uri) {
        http_response_code(404);
        header('Content-Type: text/html; charset=utf-8');
        echo '<h1>404 - Маршрут не найден: ' . htmlspecialchars($uri) . '</h1>';
        error_log('404: ' . $uri);
        exit;
    }
}

$router = new Router();
$router->add('#^/$#', function() { echo 'Главная'; });
$router->add('#^/post/(\d+)$#', function($id) { echo 'Пост ' . $id; });
$router->dispatch($_SERVER['REQUEST_URI']);
?>
Результат: при запросе /test выводится «404 - Маршрут не найден: /test».

Пример 5: Использование буферизации вывода для корректной отправки заголовков 404

Когда в скрипте уже были echo, но нужно переопределить код ответа.

Пример

<?php
ob_start();
echo 'Немного контента...';

// Позже выяснили, что страница не найдена
if ($conditionError) {
    ob_clean(); // очищаем буфер
    http_response_code(404);
    echo '<h1>Страница не найдена (после буферизации)</h1>';
}
ob_end_flush();
?>
Результат: даже если ранее был вывод, заголовки 404 устанавливаются корректно. Важно не забыть ob_clean() перед отправкой новых данных.

Обработка ошибки 404 в PHP - comments

En
404 ошибки php (php)