[PHP]header関数、headers_list、headers_sent関数とは?使い方から実践例まで完全解説【初心者向け】

PHP

PHPでWebアプリケーションを開発していると、ページのリダイレクトやファイルのダウンロード、キャッシュ制御など、様々な場面でHTTPヘッダーを操作する必要があります。そんな時に活躍するのがheader関数です。

今回は、PHPのheader関数について、基本的な使い方から実践的な活用例まで、初心者にも分かりやすく詳しく解説していきます。

header関数とは?

header関数は、PHPからHTTPヘッダーを送信するための組み込み関数です。Webサーバーからブラウザやクライアントアプリケーションに対して、様々な情報や指示を伝えることができます。

基本構文

header(string $header, bool $replace = true, int $response_code = 0)
  • $header: 送信するヘッダー文字列
  • $replace: 同じヘッダーを置き換えるかどうか(デフォルト: true)
  • $response_code: HTTPレスポンスコードを設定

header関数の重要なルール

header関数を使用する際には、以下の重要なルールを理解しておく必要があります。

1. HTMLの出力前に実行する

最も重要なルールは、header関数はHTMLやテキストの出力前に実行しなければならないということです。

<?php
// ❌ 間違った例
echo "Hello World";
header("Location: https://example.com"); // エラーが発生
?>

<?php
// ✅ 正しい例
header("Location: https://example.com");
echo "Hello World";
?>

2. 空白文字にも注意

PHPの開始タグ前に空白があるだけでもエラーになります。

 <?php // ❌ 開始タグ前に空白がある
header("Location: https://example.com");
?>

<?php // ✅ 正しい書き方
header("Location: https://example.com");
?>

実践的な使用例

1. ページリダイレクト

最も頻繁に使用される機能の一つです。

<?php
// 別のページにリダイレクト
header("Location: https://example.com/success.php");
exit(); // リダイレクト後の処理を停止
?>

重要: リダイレクト後は必ずexit()またはdie()を呼び出して、後続の処理を停止しましょう。

2. HTTPステータスコードの設定

<?php
// 404 Not Found
header("HTTP/1.1 404 Not Found");

// または
http_response_code(404);
header("Content-Type: text/html; charset=UTF-8");
?>

3. コンテンツタイプの指定

<?php
// JSON形式のデータを返す
header("Content-Type: application/json; charset=UTF-8");
echo json_encode(["message" => "Hello World"]);
?>

<?php
// CSVファイルとしてダウンロード
header("Content-Type: text/csv");
header("Content-Disposition: attachment; filename=\"data.csv\"");
echo "名前,年齢,職業\n";
echo "田中太郎,30,エンジニア\n";
?>

4. キャッシュ制御

<?php
// キャッシュを無効にする
header("Cache-Control: no-cache, must-revalidate");
header("Expires: Sat, 26 Jul 1997 05:00:00 GMT");
header("Pragma: no-cache");
?>

<?php
// 1時間キャッシュする
header("Cache-Control: public, max-age=3600");
header("Expires: " . gmdate("D, d M Y H:i:s", time() + 3600) . " GMT");
?>

5. ファイルダウンロード

<?php
$filename = "sample.pdf";
$filepath = "/path/to/file/" . $filename;

if (file_exists($filepath)) {
    header("Content-Type: application/octet-stream");
    header("Content-Disposition: attachment; filename=\"" . $filename . "\"");
    header("Content-Length: " . filesize($filepath));
    readfile($filepath);
    exit();
} else {
    header("HTTP/1.1 404 Not Found");
    echo "ファイルが見つかりません";
}
?>

6. CORS(Cross-Origin Resource Sharing)の設定

<?php
// 特定のドメインからのアクセスを許可
header("Access-Control-Allow-Origin: https://example.com");

// すべてのドメインからのアクセスを許可(開発環境のみ推奨)
header("Access-Control-Allow-Origin: *");

// 許可するHTTPメソッド
header("Access-Control-Allow-Methods: GET, POST, PUT, DELETE");

// 許可するヘッダー
header("Access-Control-Allow-Headers: Content-Type, Authorization");
?>

よくあるエラーとその対処法

“Headers already sent” エラーの根本的対策

<?php
echo "何かの出力"; // この行が原因
header("Location: redirect.php"); // エラー発生
?>

対処法:

  • header関数を出力より前に移動する
  • 出力バッファリング(ob_start())を使用する
  • headers_sent()で事前チェックする
<?php
ob_start(); // 出力バッファリング開始
echo "何かの出力";

if (!headers_sent()) {
    header("Location: redirect.php");
    ob_end_clean(); // バッファをクリア
    exit();
}
ob_end_flush(); // バッファを出力
?>

headers_sent()を使った事前チェック:

<?php
function setHeaderSafely($header) {
    if (headers_sent($filename, $linenum)) {
        error_log("Warning: Cannot set header '{$header}'. Headers already sent in {$filename} on line {$linenum}");
        return false;
    }
    header($header);
    return true;
}

// 使用例
echo "何かの出力";
if (!setHeaderSafely("Content-Type: application/json")) {
    // ヘッダー設定に失敗した場合の代替処理
    echo "<!-- Content-Type: application/json -->";
}
?>

2. 文字エンコーディングの問題

<?php
// 日本語を含むヘッダーを送信する場合
header("Content-Disposition: attachment; filename=\"" . urlencode("日本語ファイル名.txt") . "\"");
?>

セキュリティ上の注意点

1. オープンリダイレクト脆弱性

<?php
// ❌ 危険な例
$redirect_url = $_GET['url'];
header("Location: " . $redirect_url);
?>

<?php
// ✅ 安全な例
$redirect_url = $_GET['url'];
$allowed_urls = [
    'https://example.com/page1.php',
    'https://example.com/page2.php'
];

if (in_array($redirect_url, $allowed_urls)) {
    header("Location: " . $redirect_url);
} else {
    header("Location: /error.php");
}
?>

2. HTTPヘッダーインジェクション

<?php
// ❌ 危険な例
$user_input = $_GET['type'];
header("Content-Type: " . $user_input);
?>

<?php
// ✅ 安全な例
$user_input = $_GET['type'];
$allowed_types = ['text/html', 'application/json', 'text/plain'];

if (in_array($user_input, $allowed_types)) {
    header("Content-Type: " . $user_input);
} else {
    header("Content-Type: text/html");
}
?>

headers_sent関数 – ヘッダー送信状態のチェック

ヘッダー関連の関数でもう一つ重要なのがheaders_sent()関数です。この関数は、HTTPヘッダーが既に送信されているかどうかをチェックできます。

headers_sent関数の基本的な使い方

<?php
// 基本的な使用例
if (headers_sent()) {
    echo "ヘッダーは既に送信されています";
} else {
    echo "まだヘッダーは送信されていません";
    header("Content-Type: application/json");
}
?>

ファイル名と行番号も取得する

<?php
$filename = '';
$linenum = 0;

if (headers_sent($filename, $linenum)) {
    echo "ヘッダーは既に送信されています\n";
    echo "送信された場所: {$filename} の {$linenum} 行目";
} else {
    header("X-Custom-Header: 安全に設定");
}
?>

実践的な活用例

1. 安全なリダイレクト関数

<?php
function safeRedirect($url) {
    if (headers_sent($filename, $linenum)) {
        // ヘッダーが既に送信されている場合はJavaScriptでリダイレクト
        echo "<script>window.location.href = '" . htmlspecialchars($url, ENT_QUOTES) . "';</script>";
        echo "<noscript><meta http-equiv='refresh' content='0;url=" . htmlspecialchars($url, ENT_QUOTES) . "'></noscript>";
        echo "<p>リダイレクト中... <a href='" . htmlspecialchars($url, ENT_QUOTES) . "'>こちらをクリック</a></p>";
    } else {
        header("Location: " . $url);
        exit();
    }
}

// 使用例
echo "何かの処理...";
safeRedirect("https://example.com/success.php");
?>

2. 柔軟なエラーハンドリング

<?php
function handleError($message, $code = 500) {
    if (headers_sent($filename, $linenum)) {
        // ヘッダーが送信済みの場合はHTMLでエラー表示
        echo "<div style='color: red; border: 1px solid red; padding: 10px; margin: 10px;'>";
        echo "<h3>エラーが発生しました</h3>";
        echo "<p>" . htmlspecialchars($message) . "</p>";
        echo "<small>注意: HTTPステータスコードを設定できませんでした。";
        echo "ヘッダーは既に {$filename} の {$linenum} 行目で送信されています。</small>";
        echo "</div>";
    } else {
        // ヘッダーが未送信の場合は適切なHTTPステータスを設定
        http_response_code($code);
        header("Content-Type: text/html; charset=UTF-8");
        echo "<h1>エラー {$code}</h1>";
        echo "<p>" . htmlspecialchars($message) . "</p>";
    }
    exit();
}

// 使用例
echo "処理中...";
if ($someError) {
    handleError("データベース接続に失敗しました", 500);
}
?>

3. APIレスポンス管理クラス

<?php
class ApiResponse {
    private $data = [];
    private $statusCode = 200;
    private $headers = [];
    
    public function setData($data) {
        $this->data = $data;
        return $this;
    }
    
    public function setStatusCode($code) {
        $this->statusCode = $code;
        return $this;
    }
    
    public function addHeader($header) {
        $this->headers[] = $header;
        return $this;
    }
    
    public function send() {
        if (headers_sent($filename, $linenum)) {
            // デバッグ情報付きでエラーレスポンス
            $errorResponse = [
                'error' => 'Headers already sent',
                'debug' => [
                    'file' => $filename,
                    'line' => $linenum,
                    'intended_status' => $this->statusCode,
                    'intended_headers' => $this->headers
                ],
                'data' => $this->data
            ];
            echo json_encode($errorResponse, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
        } else {
            // 正常なレスポンス
            http_response_code($this->statusCode);
            header("Content-Type: application/json; charset=UTF-8");
            
            foreach ($this->headers as $header) {
                header($header);
            }
            
            echo json_encode($this->data, JSON_UNESCAPED_UNICODE);
        }
        exit();
    }
}

// 使用例
$api = new ApiResponse();
echo "デバッグ出力"; // これによりヘッダーが送信される

$api->setData(['message' => 'Hello World'])
    ->setStatusCode(200)
    ->addHeader('X-API-Version: 1.0')
    ->send();
?>

4. 開発用デバッグ関数

<?php
function debugHeaders() {
    $filename = '';
    $linenum = 0;
    
    echo "<div style='background: #f0f0f0; padding: 15px; margin: 10px; border-left: 4px solid #007cba;'>";
    echo "<h3>🔍 ヘッダー状態デバッグ情報</h3>";
    
    if (headers_sent($filename, $linenum)) {
        echo "<p><strong>❌ ヘッダー送信状態:</strong> 既に送信済み</p>";
        echo "<p><strong>📍 送信場所:</strong> " . htmlspecialchars($filename) . " の " . $linenum . " 行目</p>";
        echo "<p><strong>⚠️ 影響:</strong> 新しいヘッダーやリダイレクトは設定できません</p>";
    } else {
        echo "<p><strong>✅ ヘッダー送信状態:</strong> 未送信(設定可能)</p>";
    }
    
    $currentHeaders = headers_list();
    if (!empty($currentHeaders)) {
        echo "<p><strong>📋 設定済みヘッダー:</strong></p>";
        echo "<ul>";
        foreach ($currentHeaders as $header) {
            echo "<li>" . htmlspecialchars($header) . "</li>";
        }
        echo "</ul>";
    } else {
        echo "<p><strong>📋 設定済みヘッダー:</strong> なし</p>";
    }
    
    echo "</div>";
}

// 開発環境でのみ表示
if ($_SERVER['SERVER_NAME'] === 'localhost') {
    debugHeaders();
}
?>

headers_list関数との組み合わせ

header関数と合わせて覚えておきたいのがheaders_list()関数です。この関数を使うことで、現在設定されているヘッダーの一覧を取得できます。

headers_list関数の基本的な使い方

<?php
// ヘッダーを設定
header("Content-Type: application/json");
header("Cache-Control: no-cache");
header("X-Custom-Header: MyValue");

// 設定されたヘッダーの一覧を取得
$headers = headers_list();
print_r($headers);

/*
出力例:
Array
(
    [0] => Content-Type: application/json
    [1] => Cache-Control: no-cache
    [2] => X-Custom-Header: MyValue
)
*/
?>

デバッグ時の活用例

<?php
function debug_headers() {
    echo "<h3>現在設定されているヘッダー:</h3>";
    echo "<pre>";
    foreach (headers_list() as $header) {
        echo htmlspecialchars($header) . "\n";
    }
    echo "</pre>";
}

// ヘッダーを設定
header("Content-Type: text/html; charset=UTF-8");
header("X-Debug-Mode: true");

// 開発環境でのみヘッダー情報を表示
if ($_SERVER['SERVER_NAME'] === 'localhost') {
    debug_headers();
}
?>

条件付きヘッダー設定

<?php
// 特定のヘッダーが既に設定されているかチェック
function isHeaderSet($headerName) {
    $headers = headers_list();
    foreach ($headers as $header) {
        if (stripos($header, $headerName . ':') === 0) {
            return true;
        }
    }
    return false;
}

// Content-Typeが設定されていない場合のみ設定
if (!isHeaderSet('Content-Type')) {
    header("Content-Type: text/html; charset=UTF-8");
}

// カスタムヘッダーの重複チェック
if (!isHeaderSet('X-API-Version')) {
    header("X-API-Version: 1.0");
}
?>

APIレスポンスでの活用例

<?php
function sendApiResponse($data, $status = 200) {
    // レスポンスコードを設定
    http_response_code($status);
    
    // 基本的なAPIヘッダーを設定
    header("Content-Type: application/json; charset=UTF-8");
    header("X-API-Version: 2.0");
    header("Access-Control-Allow-Origin: *");
    
    // デバッグモードの場合、ヘッダー情報も含める
    if (defined('DEBUG_MODE') && DEBUG_MODE) {
        $response = [
            'data' => $data,
            'debug' => [
                'headers' => headers_list(),
                'timestamp' => date('Y-m-d H:i:s')
            ]
        ];
    } else {
        $response = ['data' => $data];
    }
    
    echo json_encode($response, JSON_UNESCAPED_UNICODE);
    exit();
}

// 使用例
$userData = ['name' => '田中太郎', 'age' => 30];
sendApiResponse($userData);
?>

まとめ

header関数は、PHPでWebアプリケーションを開発する上で欠かせない重要な関数です。headers_list()関数と組み合わせることで、より効果的なヘッダー管理とデバッグが可能になります。

重要なポイントをもう一度整理すると:

  • 出力前に実行することが最重要
  • リダイレクト後はexit()を忘れずに
  • セキュリティ対策を怠らない
  • 用途に応じて適切なヘッダーを設定する
  • headers_list()でデバッグや条件分岐を効率化
  • headers_sent()で安全なヘッダー操作を実現

これらのポイントを押さえて、header関数、headers_list関数、headers_sent関数を効果的に活用してください。実際の開発では、これら3つの関数を組み合わせることで、より堅牢で保守性の高いWebアプリケーションを開発できます。

タイトルとURLをコピーしました