【PHP, JavaScript】POSTデータが多すぎてエラーになったときにとった対応

画面からユーザーの入力値をPOSTするプログラムで500エラーが発生し、エラーログを確認すると以下のメッセージを吐いていました。

*413 FastCGI sent in stderr: "PHP message: PHP Warning:
Unknown: Input variables exceeded nnn.

このエラーはユーザーの入力値が多すぎて、submitしたときサーバー側で弾かれているエラーで、しきい値は php.ini の以下で設定できます。

php.ini

; How many GET/POST/COOKIE input variables may be accepted
max_input_vars = 100

しかし本来セキュリティの観点から、この値は抑えられている(デフォルトは1000)ケースがあります。その場合は、フロント側で吸収する必要があるためその方法をまとめます。※あくまで一つの例なので他に良い方法があるかもしれません。
今回発生したケースでは、ユーザーや店舗などの設定値を縦に並べて一括で更新するという処理で、100は余裕で超えてしまうため対応を行いました。

POSTデータを入力するView(修正前)

(※だいぶ簡単にしたもの)
<form method="post" action="/users/index">
    <input type="submit" name="regist">更新</input>
    <?php foreach($users as $user):?>
        <label><?php echo $user['id'];?></label>
        <label><?php echo $user['name'];?></label>
        <input type="text" name="age[<?php echo $user['id'];?>]"
            value="<?php echo $user['age'];?>" />
        <br>
    <?php endforeach;>
</form>

これはユーザーの年齢を一括で更新する例で、submitするとユーザーIDをキーに持った連想配列で年齢がPOSTされます。
この場合、ユーザー数に依存してPOSTデータの数が増えていき、ユーザー数が100を超えたとき前述のエラーとなります。

POSTデータを入力するView(修正後)

方法としては入力値を一つのJSONに圧縮してJavaScriptでsubmitするという対応をとりました。そのためのViewが以下です。
<form id="input_form" method="post" action="/users/index">
    <input type="hidden" name="input_data" />
    <input type="submit" name="regist">更新</input>
</form>
<?php foreach($users as $user):?>
    <label><?php echo $user['id'];?></label>
    <label><?php echo $user['name'];?></label>
    <input
        type="text"
        data-user-id="<?php echo $user['id'];?>"
        name="age[]"
        value="<?php echo $user['age'];?>" />
    <br>
<?php endforeach;>

まず入力値のinputタグをformの外へ出します。これをしないと結局 submit で全部サーバーに送られてしまいます。
次にJSでformのsubmitイベントをハンドリングするため、id属性を付与します。
さらにPOSTするjson用の項目(hidden)をformに用意します。
入力値になるinputタグにはカスタム属性(data-user-id)を追加します。これによりJS側で入力値に対するユーザーIDが特定できます。

JavaScript

JSで複数の入力値をJSONに圧縮してsubmitするコードが以下...
$(document).ready(function() {
    $('#input_form').submit(function() {
        var post_json = {
            age:get_values('age'),
        };
        var f = document.forms['input_form'];
        f.input_data.value = JSON.stringify(post_json);
        f.submit();
    });
    function get_values(name) {
        var ret = {};
        var input_values = $('input[name="' + name + '[]"]');
        for (i = 0; i < input_values.length; i++) {
            user_id = $('#' + input_values[i].id).data('user-id');
            value = input_values[i].value;
            ret[user_id] = value;
        }
        return ret;
    }
});

get_values関数はリスト形式で定義されたinputタグを {user_id : value, user_id : value, ...} の形式で取得する関数で、こうしておけば新たな項目が増えても即対応できます。

これでひとまとめにした入力値のjsonをhiddenで用意した領域にセットしてsubmitしてやります。

サーバー側ではPOSTデータのinput_dataに修正前と同じ形で入って来るので、同じValidationを通してやれば良いわけです。

コメント

このブログの人気の投稿

docker-compose up で proxyconnect tcp: dial tcp: lookup proxy.example.com: no such host

docker-compose で起動したweb、MySQLに接続できない事象

【PHP】PHP_CodeSnifferを使う(コーディングルールのカスタマイズ)