Skip to content

Instantly share code, notes, and snippets.

@t-cool
Forked from jesperorb/php_form_submit.md
Last active February 21, 2022 11:54
Show Gist options
  • Save t-cool/82d1599cca46d27b52844c11537cd0b7 to your computer and use it in GitHub Desktop.
Save t-cool/82d1599cca46d27b52844c11537cd0b7 to your computer and use it in GitHub Desktop.
PHP form submitting with fetch + async/await

PHP Form submitting

アプリケーションに以下のような構造があるとします。

  • 📁 application_folder_name
    • 📄 index.php
    • 📄 handle_form.php
    • 📄 main.js

そして、フォームを備えた基本的なウェブサイトを動作させるために、index.php に以下の内容を記述します。これは、お好みのphpサーバーで実行できるはずです。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>PHP</title>
</head>
<body>

    <form action="handle_form.php" method="POST" id="example_form">
        <label for="username">Username</label>
        <input type="text" name="username" id="username">
        <label for="favorite_number">Favorite number</label>
        <input type="number" name="favorite_number" id="favorite_number">
        <input type="submit" value="Skicka">
    </form>

    <script src="main.js"></script>
</body>
</html>

PHPでフォームを送信する

フォームに注目してみましょう。フォームを正常に送信するには、入力内容を form 要素の中に記述する必要があります。これは、「通常通り」にフォームを送信した場合に、入力内容も一緒に送信されることを示しています。JavaScript を使用せずにフォームを送信するには、すべての入力項目に name が必要です。実際には、JavaScript や PHP を使用してデータを送信するかどうかにかかわらず、name が必要です。実際のフォームには 3 つの項目が必要です。

  • action - この情報をどのファイルに送るかをフォームに伝えます。ここでは、index.phpと同じフォルダーにあるhandle_form.phpを使います。
  • method - どのようにこの情報を送信するか。 POST または GET です。デフォルトは GET です。
  • <input type="submit"> - Tクリックでフォームを送信できるようにするためには、JavaScriptで処理する場合でも、必ず「送信」ボタンを設置する必要があります。このボタンは、<button type="submit"> Send </button>とすることで、ボタンの中にHTMLを入れることができます(例えば、ボタンの中にアイコンを入れることができます)。
<form action="handle_form.php" method="POST" id="example_form">
    <label for="username">Username</label>
    <input type="text" name="username" id="username">
    <label for="favorite_number">Favorite number</label>
    <input type="number" name="favorite_number" id="favorite_number">
    <input type="submit" value="Skicka">
</form>

リクエストを正常に送信するためには、これらの情報がすべてHTML内に存在する必要があります。このマークアップが正しければ、 handle_form.php というファイルにフォームを送信することができるはずです。

<?php
echo $_POST["username"];
echo $_POST["favorite_number"];

<input name="username"> とすると、入力フィールドの値が $_POST["username"] に格納されます。つまり、入力フィールドのnameによって、PHPでの変数の呼び名が決まるのです。

  • フォームで method="POST" を使用すると、値は $_POST に格納されます。
  • もし、method="GET" を使用するか、method をフォームに指定しない場合、値は $_GET に格納されます。
  • PHP には、これらの値を含むことができる $_REQUEST 変数もあります。

$_POST$_GET は常に連想配列であり、これは変数が作成されない場合の書き方です。

<?php
$_POST = [
    "username"          => "zero_cool",
    "favorite_number"   => "10"
];

JavaScriptに相当するものは以下の通りです。

var $_POST = {
    username: "zero_cool",
    favorite_number: "10"
};

$_POST "の代わりに "GET "を使う

代わりに GET で同じ情報を送ることができます。

<form action="handle_form.php" method="GET" id="example_form">
    <label for="username">Username</label>
    <input type="text" name="username" id="username">
    <label for="favorite_number">Favorite number</label>
    <input type="number" name="favorite_number" id="favorite_number">
    <input type="submit" value="Skicka">
</form>

この場合の唯一の違いは、$_POSTではなく$_GETの中にデータを格納することです。

<?php
// Inside of `handle_form.php`
echo $_GET["username"];
echo $_GET["favorite_number"];

example

<?php
$_GET = [
    "username"          => "zero_cool",
    "favorite_number"   => "10"
];

JavaScriptに相当するものは以下の通りです。

var $_GET = {
    username: "zero_cool",
    favorite_number: "10"
};

もう一つの違いは、URLの中に情報が入ってくることです。

http://localhost:8888/handle_form.php?username=zero_cool&favorite_number=10

これは、基本的に、フォームを処理するようなPHPファイルを、代わりにリンクで呼び出すことができることを意味します。つまり、以下のようなコードを書いても同じ結果が得られるということです。なぜなら、情報は常にURLを介して送信され、POSTのようにボディの中では ない からです。

<a href="http://localhost:8888/handle_form.php?username=zero_cool&favorite_number=10">
Click me to submit
</a>

JavaScriptでフォームを送信する

ページをリロードすることなく、JavaScriptを使用して同じフォームを送信することができます。同じロジックがPHPファイルの中で適用され、作業のほとんどはJavaScriptで行われます。上記と同じフォームがある場合、まずフォームが 送信されていない ことを確認する必要があります。フォームのデフォルトの動作は、新しいサイトにリダイレクトすることで、これはJavaScriptがない場合には問題ありません。しかし、この場合には、JavaScriptを利用することができます。

// 個々の入力フィールドではなく、フォーム全体を取得する
const form = document.getElementById('example_form');

/**
 * コールバック関数は常にあなたが何をクリックしたかを知り、
 * 最初のパラメータとしてイベントオブジェクトを関数に与えます。
 */
form.addEventListener('click', function(event){
    // イベントがフォームを送信するのを防ぎ、リダイレクトやページの再読み込みを行わない
    event.preventDefault();
});

フォームが通常の動作をしないようにしました。つまり、代わりに何をすべきかを指定する必要があるということです。つまり、バックエンドと通信する必要があることがわかりました。クライアント側とサーバー側が同じドメイン上にあっても、JavaScriptはAJAXを経由せずにPHPに通信することができません。AJAXは必ず使用しなければなりません。それでは、ネイティブで組み込まれているAJAXの使用方法である fetch を使用してみましょう。以下の最初のコードは動作しないことに注意してください。これは単なる最初のステップです。

// 個々の入力フィールドではなく、フォーム全体を取得する
const form = document.getElementById('example_form');

/**
 * コールバック関数は常にあなたが何をクリックしたかを知り、
 * 最初のパラメータとしてイベントオブジェクトを関数に与えます。
 */
form.addEventListener('click', function(event){
    // イベントがフォームを送信するのを防ぎ、リダイレクトやページの再読み込みを行わない
    event.preventDefault();
    postData();
});

async function postData(){
    /*
     * 先ほどと同じファイルを使い、バックエンドのコードも実際のフォームも触らないようにしています。
     * フォームから action 属性を取得することもできますが、ここでは単に 'URL' を入力する方が簡単です。
     * PORTや'localhost'を指定する必要はありません。
     */
    const response = await fetch('handle_form.php');
    /*
     * handle_form.php` の中で `echo` を使用しているので、
     * 応答は JSON データではなく文字列になります。
     * このため、`response.json()` ではなく `response.text()` を使用して、
     * JavaScriptが理解できるように変換する必要があります。
     */
    const data = await response.text();
    // これで、フォームから送信された値が出力されます
    console.log(data);
}

しかし、送信する情報がまだ不足しています。生のPHP/HTMLでは、フォームがデータを変換して handle_form.php に送信します。データを送信するための独自のロジックを書く場合は、データが適切にフォーマットされていることを確認する必要があります。

データは x-www-form-urlencoded または multipart/form-data にフォーマットされている必要があり、これは PHP が受信することを期待しているものです。これを扱う最も簡単な方法は、FormData-objectを使用することです。JSONを受け入れるようにすることもできますが、FormData -object を使うほうが簡単です。

// 個々の入力フィールドではなく、フォーム全体を取得する
const form = document.getElementById('example_form');

/**
 * コールバック関数は常にあなたが何をクリックしたかを知り、
 * 最初のパラメータとしてイベントオブジェクトを関数に与えます。
 */
form.addEventListener('click', function(event){
    // イベントがフォームを送信するのを防ぎ、リダイレクトやページの再読み込みを行わない
    event.preventDefault();
    /**
     * フォーム内のすべての入力値を使用したい場合は、送信するフォームを引数として 
     * `new FormData()` を呼び出すことができます。 
     * これにより、PHPが適切に読める body オブジェクトが作成されます。
     */
    const formattedFormData = new FormData(form);
    postData(formattedFormData);
});

async function postData(formattedFormData){
    /**
     * 何かを'POST'したい場合には、`method`を'POST'に変更する必要があります。
     * POST では、リクエストが `body` の中の値を送信することも想定しているので、
     * このプロパティも指定する必要があります。
     * ここでは、先ほど作成したFormData()オブジェクトを使って、それを渡すだけです。
     */
    const response = await fetch('handle_form.php',{
        method: 'POST',
        body: formattedFormData
    });
    /*
     * handle_form.php` の中で `echo` を使用しているので、レスポンスは 
     * JSON データではなく文字列になります。
     * このため、`response.json()`ではなく`response.text()`を使用して、
     * JavaScriptが理解できるように変換する必要があります。
     */
    const data = await response.text();
    // これで、バックエンド側に送信した値が出力されます。
    console.log(data);
}

JavaScriptで FormData にさらに値を追加する

フォームの内部にないものをこのオブジェクトに追加したい場合は、FormData-オブジェクトの.append()メソッドを使用します。

form.addEventListener('click', function(event){
    event.preventDefault();
    const formattedFormData = new FormData(form);
    formattedFormData.append('property', 'value');
    postData(formattedFormData);
});

GET "と "POST "の両方を送信する場合

フォームと fetch コールは GETPOST のどちらかしか使いません。1つのリクエストは、これらのリクエストのうちの1つだけです。しかし、URLで値を送ることは可能で、リクエストがPOSTであっても、これらは$_GET変数の中に格納されます。

const form = document.getElementById('example_form');
form.addEventListener('click', function(event){
    event.preventDefault();
    const formattedFormData = new FormData(form);
    postData(formattedFormData);
});

async function postData(formattedFormData){
    /**
     * リクエストはまだ'POST'ですが、$_GET変数には'name'と'favorite_color'という値も入ります。
     */
    const response = await fetch(
        'handle_form.php?name=Jesper&favorite_color=pink',
        {
            method: 'POST',
            body: formattedFormData
        }
    );
    const data = await response.text();
    console.log(data);
}

これにより、handle_form.php内に以下の変数が設定されることになります。

<?php
// handle_form.php`の内部
echo $_POST["username"];
echo $_POST["favorite_number"];
echo $_GET["name"];
echo $_GET["favorite_color"];

JSON の利用

FormData は、サーバーが x-www-form-urlencoded 形式でデータを受信することを期待する場合に使用します。サーバーによっては、データを JSON オブジェクトとして送信することを好む場合もあります。つまり、フロントエンドとバックエンドの両方にいくつかの変更を加える必要があります。先ほどと同じフォームを使用する場合、以下のようになります。

const form = document.getElementById('example_form');
form.addEventListener('click', function(event){
    event.preventDefault();
    /*
     * new FormData(this) "のようなショートカットはなく、自分でフォームオブジェクトを構築する必要があります。
     * ここでは FormData オブジェクトではなく、通常のオブジェクトを作成しています。
     * form.username.value や form.favorite_number.value と書くこともできます。
     */
    const formattedFormData = {
        username: this.username.value,
        favorite_number: this.favorite_number.value
    }
    postData(formattedFormData);
});

async function postData(formattedFormData){
    /**
     * リクエストは'POST'のままですが、
     * $_GET変数には'name'と'favorite_color'の値も入ります。
     */
    const response = await fetch(
        'handle_form.php',
        {
            method: 'POST',
            /*
             * また、値を文字列化して、JavaScriptオブジェクトをJSONとして
             * 受け入れられる単一の文字列にする必要があります。
             * そこで、すべての値を含む1つの文字列を送信します。
             */
            body: JSON.stringify(formattedFormData)
        }
    );
    const data = await response.text();
    console.log(data);
}

このようにデータを送信する場合には、PHPにデータがこのように受け取られることを期待するように伝える必要があります。

<?php
// handle_form.php の内部

/* JSONとして送られてきたすべてのデータを解析しています。json_decode` は、JSON オブジェクトを PHP の連想配列に変換し、それを `$data` に格納します。
 */
$data = json_decode(file_get_contents('php://input'), true);
echo $data["username"];
echo $data["favorite_number"];

データをJSONとして受け取ることを選択した場合、おそらくJSONを送り返す必要があります。つまり、json形式の値を「echo」で返したい場合は、データを送信する前にjson_encodeを使用する必要があります。これは、データに対して何もしていないので、ちょっと間抜けな例ですが、構文とその動作のコツを知るためのものです。

<?php
// 'handle_form.php' の内部
$data = json_decode(file_get_contents('php://input'), true);

echo json_encode($data);

If we return JSON we must also use .json() instead of .text() in JavaScript:

const data = await response.text();
const data = await response.json();

PHP API Proxy

これは、一部のAPIがブラウザ経由でのAPIへのリクエストを許可していないことを意味する**CORS**によって制限されている場合に便利です。私はこの問題を回避するための小さなガイドをここに書きました。 Handle CORS Client-side

自分のサーバを経由してデータをリダイレクトするための小さなプロキシファイルを作ることができます。例として Pokémon API を見てみましょう。これはCORSを許可していないからです。そこで、APIを直接呼び出すのではなく、APIを呼び出す独自のファイルを呼び出します。

async function fetchPokemon(){
  const response = await fetch('fetch_pokemon.php?pokemon=25');
  const pokemon = await response.json();
  console.log(pokemon);
}

fetch_pokemon.phpの中では、通常、APIからデータを取得するために使用されるfile_get_contents`を使用しています。

<?php
$response = file_get_contents('http://pokeapi.co/api/v2/pokemon/' . $_GET["pokemon"]);
echo $response;

? の後にクエリパラメータを追加することで、どのポケモンを取得するかを送信していることに注目してください。この場合、キーは $_GET["pokemon"] となり、値は 25 となります。CORSエラーは発生しません。

** ユーザーからのデータを信用しないで、慎重に行動してください。このようなリクエストでユーザーからのデータを送信すると、有害な場合があります。送信する前に必ず入力内容を検証してください**

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment