$_GETはURLのクエリ文字列の配列なので、article.php?id=42&lang=enなら$_GET['id'] === '42'、$_GET['lang'] === 'en'になります。$_POSTは、method="post"で送信されたフォームの項目の配列です。パラメータがなければキーもないので、どちらも$_GET['id'] ?? nullのように既定値付きで読みます。
ブロックを実行してからリンクをクリックすると、クリックのたびにリンクのクエリ文字列から$_GETが埋められた状態でスクリプトがもう一度実行されます。
$_GET['lang']はURLに書かれた何でもであって、出力した3つのリンクだけではありません。誰でもアドレスバーに?lang=<script>と入力できるので、このブロックは値を出力せずに$languagesで引き、リクエストのデータを出力する唯一の場所ではエスケープしています。
クエリ文字列から数値を読む
$_GETのすべての値は文字列です。ページ番号なら、filter_var()で検証と変換を1ステップで行い、値がない、数値でない、範囲外のときは既定値に戻します。ページをクリックして移動し、Page 999とPage abcのリンクも試してみてください。
filter_var()は"3"に対して整数の3を返し、それ以外の文字列にはdefaultオプションを返すので、$pageは常に使える整数になります。ほかのバリデーターはfilter_varのページに載っています。
$_POSTでフォームのデータを読む
method="post"のフォームは項目をリクエストの本文で送り、PHPはそれを各入力欄のnameの下で$_POSTに入れます。$_SERVER['REQUEST_METHOD']は、現在のリクエストが最初の訪問(GET)か送信(POST)かを教えてくれます。フォームに入力してSendを押すと、両方が確認できます。
空のテキスト欄も""として送られるので、送信後は訪問者が何も入力していなくても$_POST['title']は存在します。サーバーではREQUEST_METHODは常に設定されていて、?? 'GET'はコマンドラインでphpとしてファイルを実行したときにスクリプトを静かに保つだけです。フォームを検証して再表示する完全なパターンはフォームのページにあります。
method="get"の検索フォーム
検索ボックスのように、結果を共有できるURLにしたいフォームにはGETを使います。ブラウザは項目をクエリ文字列(?q=sort&level=beginner)に変え、スクリプトはリンクとまったく同じように$_GETから読みます。arrayやstringで検索してみてください。
http_build_queryでリンクを作る
"?q=$q&page=2"を手で書くと、$qに空白、&、#が含まれたとたんに壊れます。http_build_query()はすべての値をエンコードしてくれ、結果はhrefに入るときにhtmlspecialchars()を通します。HTMLの中では&は&であるべきだからです。
2つの引数を渡したparse_str()は、PHPがクエリ文字列から$_GETを作るのと同じ方法なので、テストに便利です。2つ目の引数は必ず渡しましょう。変数を作っていた古い1引数の形はPHP 8で削除されました。
$_REQUESTと、それを避けるべき理由
$_REQUESTは$_GETと$_POSTを1つの配列にまとめたものです。PHPに同梱されているphp.iniはrequest_order = "GP"に設定しているので、両方に同じキーがあればPOSTが優先されます。この設定がなければCookieもまとめられます。
<?php
// POST /save.php?id=1 with a form field id=2
echo $_GET['id']; // "1"
echo $_POST['id']; // "2"
echo $_REQUEST['id']; // "2"
便利に見えますが、値がどこから来たかを隠してしまいます。$_REQUEST['id']でレコードを削除する処理は普通のリンクで起動できてしまい、これはまさにPOST専用の操作が防ぐためのものです。代わりに$_GETか$_POSTを名前で読みましょう。
GETとPOST:どちらを使うか
| GET | POST | |
|---|---|---|
| データが運ばれる場所 | URLの中:?q=php&page=2 | リクエストの本文 |
| 読むもの | $_GET | $_POST |
| ブックマーク、共有、戻るボタン | できる | できない(ブラウザが再送信を尋ねる) |
| 長さ | URLで制限される(数千文字なら安全) | post_max_sizeで制限される(デフォルトは8M) |
| ファイルのアップロード | できない | できる、enctype="multipart/form-data"で($_FILESを読む) |
| 使いどころ | 検索、絞り込み、並べ替え、ページ送り | ログイン、登録、保存、削除、決済 |
重要なルールは、GETリクエストは何も変更してはいけないということです。ブラウザはリンクを先読みし、クローラーはリンクをたどり、人はリンクを共有するので、?delete=5のリンクはいずれ削除するつもりのない何かにたどられてしまいます。どちらのメソッドもそれ自体は暗号化されていません。HTTPSが両方を守り、POSTはデータをURL、ブラウザの履歴、サーバーのログに残さないだけです。
よくある質問
PHPでURLからパラメータを取得するには?
既定値付きで$_GETから読みます:page.php?id=42なら、$id = $_GET['id'] ?? null;で文字列"42"が得られます。すべての値は文字列(id[]=なら配列)なので、たとえばfilter_var($id, FILTER_VALIDATE_INT)で変換して検証しましょう。
$_GETと$_POSTの違いは何ですか?
$_GETはURLのクエリ文字列から来るので、見え、ブックマークでき、長さに制限があります。検索、絞り込み、ページに使います。$_POSTはmethod="post"で送信されたフォームのリクエスト本文から来ます。保存、購入、ログインのようにデータを変更するものすべてに使います。
PHPでGETパラメータが存在するか確認するには?
isset($_GET['q'])を使います。キーがないかnullならfalseです。空の値(?q=)も存在しないとみなすには、trimした値を比較します:trim($_GET['q'] ?? '') !== ''。
PHPで$_REQUESTを使うべきですか?
たいていは使うべきではありません。$_REQUESTは$_GETと$_POST(request_orderによってはCookieも)をまとめたものなので、値がどこから来たかがわからず、フォームから来ると思っていた値をリンクが与えることもできてしまいます。$_GETか$_POSTを明示的に読みましょう。
PHPでリクエストがGETかPOSTかを確認するには?
$_SERVER['REQUEST_METHOD']を比較します:if ($_SERVER['REQUEST_METHOD'] === 'POST') { ... }。これはisset($_POST['submit'])より確実です。そちらはボタンにnameがない場合や、その項目なしでJavaScriptやほかのスクリプトからフォームが送られた場合に失敗します。